Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
3fd70e37bd | ||
|
5580cfaf63 |
@ -3,6 +3,7 @@
|
|||||||
const Cairo = imports.cairo;
|
const Cairo = imports.cairo;
|
||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
|
const GLib = imports.gi.GLib;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const Mainloop = imports.mainloop;
|
const Mainloop = imports.mainloop;
|
||||||
const Pango = imports.gi.Pango;
|
const Pango = imports.gi.Pango;
|
||||||
@ -235,10 +236,12 @@ const AppMenuButton = new Lang.Class({
|
|||||||
Name: 'AppMenuButton',
|
Name: 'AppMenuButton',
|
||||||
Extends: PanelMenu.Button,
|
Extends: PanelMenu.Button,
|
||||||
|
|
||||||
_init: function() {
|
_init: function(menuManager) {
|
||||||
this.parent(0.0);
|
this.parent(0.0, true);
|
||||||
|
|
||||||
this._startingApps = [];
|
this._startingApps = [];
|
||||||
|
|
||||||
|
this._menuManager = menuManager;
|
||||||
this._targetApp = null;
|
this._targetApp = null;
|
||||||
|
|
||||||
let bin = new St.Bin({ name: 'appMenu' });
|
let bin = new St.Bin({ name: 'appMenu' });
|
||||||
@ -264,10 +267,6 @@ const AppMenuButton = new Lang.Class({
|
|||||||
|
|
||||||
this._iconBottomClip = 0;
|
this._iconBottomClip = 0;
|
||||||
|
|
||||||
this._quitMenu = new PopupMenu.PopupMenuItem('');
|
|
||||||
this.menu.addMenuItem(this._quitMenu);
|
|
||||||
this._quitMenu.connect('activate', Lang.bind(this, this._onQuit));
|
|
||||||
|
|
||||||
this._visible = !Main.overview.visible;
|
this._visible = !Main.overview.visible;
|
||||||
if (!this._visible)
|
if (!this._visible)
|
||||||
this.actor.hide();
|
this.actor.hide();
|
||||||
@ -446,12 +445,6 @@ const AppMenuButton = new Lang.Class({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_onQuit: function() {
|
|
||||||
if (this._targetApp == null)
|
|
||||||
return;
|
|
||||||
this._targetApp.request_quit();
|
|
||||||
},
|
|
||||||
|
|
||||||
_onAppStateChanged: function(appSys, app) {
|
_onAppStateChanged: function(appSys, app) {
|
||||||
let state = app.state;
|
let state = app.state;
|
||||||
if (state != Shell.AppState.STARTING) {
|
if (state != Shell.AppState.STARTING) {
|
||||||
@ -513,8 +506,10 @@ const AppMenuButton = new Lang.Class({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (targetApp == this._targetApp) {
|
if (targetApp == this._targetApp) {
|
||||||
if (targetApp && targetApp.get_state() != Shell.AppState.STARTING)
|
if (targetApp && targetApp.get_state() != Shell.AppState.STARTING) {
|
||||||
this.stopAnimation();
|
this.stopAnimation();
|
||||||
|
this._maybeSetMenu();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -528,16 +523,40 @@ const AppMenuButton = new Lang.Class({
|
|||||||
let icon = targetApp.get_faded_icon(2 * PANEL_ICON_SIZE);
|
let icon = targetApp.get_faded_icon(2 * PANEL_ICON_SIZE);
|
||||||
|
|
||||||
this._label.setText(targetApp.get_name());
|
this._label.setText(targetApp.get_name());
|
||||||
// TODO - _quit() doesn't really work on apps in state STARTING yet
|
|
||||||
this._quitMenu.label.set_text(_("Quit %s").format(targetApp.get_name()));
|
|
||||||
|
|
||||||
this._iconBox.set_child(icon);
|
this._iconBox.set_child(icon);
|
||||||
this._iconBox.show();
|
this._iconBox.show();
|
||||||
|
|
||||||
if (targetApp.get_state() == Shell.AppState.STARTING)
|
if (targetApp.get_state() == Shell.AppState.STARTING)
|
||||||
this.startAnimation();
|
this.startAnimation();
|
||||||
|
else
|
||||||
|
this._maybeSetMenu();
|
||||||
|
|
||||||
this.emit('changed');
|
this.emit('changed');
|
||||||
|
},
|
||||||
|
|
||||||
|
_maybeSetMenu: function() {
|
||||||
|
let menu;
|
||||||
|
|
||||||
|
if (this._targetApp.action_group) {
|
||||||
|
if (this.menu instanceof PopupMenu.RemoteMenu &&
|
||||||
|
this.menu.actionGroup == this._targetApp.action_group)
|
||||||
|
return;
|
||||||
|
|
||||||
|
menu = new PopupMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group);
|
||||||
|
} else {
|
||||||
|
if (this.menu && !(this.menu instanceof PopupMenu.RemoteMenu))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// fallback to older menu
|
||||||
|
menu = new PopupMenu.PopupMenu(this.actor, 0.0, St.Side.TOP, 0);
|
||||||
|
menu.addAction(_("Quit"), Lang.bind(this, function() {
|
||||||
|
this._targetApp.request_quit();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setMenu(menu);
|
||||||
|
this._menuManager.addMenu(menu);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -924,9 +943,8 @@ const Panel = new Lang.Class({
|
|||||||
// more cleanly with the rest of the panel
|
// more cleanly with the rest of the panel
|
||||||
this._menus.addMenu(this._activitiesButton.menu);
|
this._menus.addMenu(this._activitiesButton.menu);
|
||||||
|
|
||||||
this._appMenu = new AppMenuButton();
|
this._appMenu = new AppMenuButton(this._menus);
|
||||||
this._leftBox.add(this._appMenu.actor);
|
this._leftBox.add(this._appMenu.actor);
|
||||||
this._menus.addMenu(this._appMenu.menu);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* center */
|
/* center */
|
||||||
|
@ -96,22 +96,39 @@ const Button = new Lang.Class({
|
|||||||
Name: 'PanelMenuButton',
|
Name: 'PanelMenuButton',
|
||||||
Extends: ButtonBox,
|
Extends: ButtonBox,
|
||||||
|
|
||||||
_init: function(menuAlignment) {
|
_init: function(menuAlignment, dontCreateMenu) {
|
||||||
this.parent({ reactive: true,
|
this.parent({ reactive: true,
|
||||||
can_focus: true,
|
can_focus: true,
|
||||||
track_hover: true });
|
track_hover: true });
|
||||||
|
|
||||||
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
|
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
|
||||||
this.actor.connect('key-press-event', Lang.bind(this, this._onSourceKeyPress));
|
this.actor.connect('key-press-event', Lang.bind(this, this._onSourceKeyPress));
|
||||||
this.menu = new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP);
|
|
||||||
this.menu.actor.add_style_class_name('panel-menu');
|
if (dontCreateMenu)
|
||||||
this.menu.connect('open-state-changed', Lang.bind(this, this._onOpenStateChanged));
|
this.menu = null;
|
||||||
this.menu.actor.connect('key-press-event', Lang.bind(this, this._onMenuKeyPress));
|
else
|
||||||
Main.uiGroup.add_actor(this.menu.actor);
|
this.setMenu(new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP, 0));
|
||||||
this.menu.actor.hide();
|
},
|
||||||
|
|
||||||
|
setMenu: function(menu) {
|
||||||
|
if (this.menu)
|
||||||
|
this.menu.destroy();
|
||||||
|
|
||||||
|
this.menu = menu;
|
||||||
|
if (this.menu) {
|
||||||
|
this.menu.actor.add_style_class_name('panel-menu');
|
||||||
|
this.menu.connect('open-state-changed', Lang.bind(this, this._onOpenStateChanged));
|
||||||
|
this.menu.actor.connect('key-press-event', Lang.bind(this, this._onMenuKeyPress));
|
||||||
|
|
||||||
|
Main.uiGroup.add_actor(this.menu.actor);
|
||||||
|
this.menu.actor.hide();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_onButtonPress: function(actor, event) {
|
_onButtonPress: function(actor, event) {
|
||||||
|
if (!this.menu)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!this.menu.isOpen) {
|
if (!this.menu.isOpen) {
|
||||||
// Setting the max-height won't do any good if the minimum height of the
|
// Setting the max-height won't do any good if the minimum height of the
|
||||||
// menu is higher then the screen; it's useful if part of the menu is
|
// menu is higher then the screen; it's useful if part of the menu is
|
||||||
@ -125,6 +142,9 @@ const Button = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_onSourceKeyPress: function(actor, event) {
|
_onSourceKeyPress: function(actor, event) {
|
||||||
|
if (!this.menu)
|
||||||
|
return false;
|
||||||
|
|
||||||
let symbol = event.get_key_symbol();
|
let symbol = event.get_key_symbol();
|
||||||
if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) {
|
if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) {
|
||||||
this.menu.toggle();
|
this.menu.toggle();
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
const Cairo = imports.cairo;
|
const Cairo = imports.cairo;
|
||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
|
const GLib = imports.gi.GLib;
|
||||||
const Gtk = imports.gi.Gtk;
|
const Gtk = imports.gi.Gtk;
|
||||||
|
const Gio = imports.gi.Gio;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const Shell = imports.gi.Shell;
|
const Shell = imports.gi.Shell;
|
||||||
const Signals = imports.signals;
|
const Signals = imports.signals;
|
||||||
@ -1692,6 +1694,254 @@ const PopupComboBoxMenuItem = new Lang.Class({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RemoteMenu:
|
||||||
|
*
|
||||||
|
* A PopupMenu that tracks a GMenuModel and shows its actions
|
||||||
|
* (exposed by GApplication/GActionGroup)
|
||||||
|
*/
|
||||||
|
const RemoteMenu = new Lang.Class({
|
||||||
|
Name: 'RemoteMenu',
|
||||||
|
Extends: PopupMenu,
|
||||||
|
|
||||||
|
_init: function(sourceActor, model, actionGroup) {
|
||||||
|
this.parent(sourceActor, 0.0, St.Side.TOP);
|
||||||
|
|
||||||
|
this.model = model;
|
||||||
|
this.actionGroup = actionGroup;
|
||||||
|
|
||||||
|
this._actions = { };
|
||||||
|
this._modelChanged(this.model, 0, 0, this.model.get_n_items(), this);
|
||||||
|
|
||||||
|
this._actionStateChangeId = this.actionGroup.connect('action-state-changed', Lang.bind(this, this._actionStateChanged));
|
||||||
|
this._actionEnableChangeId = this.actionGroup.connect('action-enabled-changed', Lang.bind(this, this._actionEnabledChanged));
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
if (this._actionStateChangeId) {
|
||||||
|
this.actionGroup.disconnect(this._actionStateChangeId);
|
||||||
|
this._actionStateChangeId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._actionEnableChangeId) {
|
||||||
|
this.actionGroup.disconnect(this._actionEnableChangeId);
|
||||||
|
this._actionEnableChangeId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.parent();
|
||||||
|
},
|
||||||
|
|
||||||
|
_createMenuItem: function(model, index) {
|
||||||
|
let section_link = model.get_item_link(index, Gio.MENU_LINK_SECTION);
|
||||||
|
if (section_link) {
|
||||||
|
let item = new PopupMenuSection();
|
||||||
|
this._modelChanged(section_link, 0, 0, section_link.get_n_items(), item);
|
||||||
|
return [item, true, ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
// labels are not checked for existance, as they're required for all items
|
||||||
|
let label = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_LABEL, null).deep_unpack();
|
||||||
|
// remove all underscores that are not followed by another underscore
|
||||||
|
label = label.replace(/_([^_])/, '$1');
|
||||||
|
let submenu_link = model.get_item_link(index, Gio.MENU_LINK_SUBMENU);
|
||||||
|
|
||||||
|
if (submenu_link) {
|
||||||
|
let item = new PopupSubMenuMenuItem(label);
|
||||||
|
this._modelChanged(submenu_link, 0, 0, submenu_link.get_n_items(), item.menu);
|
||||||
|
return [item, false, ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
let action_id = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_ACTION, null).deep_unpack();
|
||||||
|
if (!this.actionGroup.has_action(action_id)) {
|
||||||
|
// the action may not be there yet, wait for action-added
|
||||||
|
return [null, false, 'action-added'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._actions[action_id])
|
||||||
|
this._actions[action_id] = { enabled: this.actionGroup.get_action_enabled(action_id),
|
||||||
|
state: this.actionGroup.get_action_state(action_id),
|
||||||
|
items: [ ],
|
||||||
|
};
|
||||||
|
let action = this._actions[action_id];
|
||||||
|
let item, target, destroyId, specificSignalId;
|
||||||
|
|
||||||
|
if (action.state) {
|
||||||
|
// Docs have get_state_hint(), except that the DBus protocol
|
||||||
|
// has no provision for it (so ShellApp does not implement it,
|
||||||
|
// and neither GApplication), and g_action_get_state_hint()
|
||||||
|
// always returns null
|
||||||
|
// Funny :)
|
||||||
|
|
||||||
|
switch (String.fromCharCode(action.state.classify())) {
|
||||||
|
case 'b':
|
||||||
|
item = new PopupSwitchMenuItem(label, action.state.get_boolean());
|
||||||
|
action.items.push(item);
|
||||||
|
specificSignalId = item.connect('toggled', Lang.bind(this, function(item) {
|
||||||
|
this.actionGroup.change_action_state(action_id, GLib.Variant.new_boolean(item.state));
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
item = new PopupSliderMenuItem(label, action.state.get_double());
|
||||||
|
action.items.push(item);
|
||||||
|
// value-changed is emitted for each motion-event, maybe an idle is more appropriate here?
|
||||||
|
specificSignalId = item.connect('value-changed', Lang.bind(this, function(item) {
|
||||||
|
this.actionGroup.change_action_state(action_id, GLib.Variant.new_double(item.value));
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
item = new PopupMenuItem(label);
|
||||||
|
item._remoteTarget = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null).deep_unpack();
|
||||||
|
action.items.push(item);
|
||||||
|
item.setShowDot(action.state.deep_unpack() == item._remoteTarget);
|
||||||
|
specificSignalId = item.connect('activate', Lang.bind(this, function(item) {
|
||||||
|
this.actionGroup.change_action_state(action_id, GLib.Variant.new_string(item._remoteTarget));
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log('Action "%s" has state of type %s, which is not supported'.format(action_id, action.state.get_type_string()));
|
||||||
|
return [null, false, 'action-state-changed'];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null);
|
||||||
|
item = new PopupMenuItem(label);
|
||||||
|
action.items.push(item);
|
||||||
|
specificSignalId = item.connect('activate', Lang.bind(this, function() {
|
||||||
|
this.actionGroup.activate_action(action_id, target);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
item.actor.reactive = item.actor.can_focus = action.enabled;
|
||||||
|
if (action.enabled)
|
||||||
|
item.actor.remove_style_pseudo_class('insensitive');
|
||||||
|
else
|
||||||
|
item.actor.add_style_pseudo_class('insensitive');
|
||||||
|
|
||||||
|
destroyId = item.connect('destroy', Lang.bind(this, function() {
|
||||||
|
item.disconnect(destroyId);
|
||||||
|
item.disconnect(specificSignalId);
|
||||||
|
|
||||||
|
let pos = action.items.indexOf(item);
|
||||||
|
if (pos != -1)
|
||||||
|
action.items.splice(pos, 1);
|
||||||
|
}));
|
||||||
|
|
||||||
|
return [item, false, ''];
|
||||||
|
},
|
||||||
|
|
||||||
|
_modelChanged: function(model, position, removed, added, target) {
|
||||||
|
let j, k;
|
||||||
|
let j0, k0;
|
||||||
|
|
||||||
|
let currentItems = target._getMenuItems();
|
||||||
|
|
||||||
|
for (j0 = 0, k0 = 0; j0 < position; j0++, k0++) {
|
||||||
|
if (currentItems[k0] instanceof PopupSeparatorMenuItem)
|
||||||
|
k0++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removed == -1) {
|
||||||
|
// special flag to indicate we should destroy everything
|
||||||
|
for (k = k0; k < currentItems.length; k++)
|
||||||
|
currentItems[k].destroy();
|
||||||
|
} else {
|
||||||
|
for (j = j0, k = k0; j < j0 + removed; j++, k++) {
|
||||||
|
currentItems[k].destroy();
|
||||||
|
|
||||||
|
if (currentItems[k] instanceof PopupSeparatorMenuItem)
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (j = j0, k = k0; j < j0 + added; j++, k++) {
|
||||||
|
let [item, addSeparator, changeSignal] = this._createMenuItem(model, j);
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
// separators must be added in the parent to make autohiding work
|
||||||
|
if (addSeparator) {
|
||||||
|
target.addMenuItem(new PopupSeparatorMenuItem(), k+1);
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.addMenuItem(item, k);
|
||||||
|
|
||||||
|
if (addSeparator) {
|
||||||
|
target.addMenuItem(new PopupSeparatorMenuItem(), k+1);
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
} else if (changeSignal) {
|
||||||
|
let signalId = this.actionGroup.connect(changeSignal, Lang.bind(this, function() {
|
||||||
|
this.actionGroup.disconnect(signalId);
|
||||||
|
|
||||||
|
// force a full update
|
||||||
|
this._modelChanged(model, 0, -1, model.get_n_items(), target);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!model._changedId) {
|
||||||
|
model._changedId = model.connect('items-changed', Lang.bind(this, this._modelChanged, target));
|
||||||
|
model._destroyId = target.connect('destroy', function() {
|
||||||
|
if (model._changedId)
|
||||||
|
model.disconnect(model._changedId);
|
||||||
|
if (model._destroyId)
|
||||||
|
target.disconnect(model._destroyId);
|
||||||
|
model._changedId = 0;
|
||||||
|
model._destroyId = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target instanceof PopupMenuSection) {
|
||||||
|
target.actor.visible = target.numMenuItems != 0;
|
||||||
|
} else {
|
||||||
|
let sourceItem = target.sourceActor._delegate;
|
||||||
|
if (sourceItem instanceof PopupSubMenuMenuItem)
|
||||||
|
sourceItem.actor.visible = target.numMenuItems != 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_actionStateChanged: function(actionGroup, action_id) {
|
||||||
|
let action = this._actions[action_id];
|
||||||
|
if (!action)
|
||||||
|
return;
|
||||||
|
|
||||||
|
action.state = actionGroup.get_action_state(action_id);
|
||||||
|
if (action.items.length) {
|
||||||
|
switch (String.fromCharCode(action.state.classify())) {
|
||||||
|
case 'b':
|
||||||
|
for (let i = 0; i < action.items.length; i++)
|
||||||
|
action.items[i].setToggleState(action.state.get_boolean());
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
for (let i = 0; i < action.items.length; i++)
|
||||||
|
action.items[i].setValue(action.state.get_double());
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
for (let i = 0; i < action.items.length; i++)
|
||||||
|
action.items[i].setShowDot(action.items[i]._remoteTarget == action.state.deep_unpack());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_actionEnabledChanged: function(actionGroup, action_id) {
|
||||||
|
let action = this._actions[action_id];
|
||||||
|
if (!action)
|
||||||
|
return;
|
||||||
|
|
||||||
|
action.enabled = actionGroup.get_action_enabled(action_id);
|
||||||
|
if (action.items.length) {
|
||||||
|
for (let i = 0; i < action.items.length; i++) {
|
||||||
|
let item = action.items[i];
|
||||||
|
item.actor.reactive = item.actor.can_focus = action.enabled;
|
||||||
|
|
||||||
|
if (action.enabled)
|
||||||
|
item.actor.remove_style_pseudo_class('insensitive');
|
||||||
|
else
|
||||||
|
item.actor.add_style_pseudo_class('insensitive');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/* Basic implementation of a menu manager.
|
/* Basic implementation of a menu manager.
|
||||||
* Call addMenu to add menus
|
* Call addMenu to add menus
|
||||||
*/
|
*/
|
||||||
|
@ -27,6 +27,9 @@ void _shell_app_do_match (ShellApp *app,
|
|||||||
GSList **prefix_results,
|
GSList **prefix_results,
|
||||||
GSList **substring_results);
|
GSList **substring_results);
|
||||||
|
|
||||||
|
void _shell_app_set_dbus_name (ShellApp *app,
|
||||||
|
const char *name);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
#endif /* __SHELL_APP_PRIVATE_H__ */
|
#endif /* __SHELL_APP_PRIVATE_H__ */
|
||||||
|
252
src/shell-app.c
252
src/shell-app.c
@ -37,6 +37,13 @@ typedef struct {
|
|||||||
|
|
||||||
/* Whether or not we need to resort the windows; this is done on demand */
|
/* Whether or not we need to resort the windows; this is done on demand */
|
||||||
gboolean window_sort_stale : 1;
|
gboolean window_sort_stale : 1;
|
||||||
|
|
||||||
|
/* See GApplication documentation */
|
||||||
|
guint name_watcher_id;
|
||||||
|
gchar *dbus_name;
|
||||||
|
GDBusActionGroup *remote_actions;
|
||||||
|
GMenuProxy *remote_menu;
|
||||||
|
GCancellable *dbus_cancellable;
|
||||||
} ShellAppRunningState;
|
} ShellAppRunningState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,11 +79,13 @@ struct _ShellApp
|
|||||||
char *casefolded_exec;
|
char *casefolded_exec;
|
||||||
};
|
};
|
||||||
|
|
||||||
G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT);
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
PROP_0,
|
PROP_0,
|
||||||
PROP_STATE
|
PROP_STATE,
|
||||||
|
PROP_ID,
|
||||||
|
PROP_DBUS_ID,
|
||||||
|
PROP_ACTION_GROUP,
|
||||||
|
PROP_MENU
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@ -89,6 +98,8 @@ static guint shell_app_signals[LAST_SIGNAL] = { 0 };
|
|||||||
static void create_running_state (ShellApp *app);
|
static void create_running_state (ShellApp *app);
|
||||||
static void unref_running_state (ShellAppRunningState *state);
|
static void unref_running_state (ShellAppRunningState *state);
|
||||||
|
|
||||||
|
G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT)
|
||||||
|
|
||||||
static void
|
static void
|
||||||
shell_app_get_property (GObject *gobject,
|
shell_app_get_property (GObject *gobject,
|
||||||
guint prop_id,
|
guint prop_id,
|
||||||
@ -102,6 +113,20 @@ shell_app_get_property (GObject *gobject,
|
|||||||
case PROP_STATE:
|
case PROP_STATE:
|
||||||
g_value_set_enum (value, app->state);
|
g_value_set_enum (value, app->state);
|
||||||
break;
|
break;
|
||||||
|
case PROP_ID:
|
||||||
|
g_value_set_string (value, shell_app_get_id (app));
|
||||||
|
break;
|
||||||
|
case PROP_DBUS_ID:
|
||||||
|
g_value_set_string (value, shell_app_get_dbus_id (app));
|
||||||
|
break;
|
||||||
|
case PROP_ACTION_GROUP:
|
||||||
|
if (app->running_state)
|
||||||
|
g_value_set_object (value, app->running_state->remote_actions);
|
||||||
|
break;
|
||||||
|
case PROP_MENU:
|
||||||
|
if (app->running_state)
|
||||||
|
g_value_set_object (value, app->running_state->remote_menu);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
@ -151,6 +176,15 @@ window_backed_app_get_icon (ShellApp *app,
|
|||||||
return actor;
|
return actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
shell_app_get_dbus_id (ShellApp *app)
|
||||||
|
{
|
||||||
|
if (app->running_state)
|
||||||
|
return app->running_state->dbus_name;
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* shell_app_create_icon_texture:
|
* shell_app_create_icon_texture:
|
||||||
*
|
*
|
||||||
@ -948,6 +982,142 @@ _shell_app_remove_window (ShellApp *app,
|
|||||||
g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
|
g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_action_group_acquired (GObject *object,
|
||||||
|
GAsyncResult *result,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
ShellApp *self = SHELL_APP (user_data);
|
||||||
|
ShellAppRunningState *state = self->running_state;
|
||||||
|
GError *error = NULL;
|
||||||
|
char *object_path;
|
||||||
|
|
||||||
|
state->remote_actions = g_dbus_action_group_new_finish (result,
|
||||||
|
&error);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
|
||||||
|
!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
|
||||||
|
{
|
||||||
|
g_warning ("Unexpected error while reading application actions: %s", error->message);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_clear_error (&error);
|
||||||
|
g_clear_object (&state->dbus_cancellable);
|
||||||
|
|
||||||
|
if (state->name_watcher_id)
|
||||||
|
{
|
||||||
|
g_bus_unwatch_name (state->name_watcher_id);
|
||||||
|
state->name_watcher_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free (state->dbus_name);
|
||||||
|
state->dbus_name = NULL;
|
||||||
|
|
||||||
|
g_object_unref (self);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
object_path = g_strconcat ("/", state->dbus_name, NULL);
|
||||||
|
g_strdelimit (object_path, ".", '/');
|
||||||
|
|
||||||
|
state->remote_menu = g_menu_proxy_get (g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL),
|
||||||
|
state->dbus_name,
|
||||||
|
object_path);
|
||||||
|
|
||||||
|
g_object_notify (G_OBJECT (self), "dbus-id");
|
||||||
|
g_object_notify (G_OBJECT (self), "action-group");
|
||||||
|
g_object_notify (G_OBJECT (self), "menu");
|
||||||
|
|
||||||
|
g_object_unref (self);
|
||||||
|
g_free (object_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_dbus_name_appeared (GDBusConnection *bus,
|
||||||
|
const gchar *name,
|
||||||
|
const gchar *name_owner,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
ShellApp *self = SHELL_APP (user_data);
|
||||||
|
ShellAppRunningState *state = self->running_state;
|
||||||
|
char *object_path;
|
||||||
|
|
||||||
|
g_assert (state != NULL);
|
||||||
|
|
||||||
|
object_path = g_strconcat ("/", name, NULL);
|
||||||
|
g_strdelimit (object_path, ".", '/');
|
||||||
|
|
||||||
|
if (!state->dbus_cancellable)
|
||||||
|
state->dbus_cancellable = g_cancellable_new ();
|
||||||
|
|
||||||
|
g_dbus_action_group_new (bus,
|
||||||
|
name,
|
||||||
|
object_path,
|
||||||
|
G_DBUS_ACTION_GROUP_FLAGS_NONE,
|
||||||
|
state->dbus_cancellable,
|
||||||
|
on_action_group_acquired,
|
||||||
|
g_object_ref (self));
|
||||||
|
|
||||||
|
g_free (object_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_dbus_name_disappeared (GDBusConnection *bus,
|
||||||
|
const gchar *name,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
ShellApp *self = SHELL_APP (user_data);
|
||||||
|
ShellAppRunningState *state = self->running_state;
|
||||||
|
|
||||||
|
g_assert (state != NULL);
|
||||||
|
|
||||||
|
if (state->dbus_cancellable)
|
||||||
|
{
|
||||||
|
g_cancellable_cancel (state->dbus_cancellable);
|
||||||
|
g_clear_object (&state->dbus_cancellable);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_clear_object (&state->remote_actions);
|
||||||
|
g_clear_object (&state->remote_menu);
|
||||||
|
|
||||||
|
g_free (state->dbus_name);
|
||||||
|
state->dbus_name = NULL;
|
||||||
|
|
||||||
|
g_bus_unwatch_name (state->name_watcher_id);
|
||||||
|
state->name_watcher_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
_shell_app_set_dbus_name (ShellApp *app,
|
||||||
|
const char *bus_name)
|
||||||
|
{
|
||||||
|
g_return_if_fail (app->running_state != NULL);
|
||||||
|
|
||||||
|
if (app->running_state->dbus_name != NULL)
|
||||||
|
{
|
||||||
|
/* already associating with another name
|
||||||
|
(can only happen if you restart the shell in the
|
||||||
|
middle of the session, in which case it will try
|
||||||
|
all names seen on the bus; otherwise, it uses
|
||||||
|
the Hello signal from GApplication and thus knows
|
||||||
|
for sure which name is the right one)
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app->running_state->dbus_name = g_strdup (bus_name);
|
||||||
|
app->running_state->name_watcher_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
|
||||||
|
bus_name,
|
||||||
|
G_BUS_NAME_WATCHER_FLAGS_NONE,
|
||||||
|
on_dbus_name_appeared,
|
||||||
|
on_dbus_name_disappeared,
|
||||||
|
g_object_ref (app),
|
||||||
|
g_object_unref);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* shell_app_get_pids:
|
* shell_app_get_pids:
|
||||||
* @app: a #ShellApp
|
* @app: a #ShellApp
|
||||||
@ -1167,13 +1337,28 @@ unref_running_state (ShellAppRunningState *state)
|
|||||||
{
|
{
|
||||||
MetaScreen *screen;
|
MetaScreen *screen;
|
||||||
|
|
||||||
|
g_assert (state->refcount > 0);
|
||||||
|
|
||||||
state->refcount--;
|
state->refcount--;
|
||||||
if (state->refcount > 0)
|
if (state->refcount > 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
screen = shell_global_get_screen (shell_global_get ());
|
screen = shell_global_get_screen (shell_global_get ());
|
||||||
|
|
||||||
g_signal_handler_disconnect (screen, state->workspace_switch_id);
|
g_signal_handler_disconnect (screen, state->workspace_switch_id);
|
||||||
|
|
||||||
|
if (state->dbus_cancellable)
|
||||||
|
{
|
||||||
|
g_cancellable_cancel (state->dbus_cancellable);
|
||||||
|
g_object_unref (state->dbus_cancellable);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_clear_object (&state->remote_actions);
|
||||||
|
g_clear_object (&state->remote_menu);
|
||||||
|
g_free (state->dbus_name);
|
||||||
|
|
||||||
|
if (state->name_watcher_id)
|
||||||
|
g_bus_unwatch_name (state->name_watcher_id);
|
||||||
|
|
||||||
g_slice_free (ShellAppRunningState, state);
|
g_slice_free (ShellAppRunningState, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1349,6 +1534,9 @@ shell_app_dispose (GObject *object)
|
|||||||
while (app->running_state->windows)
|
while (app->running_state->windows)
|
||||||
_shell_app_remove_window (app, app->running_state->windows->data);
|
_shell_app_remove_window (app, app->running_state->windows->data);
|
||||||
}
|
}
|
||||||
|
/* We should have been transitioned when we removed all of our windows */
|
||||||
|
g_assert (app->state == SHELL_APP_STATE_STOPPED);
|
||||||
|
g_assert (app->running_state == NULL);
|
||||||
|
|
||||||
G_OBJECT_CLASS(shell_app_parent_class)->dispose (object);
|
G_OBJECT_CLASS(shell_app_parent_class)->dispose (object);
|
||||||
}
|
}
|
||||||
@ -1399,4 +1587,60 @@ shell_app_class_init(ShellAppClass *klass)
|
|||||||
SHELL_TYPE_APP_STATE,
|
SHELL_TYPE_APP_STATE,
|
||||||
SHELL_APP_STATE_STOPPED,
|
SHELL_APP_STATE_STOPPED,
|
||||||
G_PARAM_READABLE));
|
G_PARAM_READABLE));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ShellApp:id:
|
||||||
|
*
|
||||||
|
* The id of this application (a desktop filename, or a special string
|
||||||
|
* like window:0xabcd1234)
|
||||||
|
*/
|
||||||
|
g_object_class_install_property (gobject_class,
|
||||||
|
PROP_ID,
|
||||||
|
g_param_spec_string ("id",
|
||||||
|
"Application id",
|
||||||
|
"The desktop file id of this ShellApp",
|
||||||
|
NULL,
|
||||||
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ShellApp:dbus-id:
|
||||||
|
*
|
||||||
|
* The DBus well-known name of the application, if one can be associated
|
||||||
|
* to this ShellApp (it means that the application is using GApplication)
|
||||||
|
*/
|
||||||
|
g_object_class_install_property (gobject_class,
|
||||||
|
PROP_DBUS_ID,
|
||||||
|
g_param_spec_string ("dbus-id",
|
||||||
|
"Application DBus Id",
|
||||||
|
"The DBus well-known name of the application",
|
||||||
|
NULL,
|
||||||
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ShellApp:action-group:
|
||||||
|
*
|
||||||
|
* The #GDBusActionGroup associated with this ShellApp, if any. See the
|
||||||
|
* documentation of #GApplication and #GActionGroup for details.
|
||||||
|
*/
|
||||||
|
g_object_class_install_property (gobject_class,
|
||||||
|
PROP_ACTION_GROUP,
|
||||||
|
g_param_spec_object ("action-group",
|
||||||
|
"Application Action Group",
|
||||||
|
"The action group exported by the remote application",
|
||||||
|
G_TYPE_DBUS_ACTION_GROUP,
|
||||||
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||||
|
/**
|
||||||
|
* ShellApp:menu:
|
||||||
|
*
|
||||||
|
* The #GMenuProxy associated with this ShellApp, if any. See the
|
||||||
|
* documentation of #GMenuModel for details.
|
||||||
|
*/
|
||||||
|
g_object_class_install_property (gobject_class,
|
||||||
|
PROP_MENU,
|
||||||
|
g_param_spec_object ("menu",
|
||||||
|
"Application Menu",
|
||||||
|
"The primary menu exported by the remote application",
|
||||||
|
G_TYPE_MENU_PROXY,
|
||||||
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ G_BEGIN_DECLS
|
|||||||
typedef struct _ShellApp ShellApp;
|
typedef struct _ShellApp ShellApp;
|
||||||
typedef struct _ShellAppClass ShellAppClass;
|
typedef struct _ShellAppClass ShellAppClass;
|
||||||
typedef struct _ShellAppPrivate ShellAppPrivate;
|
typedef struct _ShellAppPrivate ShellAppPrivate;
|
||||||
|
typedef struct _ShellAppAction ShellAppAction;
|
||||||
|
|
||||||
#define SHELL_TYPE_APP (shell_app_get_type ())
|
#define SHELL_TYPE_APP (shell_app_get_type ())
|
||||||
#define SHELL_APP(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_APP, ShellApp))
|
#define SHELL_APP(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_APP, ShellApp))
|
||||||
@ -36,9 +37,12 @@ typedef enum {
|
|||||||
GType shell_app_get_type (void) G_GNUC_CONST;
|
GType shell_app_get_type (void) G_GNUC_CONST;
|
||||||
|
|
||||||
const char *shell_app_get_id (ShellApp *app);
|
const char *shell_app_get_id (ShellApp *app);
|
||||||
|
|
||||||
GMenuTreeEntry *shell_app_get_tree_entry (ShellApp *app);
|
GMenuTreeEntry *shell_app_get_tree_entry (ShellApp *app);
|
||||||
GDesktopAppInfo *shell_app_get_app_info (ShellApp *app);
|
GDesktopAppInfo *shell_app_get_app_info (ShellApp *app);
|
||||||
|
|
||||||
|
const char *shell_app_get_dbus_id (ShellApp *app);
|
||||||
|
|
||||||
ClutterActor *shell_app_create_icon_texture (ShellApp *app, int size);
|
ClutterActor *shell_app_create_icon_texture (ShellApp *app, int size);
|
||||||
ClutterActor *shell_app_get_faded_icon (ShellApp *app, int size);
|
ClutterActor *shell_app_get_faded_icon (ShellApp *app, int size);
|
||||||
const char *shell_app_get_name (ShellApp *app);
|
const char *shell_app_get_name (ShellApp *app);
|
||||||
|
@ -49,6 +49,9 @@ struct _ShellWindowTracker
|
|||||||
/* <MetaWindow * window, ShellApp *app> */
|
/* <MetaWindow * window, ShellApp *app> */
|
||||||
GHashTable *window_to_app;
|
GHashTable *window_to_app;
|
||||||
|
|
||||||
|
/* <int, gchar *> */
|
||||||
|
GHashTable *pid_to_dbus_connection;
|
||||||
|
|
||||||
/* <int, ShellApp *app> */
|
/* <int, ShellApp *app> */
|
||||||
GHashTable *launched_pid_to_app;
|
GHashTable *launched_pid_to_app;
|
||||||
};
|
};
|
||||||
@ -436,6 +439,8 @@ track_window (ShellWindowTracker *self,
|
|||||||
MetaWindow *window)
|
MetaWindow *window)
|
||||||
{
|
{
|
||||||
ShellApp *app;
|
ShellApp *app;
|
||||||
|
GPid pid;
|
||||||
|
gchar *dbus_name;
|
||||||
|
|
||||||
if (!shell_window_tracker_is_window_interesting (window))
|
if (!shell_window_tracker_is_window_interesting (window))
|
||||||
return;
|
return;
|
||||||
@ -451,6 +456,15 @@ track_window (ShellWindowTracker *self,
|
|||||||
|
|
||||||
_shell_app_add_window (app, window);
|
_shell_app_add_window (app, window);
|
||||||
|
|
||||||
|
/* Try to associate this ShellApp with a GApplication id, if one exists */
|
||||||
|
pid = meta_window_get_pid (window);
|
||||||
|
dbus_name = g_hash_table_lookup (self->pid_to_dbus_connection, GINT_TO_POINTER ((int) pid));
|
||||||
|
if (dbus_name != NULL)
|
||||||
|
{
|
||||||
|
_shell_app_set_dbus_name (app, dbus_name);
|
||||||
|
g_hash_table_remove (self->pid_to_dbus_connection, GINT_TO_POINTER ((int) pid));
|
||||||
|
}
|
||||||
|
|
||||||
g_signal_emit (self, signals[TRACKED_WINDOWS_CHANGED], 0);
|
g_signal_emit (self, signals[TRACKED_WINDOWS_CHANGED], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -581,13 +595,161 @@ on_startup_sequence_changed (MetaScreen *screen,
|
|||||||
g_signal_emit (G_OBJECT (self), signals[STARTUP_SEQUENCE_CHANGED], 0, sequence);
|
g_signal_emit (G_OBJECT (self), signals[STARTUP_SEQUENCE_CHANGED], 0, sequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ShellWindowTracker *tracker;
|
||||||
|
gchar *bus_name;
|
||||||
|
} LookupAppDBusData;
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_get_connection_unix_pid_reply (GObject *connection,
|
||||||
|
GAsyncResult *result,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
LookupAppDBusData *data = user_data;
|
||||||
|
GError *error = NULL;
|
||||||
|
GVariant *reply;
|
||||||
|
guint32 pid;
|
||||||
|
ShellApp *app;
|
||||||
|
|
||||||
|
reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (connection), result, &error);
|
||||||
|
if (!reply)
|
||||||
|
{
|
||||||
|
if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER))
|
||||||
|
g_warning ("%s\n", error->message);
|
||||||
|
|
||||||
|
g_clear_error (&error);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (!g_variant_is_of_type (reply, G_VARIANT_TYPE ("(u)")))
|
||||||
|
{
|
||||||
|
g_variant_unref (reply);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
g_variant_get (reply, "(u)", &pid);
|
||||||
|
g_variant_unref (reply);
|
||||||
|
|
||||||
|
app = shell_window_tracker_get_app_from_pid (data->tracker, (int)pid);
|
||||||
|
if (app)
|
||||||
|
_shell_app_set_dbus_name (app, data->bus_name);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_hash_table_insert (data->tracker->pid_to_dbus_connection,
|
||||||
|
GINT_TO_POINTER ((int) pid),
|
||||||
|
data->bus_name);
|
||||||
|
data->bus_name = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
g_object_unref (data->tracker);
|
||||||
|
g_free (data->bus_name);
|
||||||
|
g_slice_free (LookupAppDBusData, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
lookup_application_from_name (ShellWindowTracker *self,
|
||||||
|
GDBusConnection *connection,
|
||||||
|
const gchar *bus_name)
|
||||||
|
{
|
||||||
|
LookupAppDBusData *data;
|
||||||
|
|
||||||
|
data = g_slice_new0 (LookupAppDBusData);
|
||||||
|
data->tracker = g_object_ref (self);
|
||||||
|
data->bus_name = g_strdup (bus_name);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: Add something to GtkApplication so it definitely knows the .desktop file.
|
||||||
|
*/
|
||||||
|
g_dbus_connection_call (connection,
|
||||||
|
"org.freedesktop.DBus",
|
||||||
|
"/org/freedesktop/DBus",
|
||||||
|
"org.freedesktop.DBus",
|
||||||
|
"GetConnectionUnixProcessID",
|
||||||
|
g_variant_new ("(s)", bus_name),
|
||||||
|
NULL, 0, -1, NULL, on_get_connection_unix_pid_reply, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_application_signal (GDBusConnection *connection,
|
||||||
|
const gchar *sender_name,
|
||||||
|
const gchar *object_path,
|
||||||
|
const gchar *interface_name,
|
||||||
|
const gchar *signal_name,
|
||||||
|
GVariant *parameters,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
ShellWindowTracker *tracker = SHELL_WINDOW_TRACKER (user_data);
|
||||||
|
gchar *bus_name = NULL;
|
||||||
|
|
||||||
|
g_variant_get (parameters, "(&s)", &bus_name);
|
||||||
|
lookup_application_from_name (tracker, connection, bus_name);
|
||||||
|
|
||||||
|
g_variant_unref (parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_list_names_end (GObject *object,
|
||||||
|
GAsyncResult *result,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
GDBusConnection *connection = G_DBUS_CONNECTION (object);
|
||||||
|
ShellWindowTracker *self = SHELL_WINDOW_TRACKER (user_data);
|
||||||
|
GError *error = NULL;
|
||||||
|
GVariantIter iter;
|
||||||
|
gchar *bus_name = NULL;
|
||||||
|
|
||||||
|
GVariant *res = g_dbus_connection_call_finish (connection, result, &error);
|
||||||
|
|
||||||
|
if (!res)
|
||||||
|
{
|
||||||
|
g_warning ("ListNames failed: %s", error->message);
|
||||||
|
g_error_free (error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_variant_iter_init (&iter, g_variant_get_child_value (res, 0));
|
||||||
|
while (g_variant_iter_loop (&iter, "s", &bus_name))
|
||||||
|
{
|
||||||
|
if (bus_name[0] == ':')
|
||||||
|
{
|
||||||
|
/* unique name, uninteresting */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
lookup_application_from_name (self, connection, bus_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_variant_unref (res);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
shell_window_tracker_init (ShellWindowTracker *self)
|
shell_window_tracker_init (ShellWindowTracker *self)
|
||||||
{
|
{
|
||||||
MetaScreen *screen;
|
MetaScreen *screen;
|
||||||
|
|
||||||
self->window_to_app = g_hash_table_new_full (g_direct_hash, g_direct_equal,
|
g_dbus_connection_signal_subscribe (g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL),
|
||||||
NULL, (GDestroyNotify) g_object_unref);
|
NULL,
|
||||||
|
"org.gtk.Application",
|
||||||
|
"Hello",
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
G_DBUS_SIGNAL_FLAGS_NONE,
|
||||||
|
on_application_signal,
|
||||||
|
self, NULL);
|
||||||
|
|
||||||
|
g_dbus_connection_call (g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL),
|
||||||
|
"org.freedesktop.DBus",
|
||||||
|
"/org/freedesktop/DBus",
|
||||||
|
"org.freedesktop.DBus",
|
||||||
|
"ListNames",
|
||||||
|
NULL, /* parameters */
|
||||||
|
G_VARIANT_TYPE ("(as)"),
|
||||||
|
G_DBUS_CALL_FLAGS_NONE,
|
||||||
|
-1, /* timeout */
|
||||||
|
NULL, /* cancellable */
|
||||||
|
on_list_names_end, self);
|
||||||
|
|
||||||
|
self->window_to_app = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
|
||||||
|
self->pid_to_dbus_connection = g_hash_table_new_full (NULL, NULL, NULL, g_free);
|
||||||
|
|
||||||
self->launched_pid_to_app = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_object_unref);
|
self->launched_pid_to_app = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_object_unref);
|
||||||
|
|
||||||
@ -607,6 +769,7 @@ shell_window_tracker_finalize (GObject *object)
|
|||||||
|
|
||||||
g_hash_table_destroy (self->window_to_app);
|
g_hash_table_destroy (self->window_to_app);
|
||||||
g_hash_table_destroy (self->launched_pid_to_app);
|
g_hash_table_destroy (self->launched_pid_to_app);
|
||||||
|
g_hash_table_destroy (self->pid_to_dbus_connection);
|
||||||
|
|
||||||
G_OBJECT_CLASS (shell_window_tracker_parent_class)->finalize(object);
|
G_OBJECT_CLASS (shell_window_tracker_parent_class)->finalize(object);
|
||||||
}
|
}
|
||||||
|
99
src/test-gapplication.js
Executable file
99
src/test-gapplication.js
Executable file
@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env gjs
|
||||||
|
|
||||||
|
const Gdk = imports.gi.Gdk;
|
||||||
|
const Gio = imports.gi.Gio;
|
||||||
|
const GLib = imports.gi.GLib;
|
||||||
|
const Gtk = imports.gi.Gtk;
|
||||||
|
|
||||||
|
function do_action(action, parameter) {
|
||||||
|
print ("Action '" + action.name + "' invoked");
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_action_param(action, parameter) {
|
||||||
|
print ("Action '" + action.name + "' invoked with parameter " + parameter.print(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_action_state_change(action) {
|
||||||
|
print ("Action '" + action.name + "' has now state '" + action.state.deep_unpack() + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
Gtk.init(null, null);
|
||||||
|
|
||||||
|
let app = new Gtk.Application({ application_id: 'org.gnome.Shell.GtkApplicationTest' });
|
||||||
|
app.connect('activate', function() {
|
||||||
|
print ("Activated");
|
||||||
|
});
|
||||||
|
|
||||||
|
let group = new Gio.SimpleActionGroup();
|
||||||
|
|
||||||
|
let action = Gio.SimpleAction.new('one', null);
|
||||||
|
action.connect('activate', do_action);
|
||||||
|
group.insert(action);
|
||||||
|
|
||||||
|
let action = Gio.SimpleAction.new('two', null);
|
||||||
|
action.connect('activate', do_action);
|
||||||
|
group.insert(action);
|
||||||
|
|
||||||
|
let action = Gio.SimpleAction.new_stateful('toggle', null, GLib.Variant.new('b', false));
|
||||||
|
action.connect('activate', do_action);
|
||||||
|
action.connect('notify::state', do_action_state_change);
|
||||||
|
group.insert(action);
|
||||||
|
|
||||||
|
let action = Gio.SimpleAction.new('disable', null);
|
||||||
|
action.set_enabled(false);
|
||||||
|
action.connect('activate', do_action);
|
||||||
|
group.insert(action);
|
||||||
|
|
||||||
|
let action = Gio.SimpleAction.new('parameter-int', GLib.VariantType.new('u'));
|
||||||
|
action.connect('activate', do_action_param);
|
||||||
|
group.insert(action);
|
||||||
|
|
||||||
|
let action = Gio.SimpleAction.new('parameter-string', GLib.VariantType.new('s'));
|
||||||
|
action.connect('activate', do_action_param);
|
||||||
|
group.insert(action);
|
||||||
|
|
||||||
|
app.action_group = group;
|
||||||
|
|
||||||
|
let menu = new Gio.Menu();
|
||||||
|
menu.append('An action', 'one');
|
||||||
|
|
||||||
|
let section = new Gio.Menu();
|
||||||
|
section.append('Another action', 'two');
|
||||||
|
section.append('Same as above', 'two');
|
||||||
|
menu.append_section(null, section);
|
||||||
|
|
||||||
|
// another section, to check separators
|
||||||
|
section = new Gio.Menu();
|
||||||
|
section.append('Checkbox', 'toggle');
|
||||||
|
section.append('Disabled', 'disable');
|
||||||
|
menu.append_section(null, section);
|
||||||
|
|
||||||
|
// empty sections or submenus should be invisible
|
||||||
|
menu.append_section('Empty section', new Gio.Menu());
|
||||||
|
menu.append_submenu('Empty submenu', new Gio.Menu());
|
||||||
|
|
||||||
|
let submenu = new Gio.Menu();
|
||||||
|
submenu.append('Open c:\\', 'parameter-string::c:\\');
|
||||||
|
submenu.append('Open /home', 'parameter-string::/home');
|
||||||
|
menu.append_submenu('Recent files', submenu);
|
||||||
|
|
||||||
|
let item = Gio.MenuItem.new('Say 42', null);
|
||||||
|
item.set_action_and_target_value('parameter-int', GLib.Variant.new('u', 42));
|
||||||
|
menu.append_item(item);
|
||||||
|
|
||||||
|
let item = Gio.MenuItem.new('Say 43', null);
|
||||||
|
item.set_action_and_target_value('parameter-int', GLib.Variant.new('u', 43));
|
||||||
|
menu.append_item(item);
|
||||||
|
|
||||||
|
app.menu = menu;
|
||||||
|
|
||||||
|
app.connect('startup', function(app) {
|
||||||
|
let window = new Gtk.Window({ title: "Test Application", application: app });
|
||||||
|
window.present();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.run(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
Loading…
Reference in New Issue
Block a user