Compare commits
	
		
			2 Commits
		
	
	
		
			cherry-pic
			...
			wip/menus
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					3fd70e37bd | ||
| 
						 | 
					5580cfaf63 | 
@@ -3,6 +3,7 @@
 | 
			
		||||
const Cairo = imports.cairo;
 | 
			
		||||
const Clutter = imports.gi.Clutter;
 | 
			
		||||
const Gio = imports.gi.Gio;
 | 
			
		||||
const GLib = imports.gi.GLib;
 | 
			
		||||
const Lang = imports.lang;
 | 
			
		||||
const Mainloop = imports.mainloop;
 | 
			
		||||
const Pango = imports.gi.Pango;
 | 
			
		||||
@@ -235,10 +236,12 @@ const AppMenuButton = new Lang.Class({
 | 
			
		||||
    Name: 'AppMenuButton',
 | 
			
		||||
    Extends: PanelMenu.Button,
 | 
			
		||||
 | 
			
		||||
    _init: function() {
 | 
			
		||||
        this.parent(0.0);
 | 
			
		||||
    _init: function(menuManager) {
 | 
			
		||||
        this.parent(0.0, true);
 | 
			
		||||
 | 
			
		||||
        this._startingApps = [];
 | 
			
		||||
 | 
			
		||||
        this._menuManager = menuManager;
 | 
			
		||||
        this._targetApp = null;
 | 
			
		||||
 | 
			
		||||
        let bin = new St.Bin({ name: 'appMenu' });
 | 
			
		||||
@@ -264,10 +267,6 @@ const AppMenuButton = new Lang.Class({
 | 
			
		||||
 | 
			
		||||
        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;
 | 
			
		||||
        if (!this._visible)
 | 
			
		||||
            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) {
 | 
			
		||||
        let state = app.state;
 | 
			
		||||
        if (state != Shell.AppState.STARTING) {
 | 
			
		||||
@@ -513,8 +506,10 @@ const AppMenuButton = new Lang.Class({
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (targetApp == this._targetApp) {
 | 
			
		||||
            if (targetApp && targetApp.get_state() != Shell.AppState.STARTING)
 | 
			
		||||
            if (targetApp && targetApp.get_state() != Shell.AppState.STARTING) {
 | 
			
		||||
                this.stopAnimation();
 | 
			
		||||
                this._maybeSetMenu();
 | 
			
		||||
            }
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -528,16 +523,40 @@ const AppMenuButton = new Lang.Class({
 | 
			
		||||
        let icon = targetApp.get_faded_icon(2 * PANEL_ICON_SIZE);
 | 
			
		||||
 | 
			
		||||
        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.show();
 | 
			
		||||
 | 
			
		||||
        if (targetApp.get_state() == Shell.AppState.STARTING)
 | 
			
		||||
            this.startAnimation();
 | 
			
		||||
        else
 | 
			
		||||
            this._maybeSetMenu();
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
            this._menus.addMenu(this._activitiesButton.menu);
 | 
			
		||||
 | 
			
		||||
            this._appMenu = new AppMenuButton();
 | 
			
		||||
            this._appMenu = new AppMenuButton(this._menus);
 | 
			
		||||
            this._leftBox.add(this._appMenu.actor);
 | 
			
		||||
            this._menus.addMenu(this._appMenu.menu);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* center */
 | 
			
		||||
 
 | 
			
		||||
@@ -96,22 +96,39 @@ const Button = new Lang.Class({
 | 
			
		||||
    Name: 'PanelMenuButton',
 | 
			
		||||
    Extends: ButtonBox,
 | 
			
		||||
 | 
			
		||||
    _init: function(menuAlignment) {
 | 
			
		||||
    _init: function(menuAlignment, dontCreateMenu) {
 | 
			
		||||
        this.parent({ reactive: true,
 | 
			
		||||
                      can_focus: true,
 | 
			
		||||
                      track_hover: true });
 | 
			
		||||
 | 
			
		||||
        this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
 | 
			
		||||
        this.actor.connect('key-press-event', Lang.bind(this, this._onSourceKeyPress));
 | 
			
		||||
        this.menu = new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP);
 | 
			
		||||
 | 
			
		||||
        if (dontCreateMenu)
 | 
			
		||||
            this.menu = null;
 | 
			
		||||
        else
 | 
			
		||||
            this.setMenu(new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP, 0));
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
        if (!this.menu)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        if (!this.menu.isOpen) {
 | 
			
		||||
            // 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
 | 
			
		||||
@@ -125,6 +142,9 @@ const Button = new Lang.Class({
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _onSourceKeyPress: function(actor, event) {
 | 
			
		||||
        if (!this.menu)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        let symbol = event.get_key_symbol();
 | 
			
		||||
        if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) {
 | 
			
		||||
            this.menu.toggle();
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,9 @@
 | 
			
		||||
 | 
			
		||||
const Cairo = imports.cairo;
 | 
			
		||||
const Clutter = imports.gi.Clutter;
 | 
			
		||||
const GLib = imports.gi.GLib;
 | 
			
		||||
const Gtk = imports.gi.Gtk;
 | 
			
		||||
const Gio = imports.gi.Gio;
 | 
			
		||||
const Lang = imports.lang;
 | 
			
		||||
const Shell = imports.gi.Shell;
 | 
			
		||||
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.
 | 
			
		||||
 * Call addMenu to add menus
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,9 @@ void _shell_app_do_match (ShellApp         *app,
 | 
			
		||||
                          GSList          **prefix_results,
 | 
			
		||||
                          GSList          **substring_results);
 | 
			
		||||
 | 
			
		||||
void _shell_app_set_dbus_name (ShellApp   *app,
 | 
			
		||||
                               const char *name);
 | 
			
		||||
 | 
			
		||||
G_END_DECLS
 | 
			
		||||
 | 
			
		||||
#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 */
 | 
			
		||||
  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;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -72,11 +79,13 @@ struct _ShellApp
 | 
			
		||||
  char *casefolded_exec;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT);
 | 
			
		||||
 | 
			
		||||
enum {
 | 
			
		||||
  PROP_0,
 | 
			
		||||
  PROP_STATE
 | 
			
		||||
  PROP_STATE,
 | 
			
		||||
  PROP_ID,
 | 
			
		||||
  PROP_DBUS_ID,
 | 
			
		||||
  PROP_ACTION_GROUP,
 | 
			
		||||
  PROP_MENU
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum {
 | 
			
		||||
@@ -89,6 +98,8 @@ static guint shell_app_signals[LAST_SIGNAL] = { 0 };
 | 
			
		||||
static void create_running_state (ShellApp *app);
 | 
			
		||||
static void unref_running_state (ShellAppRunningState *state);
 | 
			
		||||
 | 
			
		||||
G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT)
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
shell_app_get_property (GObject    *gobject,
 | 
			
		||||
                        guint       prop_id,
 | 
			
		||||
@@ -102,6 +113,20 @@ shell_app_get_property (GObject    *gobject,
 | 
			
		||||
    case PROP_STATE:
 | 
			
		||||
      g_value_set_enum (value, app->state);
 | 
			
		||||
      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:
 | 
			
		||||
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
 | 
			
		||||
      break;
 | 
			
		||||
@@ -151,6 +176,15 @@ window_backed_app_get_icon (ShellApp *app,
 | 
			
		||||
  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:
 | 
			
		||||
 *
 | 
			
		||||
@@ -948,6 +982,142 @@ _shell_app_remove_window (ShellApp   *app,
 | 
			
		||||
  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:
 | 
			
		||||
 * @app: a #ShellApp
 | 
			
		||||
@@ -1167,13 +1337,28 @@ unref_running_state (ShellAppRunningState *state)
 | 
			
		||||
{
 | 
			
		||||
  MetaScreen *screen;
 | 
			
		||||
 | 
			
		||||
  g_assert (state->refcount > 0);
 | 
			
		||||
 | 
			
		||||
  state->refcount--;
 | 
			
		||||
  if (state->refcount > 0)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  screen = shell_global_get_screen (shell_global_get ());
 | 
			
		||||
 | 
			
		||||
  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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1349,6 +1534,9 @@ shell_app_dispose (GObject *object)
 | 
			
		||||
      while (app->running_state->windows)
 | 
			
		||||
        _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);
 | 
			
		||||
}
 | 
			
		||||
@@ -1399,4 +1587,60 @@ shell_app_class_init(ShellAppClass *klass)
 | 
			
		||||
                                                      SHELL_TYPE_APP_STATE,
 | 
			
		||||
                                                      SHELL_APP_STATE_STOPPED,
 | 
			
		||||
                                                      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 _ShellAppClass ShellAppClass;
 | 
			
		||||
typedef struct _ShellAppPrivate ShellAppPrivate;
 | 
			
		||||
typedef struct _ShellAppAction ShellAppAction;
 | 
			
		||||
 | 
			
		||||
#define SHELL_TYPE_APP              (shell_app_get_type ())
 | 
			
		||||
#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;
 | 
			
		||||
 | 
			
		||||
const char *shell_app_get_id (ShellApp *app);
 | 
			
		||||
 | 
			
		||||
GMenuTreeEntry *shell_app_get_tree_entry (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_get_faded_icon (ShellApp *app, int size);
 | 
			
		||||
const char *shell_app_get_name (ShellApp *app);
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,9 @@ struct _ShellWindowTracker
 | 
			
		||||
  /* <MetaWindow * window, ShellApp *app> */
 | 
			
		||||
  GHashTable *window_to_app;
 | 
			
		||||
 | 
			
		||||
  /* <int, gchar *> */
 | 
			
		||||
  GHashTable *pid_to_dbus_connection;
 | 
			
		||||
 | 
			
		||||
  /* <int, ShellApp *app> */
 | 
			
		||||
  GHashTable *launched_pid_to_app;
 | 
			
		||||
};
 | 
			
		||||
@@ -436,6 +439,8 @@ track_window (ShellWindowTracker *self,
 | 
			
		||||
              MetaWindow      *window)
 | 
			
		||||
{
 | 
			
		||||
  ShellApp *app;
 | 
			
		||||
  GPid pid;
 | 
			
		||||
  gchar *dbus_name;
 | 
			
		||||
 | 
			
		||||
  if (!shell_window_tracker_is_window_interesting (window))
 | 
			
		||||
    return;
 | 
			
		||||
@@ -451,6 +456,15 @@ track_window (ShellWindowTracker *self,
 | 
			
		||||
 | 
			
		||||
  _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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -581,13 +595,161 @@ on_startup_sequence_changed (MetaScreen            *screen,
 | 
			
		||||
  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
 | 
			
		||||
shell_window_tracker_init (ShellWindowTracker *self)
 | 
			
		||||
{
 | 
			
		||||
  MetaScreen *screen;
 | 
			
		||||
 | 
			
		||||
  self->window_to_app = g_hash_table_new_full (g_direct_hash, g_direct_equal,
 | 
			
		||||
                                               NULL, (GDestroyNotify) g_object_unref);
 | 
			
		||||
  g_dbus_connection_signal_subscribe (g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL),
 | 
			
		||||
                                      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);
 | 
			
		||||
 | 
			
		||||
@@ -607,6 +769,7 @@ shell_window_tracker_finalize (GObject *object)
 | 
			
		||||
 | 
			
		||||
  g_hash_table_destroy (self->window_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);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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();
 | 
			
		||||
		Reference in New Issue
	
	Block a user