Compare commits
	
		
			11 Commits
		
	
	
		
			3.16.2
			...
			wip/menus-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					94d01bed12 | ||
| 
						 | 
					9daf358122 | ||
| 
						 | 
					b4b3f5a669 | ||
| 
						 | 
					5290a9dd08 | ||
| 
						 | 
					ab4a7c5237 | ||
| 
						 | 
					95e5c5cfb1 | ||
| 
						 | 
					757fb5796e | ||
| 
						 | 
					75e9fa9cfb | ||
| 
						 | 
					8997aa45b1 | ||
| 
						 | 
					f884dbbfb2 | ||
| 
						 | 
					d0c36bb732 | 
@@ -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,11 +236,15 @@ 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;
 | 
			
		||||
        this._appMenuNotifyId = 0;
 | 
			
		||||
        this._actionGroupNotifyId = 0;
 | 
			
		||||
 | 
			
		||||
        let bin = new St.Bin({ name: 'appMenu' });
 | 
			
		||||
        this.actor.add_actor(bin);
 | 
			
		||||
@@ -264,10 +269,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 +447,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 +508,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;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -524,20 +521,53 @@ const AppMenuButton = new Lang.Class({
 | 
			
		||||
        this._iconBox.hide();
 | 
			
		||||
        this._label.setText('');
 | 
			
		||||
 | 
			
		||||
        if (this._appMenuNotifyId)
 | 
			
		||||
            this._targetApp.disconnect(this._appMenuNotifyId);
 | 
			
		||||
        if (this._actionGroupNotifyId)
 | 
			
		||||
            this._targetApp.disconnect(this._actionGroupNotifyId);
 | 
			
		||||
        if (targetApp) {
 | 
			
		||||
            this._appMenuNotifyId = targetApp.connect('notify::menu', Lang.bind(this, this._sync));
 | 
			
		||||
            this._actionGroupNotifyId = targetApp.connect('notify::action-group', Lang.bind(this, this._sync));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this._targetApp = targetApp;
 | 
			
		||||
        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 +954,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);
 | 
			
		||||
        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();
 | 
			
		||||
 | 
			
		||||
        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,246 @@ 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.activate_action(action_id, null);
 | 
			
		||||
                }));
 | 
			
		||||
                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.activate_action(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
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
@@ -121,9 +121,18 @@ shell_public_headers_h =		\
 | 
			
		||||
	shell-wm.h			\
 | 
			
		||||
	shell-xfixes-cursor.h
 | 
			
		||||
 | 
			
		||||
shell_private_sources = \
 | 
			
		||||
	gactionmuxer.h			\
 | 
			
		||||
	gactionmuxer.c			\
 | 
			
		||||
	gactionobservable.h		\
 | 
			
		||||
	gactionobservable.c		\
 | 
			
		||||
	gactionobserver.h		\
 | 
			
		||||
	gactionobserver.c
 | 
			
		||||
 | 
			
		||||
libgnome_shell_la_SOURCES =		\
 | 
			
		||||
	$(shell_built_sources)		\
 | 
			
		||||
	$(shell_public_headers_h)	\
 | 
			
		||||
	$(shell_private_sources)	\
 | 
			
		||||
	shell-app-private.h		\
 | 
			
		||||
	shell-app-system-private.h	\
 | 
			
		||||
	shell-embedded-window-private.h	\
 | 
			
		||||
@@ -157,10 +166,12 @@ libgnome_shell_la_SOURCES =		\
 | 
			
		||||
	shell-util.c			\
 | 
			
		||||
	shell-window-tracker.c		\
 | 
			
		||||
	shell-wm.c			\
 | 
			
		||||
	shell-xfixes-cursor.c
 | 
			
		||||
	shell-xfixes-cursor.c		\
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
libgnome_shell_la_gir_sources = \
 | 
			
		||||
	$(filter-out %-private.h $(shell_recorder_non_gir_sources), $(shell_public_headers_h) $(libgnome_shell_la_SOURCES))
 | 
			
		||||
	$(filter-out %-private.h $(shell_private_sources), $(shell_public_headers_h) $(libgnome_shell_la_SOURCES))
 | 
			
		||||
 | 
			
		||||
gnome_shell_real_SOURCES =		\
 | 
			
		||||
	main.c
 | 
			
		||||
@@ -175,12 +186,16 @@ shell_recorder_sources =        \
 | 
			
		||||
	shell-recorder.h
 | 
			
		||||
 | 
			
		||||
# Custom element is an internal detail
 | 
			
		||||
shell_recorder_non_gir_sources =  \
 | 
			
		||||
	shell-recorder-src.c	  \
 | 
			
		||||
	shell-recorder-src.h
 | 
			
		||||
 | 
			
		||||
if BUILD_RECORDER
 | 
			
		||||
libgnome_shell_la_SOURCES += $(shell_recorder_sources) $(shell_recorder_non_gir_sources)
 | 
			
		||||
libgnome_shell_la_SOURCES += $(shell_recorder_sources)
 | 
			
		||||
 | 
			
		||||
shell_recorder_private_sources =  \
 | 
			
		||||
	shell-recorder-src.c	\
 | 
			
		||||
	shell-recorder-src.h	\
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
shell_private_sources += $(shell_recorder_private_sources)
 | 
			
		||||
 | 
			
		||||
noinst_PROGRAMS += test-recorder
 | 
			
		||||
 | 
			
		||||
@@ -188,7 +203,7 @@ test_recorder_CPPFLAGS = $(TEST_SHELL_RECORDER_CFLAGS)
 | 
			
		||||
test_recorder_LDADD = $(TEST_SHELL_RECORDER_LIBS)
 | 
			
		||||
 | 
			
		||||
test_recorder_SOURCES =     \
 | 
			
		||||
	$(shell_recorder_sources) $(shell_recorder_non_gir_sources) \
 | 
			
		||||
	$(shell_recorder_sources) $(shell_recorder_private_sources) \
 | 
			
		||||
	test-recorder.c
 | 
			
		||||
endif BUILD_RECORDER
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										533
									
								
								src/gactionmuxer.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										533
									
								
								src/gactionmuxer.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,533 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright © 2011 Canonical Limited
 | 
			
		||||
 *
 | 
			
		||||
 * This library is free software; you can redistribute it and/or
 | 
			
		||||
 * modify it under the terms of the GNU Lesser General Public
 | 
			
		||||
 * License as published by the Free Software Foundation; either
 | 
			
		||||
 * version 2 of the licence, or (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This library is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
			
		||||
 * Lesser General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public
 | 
			
		||||
 * License along with this library; if not, write to the
 | 
			
		||||
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 | 
			
		||||
 * Boston, MA 02111-1307, USA.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Ryan Lortie <desrt@desrt.ca>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "config.h"
 | 
			
		||||
 | 
			
		||||
#include "gactionmuxer.h"
 | 
			
		||||
 | 
			
		||||
#include "gactionobservable.h"
 | 
			
		||||
#include "gactionobserver.h"
 | 
			
		||||
 | 
			
		||||
#include <clutter/clutter.h>
 | 
			
		||||
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * SECTION:gactionmuxer
 | 
			
		||||
 * @short_description: Aggregate and monitor several action groups
 | 
			
		||||
 *
 | 
			
		||||
 * #GActionMuxer is a #GActionGroup and #GActionObservable that is
 | 
			
		||||
 * capable of containing other #GActionGroup instances.
 | 
			
		||||
 *
 | 
			
		||||
 * The typical use is aggregating all of the actions applicable to a
 | 
			
		||||
 * particular context into a single action group, with namespacing.
 | 
			
		||||
 *
 | 
			
		||||
 * Consider the case of two action groups -- one containing actions
 | 
			
		||||
 * applicable to an entire application (such as 'quit') and one
 | 
			
		||||
 * containing actions applicable to a particular window in the
 | 
			
		||||
 * application (such as 'fullscreen').
 | 
			
		||||
 *
 | 
			
		||||
 * In this case, each of these action groups could be added to a
 | 
			
		||||
 * #GActionMuxer with the prefixes "app" and "win", respectively.  This
 | 
			
		||||
 * would expose the actions as "app.quit" and "win.fullscreen" on the
 | 
			
		||||
 * #GActionGroup interface presented by the #GActionMuxer.
 | 
			
		||||
 *
 | 
			
		||||
 * Activations and state change requests on the #GActionMuxer are wired
 | 
			
		||||
 * through to the underlying action group in the expected way.
 | 
			
		||||
 *
 | 
			
		||||
 * This class is typically only used at the site of "consumption" of
 | 
			
		||||
 * actions (eg: when displaying a menu that contains many actions on
 | 
			
		||||
 * different objects).
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
static void     g_action_muxer_group_iface_init         (GActionGroupInterface      *iface);
 | 
			
		||||
static void     g_action_muxer_observable_iface_init    (GActionObservableInterface *iface);
 | 
			
		||||
 | 
			
		||||
typedef GObjectClass GActionMuxerClass;
 | 
			
		||||
 | 
			
		||||
struct _GActionMuxer
 | 
			
		||||
{
 | 
			
		||||
  GObject parent_instance;
 | 
			
		||||
 | 
			
		||||
  GHashTable *actions;
 | 
			
		||||
  GHashTable *groups;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
G_DEFINE_TYPE_WITH_CODE (GActionMuxer, g_action_muxer, G_TYPE_OBJECT,
 | 
			
		||||
                         G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, g_action_muxer_group_iface_init)
 | 
			
		||||
                         G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVABLE, g_action_muxer_observable_iface_init))
 | 
			
		||||
 | 
			
		||||
typedef struct
 | 
			
		||||
{
 | 
			
		||||
  GActionMuxer *muxer;
 | 
			
		||||
  GSList       *watchers;
 | 
			
		||||
  gchar        *fullname;
 | 
			
		||||
} Action;
 | 
			
		||||
 | 
			
		||||
typedef struct
 | 
			
		||||
{
 | 
			
		||||
  GActionMuxer *muxer;
 | 
			
		||||
  GActionGroup *group;
 | 
			
		||||
  gchar        *prefix;
 | 
			
		||||
  gulong        handler_ids[4];
 | 
			
		||||
} Group;
 | 
			
		||||
 | 
			
		||||
static gchar **
 | 
			
		||||
g_action_muxer_list_actions (GActionGroup *action_group)
 | 
			
		||||
{
 | 
			
		||||
  GActionMuxer *muxer = G_ACTION_MUXER (action_group);
 | 
			
		||||
 | 
			
		||||
  return (gchar **) muxer->groups;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static Group *
 | 
			
		||||
g_action_muxer_find_group (GActionMuxer  *muxer,
 | 
			
		||||
                              const gchar     **name)
 | 
			
		||||
{
 | 
			
		||||
  const gchar *dot;
 | 
			
		||||
  gchar *prefix;
 | 
			
		||||
  Group *group;
 | 
			
		||||
 | 
			
		||||
  dot = strchr (*name, '.');
 | 
			
		||||
 | 
			
		||||
  if (!dot)
 | 
			
		||||
    return NULL;
 | 
			
		||||
 | 
			
		||||
  prefix = g_strndup (*name, dot - *name);
 | 
			
		||||
  group = g_hash_table_lookup (muxer->groups, prefix);
 | 
			
		||||
  g_free (prefix);
 | 
			
		||||
 | 
			
		||||
  *name = dot + 1;
 | 
			
		||||
 | 
			
		||||
  return group;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static Action *
 | 
			
		||||
g_action_muxer_lookup_action (GActionMuxer  *muxer,
 | 
			
		||||
                              const gchar   *prefix,
 | 
			
		||||
                              const gchar   *action_name,
 | 
			
		||||
                              gchar        **fullname)
 | 
			
		||||
{
 | 
			
		||||
  Action *action;
 | 
			
		||||
 | 
			
		||||
  *fullname = g_strconcat (prefix, ".", action_name, NULL);
 | 
			
		||||
  action = g_hash_table_lookup (muxer->actions, *fullname);
 | 
			
		||||
 | 
			
		||||
  return action;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
g_action_muxer_action_enabled_changed (GActionGroup *action_group,
 | 
			
		||||
                                       const gchar  *action_name,
 | 
			
		||||
                                       gboolean      enabled,
 | 
			
		||||
                                       gpointer      user_data)
 | 
			
		||||
{
 | 
			
		||||
  Group *group = user_data;
 | 
			
		||||
  gchar *fullname;
 | 
			
		||||
  Action *action;
 | 
			
		||||
  GSList *node;
 | 
			
		||||
 | 
			
		||||
  action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
 | 
			
		||||
  for (node = action ? action->watchers : NULL; node; node = node->next)
 | 
			
		||||
    g_action_observer_action_enabled_changed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname, enabled);
 | 
			
		||||
  g_action_group_action_enabled_changed (G_ACTION_GROUP (group->muxer), fullname, enabled);
 | 
			
		||||
  g_free (fullname);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
g_action_muxer_action_state_changed (GActionGroup *action_group,
 | 
			
		||||
                                     const gchar  *action_name,
 | 
			
		||||
                                     GVariant     *state,
 | 
			
		||||
                                     gpointer      user_data)
 | 
			
		||||
{
 | 
			
		||||
  Group *group = user_data;
 | 
			
		||||
  gchar *fullname;
 | 
			
		||||
  Action *action;
 | 
			
		||||
  GSList *node;
 | 
			
		||||
 | 
			
		||||
  action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
 | 
			
		||||
  for (node = action ? action->watchers : NULL; node; node = node->next)
 | 
			
		||||
    g_action_observer_action_state_changed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname, state);
 | 
			
		||||
  g_action_group_action_state_changed (G_ACTION_GROUP (group->muxer), fullname, state);
 | 
			
		||||
  g_free (fullname);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
g_action_muxer_action_added (GActionGroup *action_group,
 | 
			
		||||
                             const gchar  *action_name,
 | 
			
		||||
                             gpointer      user_data)
 | 
			
		||||
{
 | 
			
		||||
  const GVariantType *parameter_type;
 | 
			
		||||
  Group *group = user_data;
 | 
			
		||||
  gboolean enabled;
 | 
			
		||||
  GVariant *state;
 | 
			
		||||
 | 
			
		||||
  if (g_action_group_query_action (group->group, action_name, &enabled, ¶meter_type, NULL, NULL, &state))
 | 
			
		||||
    {
 | 
			
		||||
      gchar *fullname;
 | 
			
		||||
      Action *action;
 | 
			
		||||
      GSList *node;
 | 
			
		||||
 | 
			
		||||
      action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
 | 
			
		||||
 | 
			
		||||
      for (node = action ? action->watchers : NULL; node; node = node->next)
 | 
			
		||||
        g_action_observer_action_added (node->data,
 | 
			
		||||
                                        G_ACTION_OBSERVABLE (group->muxer),
 | 
			
		||||
                                        fullname, parameter_type, enabled, state);
 | 
			
		||||
 | 
			
		||||
      g_action_group_action_added (G_ACTION_GROUP (group->muxer), fullname);
 | 
			
		||||
 | 
			
		||||
      if (state)
 | 
			
		||||
        g_variant_unref (state);
 | 
			
		||||
 | 
			
		||||
      g_free (fullname);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
g_action_muxer_action_removed (GActionGroup *action_group,
 | 
			
		||||
                               const gchar  *action_name,
 | 
			
		||||
                               gpointer      user_data)
 | 
			
		||||
{
 | 
			
		||||
  Group *group = user_data;
 | 
			
		||||
  gchar *fullname;
 | 
			
		||||
  Action *action;
 | 
			
		||||
  GSList *node;
 | 
			
		||||
 | 
			
		||||
  action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
 | 
			
		||||
  for (node = action ? action->watchers : NULL; node; node = node->next)
 | 
			
		||||
    g_action_observer_action_removed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname);
 | 
			
		||||
  g_action_group_action_removed (G_ACTION_GROUP (group->muxer), fullname);
 | 
			
		||||
  g_free (fullname);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static gboolean
 | 
			
		||||
g_action_muxer_query_action (GActionGroup        *action_group,
 | 
			
		||||
                             const gchar         *action_name,
 | 
			
		||||
                             gboolean            *enabled,
 | 
			
		||||
                             const GVariantType **parameter_type,
 | 
			
		||||
                             const GVariantType **state_type,
 | 
			
		||||
                             GVariant           **state_hint,
 | 
			
		||||
                             GVariant           **state)
 | 
			
		||||
{
 | 
			
		||||
  GActionMuxer *muxer = G_ACTION_MUXER (action_group);
 | 
			
		||||
  Group *group;
 | 
			
		||||
 | 
			
		||||
  group = g_action_muxer_find_group (muxer, &action_name);
 | 
			
		||||
 | 
			
		||||
  if (!group)
 | 
			
		||||
    return FALSE;
 | 
			
		||||
 | 
			
		||||
  return g_action_group_query_action (group->group, action_name, enabled,
 | 
			
		||||
                                      parameter_type, state_type, state_hint, state);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static GVariant *
 | 
			
		||||
get_platform_data (void)
 | 
			
		||||
{
 | 
			
		||||
  gchar time[32];
 | 
			
		||||
  GVariantBuilder *builder;
 | 
			
		||||
  GVariant *result;
 | 
			
		||||
 | 
			
		||||
  g_snprintf (time, 32, "_TIME%d", clutter_get_current_event_time ());
 | 
			
		||||
 | 
			
		||||
  builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
 | 
			
		||||
 | 
			
		||||
  g_variant_builder_add (builder, "{sv}", "desktop-startup-id",
 | 
			
		||||
                         g_variant_new_string (time));
 | 
			
		||||
 | 
			
		||||
  result = g_variant_builder_end (builder);
 | 
			
		||||
  g_variant_builder_unref (builder);
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
g_action_muxer_activate_action (GActionGroup *action_group,
 | 
			
		||||
                                const gchar  *action_name,
 | 
			
		||||
                                GVariant     *parameter)
 | 
			
		||||
{
 | 
			
		||||
  GActionMuxer *muxer = G_ACTION_MUXER (action_group);
 | 
			
		||||
  Group *group;
 | 
			
		||||
 | 
			
		||||
  group = g_action_muxer_find_group (muxer, &action_name);
 | 
			
		||||
 | 
			
		||||
  if (group)
 | 
			
		||||
    {
 | 
			
		||||
      if (G_IS_REMOTE_ACTION_GROUP (group->group))
 | 
			
		||||
        g_remote_action_group_activate_action_full (G_REMOTE_ACTION_GROUP (group->group),
 | 
			
		||||
                                                    action_name,
 | 
			
		||||
                                                    parameter,
 | 
			
		||||
                                                    get_platform_data ());
 | 
			
		||||
      else
 | 
			
		||||
        g_action_group_activate_action (group->group, action_name, parameter);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
g_action_muxer_change_action_state (GActionGroup *action_group,
 | 
			
		||||
                                    const gchar  *action_name,
 | 
			
		||||
                                    GVariant     *state)
 | 
			
		||||
{
 | 
			
		||||
  GActionMuxer *muxer = G_ACTION_MUXER (action_group);
 | 
			
		||||
  Group *group;
 | 
			
		||||
 | 
			
		||||
  group = g_action_muxer_find_group (muxer, &action_name);
 | 
			
		||||
 | 
			
		||||
  if (group)
 | 
			
		||||
    {
 | 
			
		||||
      if (G_IS_REMOTE_ACTION_GROUP (group->group))
 | 
			
		||||
        g_remote_action_group_change_action_state_full (G_REMOTE_ACTION_GROUP (group->group),
 | 
			
		||||
                                                        action_name,
 | 
			
		||||
                                                        state,
 | 
			
		||||
                                                        get_platform_data ());
 | 
			
		||||
      else
 | 
			
		||||
        g_action_group_change_action_state (group->group, action_name, state);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
g_action_muxer_unregister_internal (Action   *action,
 | 
			
		||||
                                    gpointer  observer)
 | 
			
		||||
{
 | 
			
		||||
  GActionMuxer *muxer = action->muxer;
 | 
			
		||||
  GSList **ptr;
 | 
			
		||||
 | 
			
		||||
  for (ptr = &action->watchers; *ptr; ptr = &(*ptr)->next)
 | 
			
		||||
    if ((*ptr)->data == observer)
 | 
			
		||||
      {
 | 
			
		||||
        *ptr = g_slist_remove (*ptr, observer);
 | 
			
		||||
 | 
			
		||||
        if (action->watchers == NULL)
 | 
			
		||||
          {
 | 
			
		||||
            g_hash_table_remove (muxer->actions, action->fullname);
 | 
			
		||||
            g_free (action->fullname);
 | 
			
		||||
 | 
			
		||||
            g_slice_free (Action, action);
 | 
			
		||||
 | 
			
		||||
            g_object_unref (muxer);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
g_action_muxer_weak_notify (gpointer  data,
 | 
			
		||||
                            GObject  *where_the_object_was)
 | 
			
		||||
{
 | 
			
		||||
  Action *action = data;
 | 
			
		||||
 | 
			
		||||
  g_action_muxer_unregister_internal (action, where_the_object_was);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
g_action_muxer_register_observer (GActionObservable *observable,
 | 
			
		||||
                                  const gchar       *name,
 | 
			
		||||
                                  GActionObserver   *observer)
 | 
			
		||||
{
 | 
			
		||||
  GActionMuxer *muxer = G_ACTION_MUXER (observable);
 | 
			
		||||
  Action *action;
 | 
			
		||||
 | 
			
		||||
  action = g_hash_table_lookup (muxer->actions, name);
 | 
			
		||||
 | 
			
		||||
  if (action == NULL)
 | 
			
		||||
    {
 | 
			
		||||
      action = g_slice_new (Action);
 | 
			
		||||
      action->muxer = g_object_ref (muxer);
 | 
			
		||||
      action->fullname = g_strdup (name);
 | 
			
		||||
      action->watchers = NULL;
 | 
			
		||||
 | 
			
		||||
      g_hash_table_insert (muxer->actions, action->fullname, action);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  action->watchers = g_slist_prepend (action->watchers, observer);
 | 
			
		||||
  g_object_weak_ref (G_OBJECT (observer), g_action_muxer_weak_notify, action);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
g_action_muxer_unregister_observer (GActionObservable *observable,
 | 
			
		||||
                                    const gchar       *name,
 | 
			
		||||
                                    GActionObserver   *observer)
 | 
			
		||||
{
 | 
			
		||||
  GActionMuxer *muxer = G_ACTION_MUXER (observable);
 | 
			
		||||
  Action *action;
 | 
			
		||||
 | 
			
		||||
  action = g_hash_table_lookup (muxer->actions, name);
 | 
			
		||||
  g_object_weak_unref (G_OBJECT (observer), g_action_muxer_weak_notify, action);
 | 
			
		||||
  g_action_muxer_unregister_internal (action, observer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
g_action_muxer_free_group (gpointer data)
 | 
			
		||||
{
 | 
			
		||||
  Group *group = data;
 | 
			
		||||
 | 
			
		||||
  g_object_unref (group->group);
 | 
			
		||||
  g_free (group->prefix);
 | 
			
		||||
 | 
			
		||||
  g_slice_free (Group, group);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
g_action_muxer_finalize (GObject *object)
 | 
			
		||||
{
 | 
			
		||||
  GActionMuxer *muxer = G_ACTION_MUXER (object);
 | 
			
		||||
 | 
			
		||||
  g_assert_cmpint (g_hash_table_size (muxer->actions), ==, 0);
 | 
			
		||||
  g_hash_table_unref (muxer->actions);
 | 
			
		||||
  g_hash_table_unref (muxer->groups);
 | 
			
		||||
 | 
			
		||||
  G_OBJECT_CLASS (g_action_muxer_parent_class)
 | 
			
		||||
    ->finalize (object);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
g_action_muxer_init (GActionMuxer *muxer)
 | 
			
		||||
{
 | 
			
		||||
  muxer->actions = g_hash_table_new (g_str_hash, g_str_equal);
 | 
			
		||||
  muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_action_muxer_free_group);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
g_action_muxer_observable_iface_init (GActionObservableInterface *iface)
 | 
			
		||||
{
 | 
			
		||||
  iface->register_observer = g_action_muxer_register_observer;
 | 
			
		||||
  iface->unregister_observer = g_action_muxer_unregister_observer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
g_action_muxer_group_iface_init (GActionGroupInterface *iface)
 | 
			
		||||
{
 | 
			
		||||
  iface->list_actions = g_action_muxer_list_actions;
 | 
			
		||||
  iface->query_action = g_action_muxer_query_action;
 | 
			
		||||
  iface->activate_action = g_action_muxer_activate_action;
 | 
			
		||||
  iface->change_action_state = g_action_muxer_change_action_state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
g_action_muxer_class_init (GObjectClass *class)
 | 
			
		||||
{
 | 
			
		||||
  class->finalize = g_action_muxer_finalize;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * g_action_muxer_insert:
 | 
			
		||||
 * @muxer: a #GActionMuxer
 | 
			
		||||
 * @prefix: the prefix string for the action group
 | 
			
		||||
 * @action_group: a #GActionGroup
 | 
			
		||||
 *
 | 
			
		||||
 * Adds the actions in @action_group to the list of actions provided by
 | 
			
		||||
 * @muxer.  @prefix is prefixed to each action name, such that for each
 | 
			
		||||
 * action <varname>x</varname> in @action_group, there is an equivalent
 | 
			
		||||
 * action @prefix<literal>.</literal><varname>x</varname> in @muxer.
 | 
			
		||||
 *
 | 
			
		||||
 * For example, if @prefix is "<literal>app</literal>" and @action_group
 | 
			
		||||
 * contains an action called "<literal>quit</literal>", then @muxer will
 | 
			
		||||
 * now contain an action called "<literal>app.quit</literal>".
 | 
			
		||||
 *
 | 
			
		||||
 * If any #GActionObservers are registered for actions in the group,
 | 
			
		||||
 * "action_added" notifications will be emitted, as appropriate.
 | 
			
		||||
 *
 | 
			
		||||
 * @prefix must not contain a dot ('.').
 | 
			
		||||
 */
 | 
			
		||||
void
 | 
			
		||||
g_action_muxer_insert (GActionMuxer *muxer,
 | 
			
		||||
                       const gchar  *prefix,
 | 
			
		||||
                       GActionGroup *action_group)
 | 
			
		||||
{
 | 
			
		||||
  gchar **actions;
 | 
			
		||||
  Group *group;
 | 
			
		||||
  gint i;
 | 
			
		||||
 | 
			
		||||
  /* TODO: diff instead of ripout and replace */
 | 
			
		||||
  g_action_muxer_remove (muxer, prefix);
 | 
			
		||||
 | 
			
		||||
  group = g_slice_new (Group);
 | 
			
		||||
  group->muxer = muxer;
 | 
			
		||||
  group->group = g_object_ref (action_group);
 | 
			
		||||
  group->prefix = g_strdup (prefix);
 | 
			
		||||
 | 
			
		||||
  g_hash_table_insert (muxer->groups, group->prefix, group);
 | 
			
		||||
 | 
			
		||||
  actions = g_action_group_list_actions (group->group);
 | 
			
		||||
  for (i = 0; actions[i]; i++)
 | 
			
		||||
    g_action_muxer_action_added (group->group, actions[i], group);
 | 
			
		||||
  g_strfreev (actions);
 | 
			
		||||
 | 
			
		||||
  group->handler_ids[0] = g_signal_connect (group->group, "action-added",
 | 
			
		||||
                                            G_CALLBACK (g_action_muxer_action_added), group);
 | 
			
		||||
  group->handler_ids[1] = g_signal_connect (group->group, "action-removed",
 | 
			
		||||
                                            G_CALLBACK (g_action_muxer_action_removed), group);
 | 
			
		||||
  group->handler_ids[2] = g_signal_connect (group->group, "action-enabled-changed",
 | 
			
		||||
                                            G_CALLBACK (g_action_muxer_action_enabled_changed), group);
 | 
			
		||||
  group->handler_ids[3] = g_signal_connect (group->group, "action-state-changed",
 | 
			
		||||
                                            G_CALLBACK (g_action_muxer_action_state_changed), group);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * g_action_muxer_remove:
 | 
			
		||||
 * @muxer: a #GActionMuxer
 | 
			
		||||
 * @prefix: the prefix of the action group to remove
 | 
			
		||||
 *
 | 
			
		||||
 * Removes a #GActionGroup from the #GActionMuxer.
 | 
			
		||||
 *
 | 
			
		||||
 * If any #GActionObservers are registered for actions in the group,
 | 
			
		||||
 * "action_removed" notifications will be emitted, as appropriate.
 | 
			
		||||
 */
 | 
			
		||||
void
 | 
			
		||||
g_action_muxer_remove (GActionMuxer *muxer,
 | 
			
		||||
                       const gchar  *prefix)
 | 
			
		||||
{
 | 
			
		||||
  Group *group;
 | 
			
		||||
 | 
			
		||||
  group = g_hash_table_lookup (muxer->groups, prefix);
 | 
			
		||||
 | 
			
		||||
  if (group != NULL)
 | 
			
		||||
    {
 | 
			
		||||
      gchar **actions;
 | 
			
		||||
      gint i;
 | 
			
		||||
 | 
			
		||||
      g_hash_table_steal (muxer->groups, prefix);
 | 
			
		||||
 | 
			
		||||
      actions = g_action_group_list_actions (group->group);
 | 
			
		||||
      for (i = 0; actions[i]; i++)
 | 
			
		||||
        g_action_muxer_action_removed (group->group, actions[i], group);
 | 
			
		||||
      g_strfreev (actions);
 | 
			
		||||
 | 
			
		||||
      /* 'for loop' or 'four loop'? */
 | 
			
		||||
      for (i = 0; i < 4; i++)
 | 
			
		||||
        g_signal_handler_disconnect (group->group, group->handler_ids[i]);
 | 
			
		||||
 | 
			
		||||
      g_action_muxer_free_group (group);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * g_action_muxer_new:
 | 
			
		||||
 *
 | 
			
		||||
 * Creates a new #GActionMuxer.
 | 
			
		||||
 */
 | 
			
		||||
GActionMuxer *
 | 
			
		||||
g_action_muxer_new (void)
 | 
			
		||||
{
 | 
			
		||||
  return g_object_new (G_TYPE_ACTION_MUXER, NULL);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								src/gactionmuxer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/gactionmuxer.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright © 2011 Canonical Limited
 | 
			
		||||
 *
 | 
			
		||||
 * This library is free software; you can redistribute it and/or
 | 
			
		||||
 * modify it under the terms of the GNU Lesser General Public
 | 
			
		||||
 * License as published by the Free Software Foundation; either
 | 
			
		||||
 * version 2 of the licence, or (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This library is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
			
		||||
 * Lesser General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public
 | 
			
		||||
 * License along with this library; if not, write to the
 | 
			
		||||
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 | 
			
		||||
 * Boston, MA 02111-1307, USA.
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Ryan Lortie <desrt@desrt.ca>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __G_ACTION_MUXER_H__
 | 
			
		||||
#define __G_ACTION_MUXER_H__
 | 
			
		||||
 | 
			
		||||
#include <gio/gio.h>
 | 
			
		||||
 | 
			
		||||
G_BEGIN_DECLS
 | 
			
		||||
 | 
			
		||||
#define G_TYPE_ACTION_MUXER                                 (g_action_muxer_get_type ())
 | 
			
		||||
#define G_ACTION_MUXER(inst)                                (G_TYPE_CHECK_INSTANCE_CAST ((inst),                     \
 | 
			
		||||
                                                             G_TYPE_ACTION_MUXER, GActionMuxer))
 | 
			
		||||
#define G_IS_ACTION_MUXER(inst)                             (G_TYPE_CHECK_INSTANCE_TYPE ((inst),                     \
 | 
			
		||||
                                                             G_TYPE_ACTION_MUXER))
 | 
			
		||||
 | 
			
		||||
typedef struct _GActionMuxer                                GActionMuxer;
 | 
			
		||||
 | 
			
		||||
G_GNUC_INTERNAL
 | 
			
		||||
GType                   g_action_muxer_get_type                         (void);
 | 
			
		||||
G_GNUC_INTERNAL
 | 
			
		||||
GActionMuxer *          g_action_muxer_new                              (void);
 | 
			
		||||
 | 
			
		||||
G_GNUC_INTERNAL
 | 
			
		||||
void                    g_action_muxer_insert                           (GActionMuxer *muxer,
 | 
			
		||||
                                                                         const gchar  *prefix,
 | 
			
		||||
                                                                         GActionGroup *group);
 | 
			
		||||
 | 
			
		||||
G_GNUC_INTERNAL
 | 
			
		||||
void                    g_action_muxer_remove                           (GActionMuxer *muxer,
 | 
			
		||||
                                                                         const gchar  *prefix);
 | 
			
		||||
 | 
			
		||||
G_END_DECLS
 | 
			
		||||
 | 
			
		||||
#endif /* __G_ACTION_MUXER_H__ */
 | 
			
		||||
							
								
								
									
										80
									
								
								src/gactionobservable.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/gactionobservable.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright © 2011 Canonical Limited
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as
 | 
			
		||||
 * published by the Free Software Foundation; either version 2 of the
 | 
			
		||||
 * licence or (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This library is distributed in the hope that it will be useful, but
 | 
			
		||||
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
			
		||||
 * Lesser General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public
 | 
			
		||||
 * License along with this library; if not, write to the Free Software
 | 
			
		||||
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
 | 
			
		||||
 * USA.
 | 
			
		||||
 *
 | 
			
		||||
 * Authors: Ryan Lortie <desrt@desrt.ca>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "config.h"
 | 
			
		||||
 | 
			
		||||
#include "gactionobservable.h"
 | 
			
		||||
 | 
			
		||||
G_DEFINE_INTERFACE (GActionObservable, g_action_observable, G_TYPE_OBJECT)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * SECTION:gactionobserable
 | 
			
		||||
 * @short_description: an interface implemented by objects that report
 | 
			
		||||
 *                     changes to actions
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
g_action_observable_default_init (GActionObservableInterface *iface)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * g_action_observable_register_observer:
 | 
			
		||||
 * @observable: a #GActionObservable
 | 
			
		||||
 * @action_name: the name of the action
 | 
			
		||||
 * @observer: the #GActionObserver to which the events will be reported
 | 
			
		||||
 *
 | 
			
		||||
 * Registers @observer as being interested in changes to @action_name on
 | 
			
		||||
 * @observable.
 | 
			
		||||
 */
 | 
			
		||||
void
 | 
			
		||||
g_action_observable_register_observer (GActionObservable *observable,
 | 
			
		||||
                                       const gchar       *action_name,
 | 
			
		||||
                                       GActionObserver   *observer)
 | 
			
		||||
{
 | 
			
		||||
  g_return_if_fail (G_IS_ACTION_OBSERVABLE (observable));
 | 
			
		||||
 | 
			
		||||
  G_ACTION_OBSERVABLE_GET_IFACE (observable)
 | 
			
		||||
    ->register_observer (observable, action_name, observer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * g_action_observable_unregister_observer:
 | 
			
		||||
 * @observable: a #GActionObservable
 | 
			
		||||
 * @action_name: the name of the action
 | 
			
		||||
 * @observer: the #GActionObserver to which the events will be reported
 | 
			
		||||
 *
 | 
			
		||||
 * Removes the registration of @observer as being interested in changes
 | 
			
		||||
 * to @action_name on @observable.
 | 
			
		||||
 *
 | 
			
		||||
 * If the observer was registered multiple times, it must be
 | 
			
		||||
 * unregistered an equal number of times.
 | 
			
		||||
 */
 | 
			
		||||
void
 | 
			
		||||
g_action_observable_unregister_observer (GActionObservable *observable,
 | 
			
		||||
                                         const gchar       *action_name,
 | 
			
		||||
                                         GActionObserver   *observer)
 | 
			
		||||
{
 | 
			
		||||
  g_return_if_fail (G_IS_ACTION_OBSERVABLE (observable));
 | 
			
		||||
 | 
			
		||||
  G_ACTION_OBSERVABLE_GET_IFACE (observable)
 | 
			
		||||
    ->unregister_observer (observable, action_name, observer);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								src/gactionobservable.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/gactionobservable.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright © 2011 Canonical Limited
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as
 | 
			
		||||
 * published by the Free Software Foundation; either version 2 of the
 | 
			
		||||
 * licence or (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This library is distributed in the hope that it will be useful, but
 | 
			
		||||
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
			
		||||
 * Lesser General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public
 | 
			
		||||
 * License along with this library; if not, write to the Free Software
 | 
			
		||||
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
 | 
			
		||||
 * USA.
 | 
			
		||||
 *
 | 
			
		||||
 * Authors: Ryan Lortie <desrt@desrt.ca>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __G_ACTION_OBSERVABLE_H__
 | 
			
		||||
#define __G_ACTION_OBSERVABLE_H__
 | 
			
		||||
 | 
			
		||||
#include "gactionobserver.h"
 | 
			
		||||
 | 
			
		||||
G_BEGIN_DECLS
 | 
			
		||||
 | 
			
		||||
#define G_TYPE_ACTION_OBSERVABLE                            (g_action_observable_get_type ())
 | 
			
		||||
#define G_ACTION_OBSERVABLE(inst)                           (G_TYPE_CHECK_INSTANCE_CAST ((inst),                     \
 | 
			
		||||
                                                             G_TYPE_ACTION_OBSERVABLE, GActionObservable))
 | 
			
		||||
#define G_IS_ACTION_OBSERVABLE(inst)                        (G_TYPE_CHECK_INSTANCE_TYPE ((inst),                     \
 | 
			
		||||
                                                             G_TYPE_ACTION_OBSERVABLE))
 | 
			
		||||
#define G_ACTION_OBSERVABLE_GET_IFACE(inst)                 (G_TYPE_INSTANCE_GET_INTERFACE ((inst),                  \
 | 
			
		||||
                                                             G_TYPE_ACTION_OBSERVABLE, GActionObservableInterface))
 | 
			
		||||
 | 
			
		||||
typedef struct _GActionObservableInterface                  GActionObservableInterface;
 | 
			
		||||
 | 
			
		||||
struct _GActionObservableInterface
 | 
			
		||||
{
 | 
			
		||||
  GTypeInterface g_iface;
 | 
			
		||||
 | 
			
		||||
  void (* register_observer)   (GActionObservable *observable,
 | 
			
		||||
                                const gchar       *action_name,
 | 
			
		||||
                                GActionObserver   *observer);
 | 
			
		||||
  void (* unregister_observer) (GActionObservable *observable,
 | 
			
		||||
                                const gchar       *action_name,
 | 
			
		||||
                                GActionObserver   *observer);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
G_GNUC_INTERNAL
 | 
			
		||||
GType                   g_action_observable_get_type                    (void);
 | 
			
		||||
G_GNUC_INTERNAL
 | 
			
		||||
void                    g_action_observable_register_observer           (GActionObservable  *observable,
 | 
			
		||||
                                                                         const gchar        *action_name,
 | 
			
		||||
                                                                         GActionObserver    *observer);
 | 
			
		||||
G_GNUC_INTERNAL
 | 
			
		||||
void                    g_action_observable_unregister_observer         (GActionObservable  *observable,
 | 
			
		||||
                                                                         const gchar        *action_name,
 | 
			
		||||
                                                                         GActionObserver    *observer);
 | 
			
		||||
 | 
			
		||||
G_END_DECLS
 | 
			
		||||
 | 
			
		||||
#endif /* __G_ACTION_OBSERVABLE_H__ */
 | 
			
		||||
							
								
								
									
										161
									
								
								src/gactionobserver.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								src/gactionobserver.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,161 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright © 2011 Canonical Limited
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as
 | 
			
		||||
 * published by the Free Software Foundation; either version 2 of the
 | 
			
		||||
 * licence or (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This library is distributed in the hope that it will be useful, but
 | 
			
		||||
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
			
		||||
 * Lesser General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public
 | 
			
		||||
 * License along with this library; if not, write to the Free Software
 | 
			
		||||
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
 | 
			
		||||
 * USA.
 | 
			
		||||
 *
 | 
			
		||||
 * Authors: Ryan Lortie <desrt@desrt.ca>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "config.h"
 | 
			
		||||
 | 
			
		||||
#include "gactionobserver.h"
 | 
			
		||||
 | 
			
		||||
G_DEFINE_INTERFACE (GActionObserver, g_action_observer, G_TYPE_OBJECT)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * SECTION:gactionobserver
 | 
			
		||||
 * @short_description: an interface implemented by objects that are
 | 
			
		||||
 *                     interested in monitoring actions for changes
 | 
			
		||||
 *
 | 
			
		||||
 * GActionObserver is a simple interface allowing objects that wish to
 | 
			
		||||
 * be notified of changes to actions to be notified of those changes.
 | 
			
		||||
 *
 | 
			
		||||
 * It is also possible to monitor changes to action groups using
 | 
			
		||||
 * #GObject signals, but there are a number of reasons that this
 | 
			
		||||
 * approach could become problematic:
 | 
			
		||||
 *
 | 
			
		||||
 *  - there are four separate signals that must be manually connected
 | 
			
		||||
 *    and disconnected
 | 
			
		||||
 *
 | 
			
		||||
 *  - when a large number of different observers wish to monitor a
 | 
			
		||||
 *    (usually disjoint) set of actions within the same action group,
 | 
			
		||||
 *    there is only one way to avoid having all notifications delivered
 | 
			
		||||
 *    to all observers: signal detail.  In order to use signal detail,
 | 
			
		||||
 *    each action name must be quarked, which is not always practical.
 | 
			
		||||
 *
 | 
			
		||||
 *  - even if quarking is acceptable, #GObject signal details are
 | 
			
		||||
 *    implemented by scanning a linked list, so there is no real
 | 
			
		||||
 *    decrease in complexity
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
g_action_observer_default_init (GActionObserverInterface *class)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * g_action_observer_action_added:
 | 
			
		||||
 * @observer: a #GActionObserver
 | 
			
		||||
 * @observable: the source of the event
 | 
			
		||||
 * @action_name: the name of the action
 | 
			
		||||
 * @enabled: %TRUE if the action is now enabled
 | 
			
		||||
 * @parameter_type: the parameter type for action invocations, or %NULL
 | 
			
		||||
 *                  if no parameter is required
 | 
			
		||||
 * @state: the current state of the action, or %NULL if the action is
 | 
			
		||||
 *         stateless
 | 
			
		||||
 *
 | 
			
		||||
 * This function is called when an action that the observer is
 | 
			
		||||
 * registered to receive events for is added.
 | 
			
		||||
 *
 | 
			
		||||
 * This function should only be called by objects with which the
 | 
			
		||||
 * observer has explicitly registered itself to receive events.
 | 
			
		||||
 */
 | 
			
		||||
void
 | 
			
		||||
g_action_observer_action_added (GActionObserver    *observer,
 | 
			
		||||
                                GActionObservable  *observable,
 | 
			
		||||
                                const gchar        *action_name,
 | 
			
		||||
                                const GVariantType *parameter_type,
 | 
			
		||||
                                gboolean            enabled,
 | 
			
		||||
                                GVariant           *state)
 | 
			
		||||
{
 | 
			
		||||
  g_return_if_fail (G_IS_ACTION_OBSERVER (observer));
 | 
			
		||||
 | 
			
		||||
  G_ACTION_OBSERVER_GET_IFACE (observer)
 | 
			
		||||
    ->action_added (observer, observable, action_name, parameter_type, enabled, state);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * g_action_observer_action_enabled_changed:
 | 
			
		||||
 * @observer: a #GActionObserver
 | 
			
		||||
 * @observable: the source of the event
 | 
			
		||||
 * @action_name: the name of the action
 | 
			
		||||
 * @enabled: %TRUE if the action is now enabled
 | 
			
		||||
 *
 | 
			
		||||
 * This function is called when an action that the observer is
 | 
			
		||||
 * registered to receive events for becomes enabled or disabled.
 | 
			
		||||
 *
 | 
			
		||||
 * This function should only be called by objects with which the
 | 
			
		||||
 * observer has explicitly registered itself to receive events.
 | 
			
		||||
 */
 | 
			
		||||
void
 | 
			
		||||
g_action_observer_action_enabled_changed (GActionObserver   *observer,
 | 
			
		||||
                                          GActionObservable *observable,
 | 
			
		||||
                                          const gchar       *action_name,
 | 
			
		||||
                                          gboolean           enabled)
 | 
			
		||||
{
 | 
			
		||||
  g_return_if_fail (G_IS_ACTION_OBSERVER (observer));
 | 
			
		||||
 | 
			
		||||
  G_ACTION_OBSERVER_GET_IFACE (observer)
 | 
			
		||||
    ->action_enabled_changed (observer, observable, action_name, enabled);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * g_action_observer_action_state_changed:
 | 
			
		||||
 * @observer: a #GActionObserver
 | 
			
		||||
 * @observable: the source of the event
 | 
			
		||||
 * @action_name: the name of the action
 | 
			
		||||
 * @state: the new state of the action
 | 
			
		||||
 *
 | 
			
		||||
 * This function is called when an action that the observer is
 | 
			
		||||
 * registered to receive events for changes its state.
 | 
			
		||||
 *
 | 
			
		||||
 * This function should only be called by objects with which the
 | 
			
		||||
 * observer has explicitly registered itself to receive events.
 | 
			
		||||
 */
 | 
			
		||||
void
 | 
			
		||||
g_action_observer_action_state_changed (GActionObserver   *observer,
 | 
			
		||||
                                        GActionObservable *observable,
 | 
			
		||||
                                        const gchar       *action_name,
 | 
			
		||||
                                        GVariant          *state)
 | 
			
		||||
{
 | 
			
		||||
  g_return_if_fail (G_IS_ACTION_OBSERVER (observer));
 | 
			
		||||
 | 
			
		||||
  G_ACTION_OBSERVER_GET_IFACE (observer)
 | 
			
		||||
    ->action_state_changed (observer, observable, action_name, state);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * g_action_observer_action_removed:
 | 
			
		||||
 * @observer: a #GActionObserver
 | 
			
		||||
 * @observable: the source of the event
 | 
			
		||||
 * @action_name: the name of the action
 | 
			
		||||
 *
 | 
			
		||||
 * This function is called when an action that the observer is
 | 
			
		||||
 * registered to receive events for is removed.
 | 
			
		||||
 *
 | 
			
		||||
 * This function should only be called by objects with which the
 | 
			
		||||
 * observer has explicitly registered itself to receive events.
 | 
			
		||||
 */
 | 
			
		||||
void
 | 
			
		||||
g_action_observer_action_removed (GActionObserver   *observer,
 | 
			
		||||
                                  GActionObservable *observable,
 | 
			
		||||
                                  const gchar       *action_name)
 | 
			
		||||
{
 | 
			
		||||
  g_return_if_fail (G_IS_ACTION_OBSERVER (observer));
 | 
			
		||||
 | 
			
		||||
  G_ACTION_OBSERVER_GET_IFACE (observer)
 | 
			
		||||
    ->action_removed (observer, observable, action_name);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										90
									
								
								src/gactionobserver.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/gactionobserver.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright © 2011 Canonical Limited
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as
 | 
			
		||||
 * published by the Free Software Foundation; either version 2 of the
 | 
			
		||||
 * licence or (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This library is distributed in the hope that it will be useful, but
 | 
			
		||||
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
			
		||||
 * Lesser General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public
 | 
			
		||||
 * License along with this library; if not, write to the Free Software
 | 
			
		||||
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
 | 
			
		||||
 * USA.
 | 
			
		||||
 *
 | 
			
		||||
 * Authors: Ryan Lortie <desrt@desrt.ca>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __G_ACTION_OBSERVER_H__
 | 
			
		||||
#define __G_ACTION_OBSERVER_H__
 | 
			
		||||
 | 
			
		||||
#include <gio/gio.h>
 | 
			
		||||
 | 
			
		||||
G_BEGIN_DECLS
 | 
			
		||||
 | 
			
		||||
#define G_TYPE_ACTION_OBSERVER                              (g_action_observer_get_type ())
 | 
			
		||||
#define G_ACTION_OBSERVER(inst)                             (G_TYPE_CHECK_INSTANCE_CAST ((inst),                     \
 | 
			
		||||
                                                             G_TYPE_ACTION_OBSERVER, GActionObserver))
 | 
			
		||||
#define G_IS_ACTION_OBSERVER(inst)                          (G_TYPE_CHECK_INSTANCE_TYPE ((inst),                     \
 | 
			
		||||
                                                             G_TYPE_ACTION_OBSERVER))
 | 
			
		||||
#define G_ACTION_OBSERVER_GET_IFACE(inst)                   (G_TYPE_INSTANCE_GET_INTERFACE ((inst),                  \
 | 
			
		||||
                                                             G_TYPE_ACTION_OBSERVER, GActionObserverInterface))
 | 
			
		||||
 | 
			
		||||
typedef struct _GActionObserverInterface                    GActionObserverInterface;
 | 
			
		||||
typedef struct _GActionObservable                           GActionObservable;
 | 
			
		||||
typedef struct _GActionObserver                             GActionObserver;
 | 
			
		||||
 | 
			
		||||
struct _GActionObserverInterface
 | 
			
		||||
{
 | 
			
		||||
  GTypeInterface g_iface;
 | 
			
		||||
 | 
			
		||||
  void (* action_added)           (GActionObserver    *observer,
 | 
			
		||||
                                   GActionObservable  *observable,
 | 
			
		||||
                                   const gchar        *action_name,
 | 
			
		||||
                                   const GVariantType *parameter_type,
 | 
			
		||||
                                   gboolean            enabled,
 | 
			
		||||
                                   GVariant           *state);
 | 
			
		||||
  void (* action_enabled_changed) (GActionObserver    *observer,
 | 
			
		||||
                                   GActionObservable  *observable,
 | 
			
		||||
                                   const gchar        *action_name,
 | 
			
		||||
                                   gboolean            enabled);
 | 
			
		||||
  void (* action_state_changed)   (GActionObserver    *observer,
 | 
			
		||||
                                   GActionObservable  *observable,
 | 
			
		||||
                                   const gchar        *action_name,
 | 
			
		||||
                                   GVariant           *state);
 | 
			
		||||
  void (* action_removed)         (GActionObserver    *observer,
 | 
			
		||||
                                   GActionObservable  *observable,
 | 
			
		||||
                                   const gchar        *action_name);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
G_GNUC_INTERNAL
 | 
			
		||||
GType                   g_action_observer_get_type                      (void);
 | 
			
		||||
G_GNUC_INTERNAL
 | 
			
		||||
void                    g_action_observer_action_added                  (GActionObserver    *observer,
 | 
			
		||||
                                                                         GActionObservable  *observable,
 | 
			
		||||
                                                                         const gchar        *action_name,
 | 
			
		||||
                                                                         const GVariantType *parameter_type,
 | 
			
		||||
                                                                         gboolean            enabled,
 | 
			
		||||
                                                                         GVariant           *state);
 | 
			
		||||
G_GNUC_INTERNAL
 | 
			
		||||
void                    g_action_observer_action_enabled_changed        (GActionObserver    *observer,
 | 
			
		||||
                                                                         GActionObservable  *observable,
 | 
			
		||||
                                                                         const gchar        *action_name,
 | 
			
		||||
                                                                         gboolean            enabled);
 | 
			
		||||
G_GNUC_INTERNAL
 | 
			
		||||
void                    g_action_observer_action_state_changed          (GActionObserver    *observer,
 | 
			
		||||
                                                                         GActionObservable  *observable,
 | 
			
		||||
                                                                         const gchar        *action_name,
 | 
			
		||||
                                                                         GVariant           *state);
 | 
			
		||||
G_GNUC_INTERNAL
 | 
			
		||||
void                    g_action_observer_action_removed                (GActionObserver    *observer,
 | 
			
		||||
                                                                         GActionObservable  *observable,
 | 
			
		||||
                                                                         const gchar        *action_name);
 | 
			
		||||
 | 
			
		||||
G_END_DECLS
 | 
			
		||||
 | 
			
		||||
#endif /* __G_ACTION_OBSERVER_H__ */
 | 
			
		||||
							
								
								
									
										313
									
								
								src/shell-app.c
									
									
									
									
									
								
							
							
						
						
									
										313
									
								
								src/shell-app.c
									
									
									
									
									
								
							@@ -15,6 +15,7 @@
 | 
			
		||||
#include "shell-app-system-private.h"
 | 
			
		||||
#include "shell-window-tracker-private.h"
 | 
			
		||||
#include "st.h"
 | 
			
		||||
#include "gactionmuxer.h"
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
  MATCH_NONE,
 | 
			
		||||
@@ -37,6 +38,15 @@ typedef struct {
 | 
			
		||||
 | 
			
		||||
  /* Whether or not we need to resort the windows; this is done on demand */
 | 
			
		||||
  gboolean window_sort_stale : 1;
 | 
			
		||||
 | 
			
		||||
  /* See GApplication documentation */
 | 
			
		||||
  gint              name_watcher_id;
 | 
			
		||||
  gchar            *dbus_name;
 | 
			
		||||
  GDBusProxy       *app_proxy;
 | 
			
		||||
  GActionGroup     *remote_actions;
 | 
			
		||||
  GMenuModel       *remote_menu;
 | 
			
		||||
  GActionMuxer     *muxer;
 | 
			
		||||
  GCancellable     *dbus_cancellable;
 | 
			
		||||
} ShellAppRunningState;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -72,11 +82,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 {
 | 
			
		||||
@@ -88,6 +100,15 @@ static guint shell_app_signals[LAST_SIGNAL] = { 0 };
 | 
			
		||||
 | 
			
		||||
static void create_running_state (ShellApp *app);
 | 
			
		||||
static void unref_running_state (ShellAppRunningState *state);
 | 
			
		||||
static void on_dbus_name_appeared (GDBusConnection *bus,
 | 
			
		||||
                                   const gchar     *name,
 | 
			
		||||
                                   const gchar     *name_owner,
 | 
			
		||||
                                   gpointer         user_data);
 | 
			
		||||
static void on_dbus_name_disappeared (GDBusConnection *bus,
 | 
			
		||||
                                      const gchar     *name,
 | 
			
		||||
                                      gpointer         user_data);
 | 
			
		||||
 | 
			
		||||
G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT)
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
shell_app_get_property (GObject    *gobject,
 | 
			
		||||
@@ -102,6 +123,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->muxer);
 | 
			
		||||
      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 +186,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:
 | 
			
		||||
 *
 | 
			
		||||
@@ -513,6 +557,31 @@ shell_app_activate_window (ShellApp     *app,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
shell_app_update_window_actions (ShellApp *app, MetaWindow *window)
 | 
			
		||||
{
 | 
			
		||||
  const char *object_path;
 | 
			
		||||
 | 
			
		||||
  object_path = meta_window_get_dbus_object_path (window);
 | 
			
		||||
  if (object_path != NULL)
 | 
			
		||||
    {
 | 
			
		||||
      GActionGroup *actions;
 | 
			
		||||
 | 
			
		||||
      actions = g_object_get_data (G_OBJECT (window), "actions");
 | 
			
		||||
      if (actions == NULL)
 | 
			
		||||
        {
 | 
			
		||||
          actions = G_ACTION_GROUP (g_dbus_action_group_get (g_dbus_proxy_get_connection (app->running_state->app_proxy),
 | 
			
		||||
                                                             meta_window_get_dbus_unique_name (window),
 | 
			
		||||
                                                             object_path));
 | 
			
		||||
          g_object_set_data_full (G_OBJECT (window), "actions", actions, g_object_unref);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
      g_action_muxer_insert (app->running_state->muxer, "win", actions);
 | 
			
		||||
      g_object_notify (G_OBJECT (app), "action-group");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * shell_app_activate:
 | 
			
		||||
 * @app: a #ShellApp
 | 
			
		||||
@@ -903,6 +972,34 @@ shell_app_on_ws_switch (MetaScreen         *screen,
 | 
			
		||||
  g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_dbus_application_id_changed (MetaWindow   *window,
 | 
			
		||||
                                GParamSpec   *pspec,
 | 
			
		||||
                                gpointer      user_data)
 | 
			
		||||
{
 | 
			
		||||
  const char *appid;
 | 
			
		||||
  ShellApp *app = SHELL_APP (user_data);
 | 
			
		||||
 | 
			
		||||
  /* Ignore changes in the appid after it's set, shouldn't happen */
 | 
			
		||||
  if (app->running_state->dbus_name != NULL)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  appid = meta_window_get_dbus_application_id (window);
 | 
			
		||||
 | 
			
		||||
  if (!appid)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  g_assert (app->running_state != NULL);
 | 
			
		||||
  app->running_state->dbus_name = g_strdup (appid);
 | 
			
		||||
  app->running_state->name_watcher_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
 | 
			
		||||
                                                          appid,
 | 
			
		||||
                                                          G_BUS_NAME_WATCHER_FLAGS_NONE,
 | 
			
		||||
                                                          on_dbus_name_appeared,
 | 
			
		||||
                                                          on_dbus_name_disappeared,
 | 
			
		||||
                                                          g_object_ref (app),
 | 
			
		||||
                                                          g_object_unref);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
_shell_app_add_window (ShellApp        *app,
 | 
			
		||||
                       MetaWindow      *window)
 | 
			
		||||
@@ -923,6 +1020,9 @@ _shell_app_add_window (ShellApp        *app,
 | 
			
		||||
  if (app->state != SHELL_APP_STATE_STARTING)
 | 
			
		||||
    shell_app_state_transition (app, SHELL_APP_STATE_RUNNING);
 | 
			
		||||
 | 
			
		||||
  g_signal_connect (window, "notify::dbus-application-id", G_CALLBACK(on_dbus_application_id_changed), app);
 | 
			
		||||
  on_dbus_application_id_changed (window, NULL, app);
 | 
			
		||||
 | 
			
		||||
  g_object_thaw_notify (G_OBJECT (app));
 | 
			
		||||
 | 
			
		||||
  g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
 | 
			
		||||
@@ -939,6 +1039,7 @@ _shell_app_remove_window (ShellApp   *app,
 | 
			
		||||
 | 
			
		||||
  g_signal_handlers_disconnect_by_func (window, G_CALLBACK(shell_app_on_unmanaged), app);
 | 
			
		||||
  g_signal_handlers_disconnect_by_func (window, G_CALLBACK(shell_app_on_user_time_changed), app);
 | 
			
		||||
  g_signal_handlers_disconnect_by_func (window, G_CALLBACK(on_dbus_application_id_changed), app);
 | 
			
		||||
  g_object_unref (window);
 | 
			
		||||
  app->running_state->windows = g_slist_remove (app->running_state->windows, window);
 | 
			
		||||
 | 
			
		||||
@@ -948,6 +1049,134 @@ _shell_app_remove_window (ShellApp   *app,
 | 
			
		||||
  g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_dbus_proxy_gotten (GObject      *initable,
 | 
			
		||||
                      GAsyncResult *result,
 | 
			
		||||
                      gpointer      user_data)
 | 
			
		||||
{
 | 
			
		||||
  ShellApp *self = SHELL_APP (user_data);
 | 
			
		||||
  ShellAppRunningState *state = self->running_state;
 | 
			
		||||
  GError *error = NULL;
 | 
			
		||||
  GVariant *menu_property;
 | 
			
		||||
 | 
			
		||||
  state->app_proxy = g_dbus_proxy_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 creating application proxy: %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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  /* on to the second step, the primary action group */
 | 
			
		||||
 | 
			
		||||
  state->remote_actions = (GActionGroup*)g_dbus_action_group_get (
 | 
			
		||||
                           g_dbus_proxy_get_connection (state->app_proxy),
 | 
			
		||||
                           g_dbus_proxy_get_name (state->app_proxy),
 | 
			
		||||
                           g_dbus_proxy_get_object_path (state->app_proxy));
 | 
			
		||||
  state->muxer = g_action_muxer_new ();
 | 
			
		||||
  g_action_muxer_insert (state->muxer, "app", state->remote_actions);
 | 
			
		||||
  g_strfreev (g_action_group_list_actions (state->remote_actions));
 | 
			
		||||
 | 
			
		||||
  g_object_notify (G_OBJECT (self), "action-group");
 | 
			
		||||
 | 
			
		||||
  menu_property = g_dbus_proxy_get_cached_property (state->app_proxy, "AppMenu");
 | 
			
		||||
 | 
			
		||||
  if (menu_property && g_variant_n_children (menu_property) > 0)
 | 
			
		||||
    {
 | 
			
		||||
      const gchar *object_path;
 | 
			
		||||
 | 
			
		||||
      g_variant_get_child (menu_property, 0, "&o", &object_path);
 | 
			
		||||
 | 
			
		||||
      state->remote_menu = G_MENU_MODEL (g_dbus_menu_model_get (g_dbus_proxy_get_connection (state->app_proxy),
 | 
			
		||||
                                                                state->dbus_name,
 | 
			
		||||
                                                                object_path));
 | 
			
		||||
 | 
			
		||||
      g_object_notify (G_OBJECT (self), "menu");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 ();
 | 
			
		||||
 | 
			
		||||
  /* first step: the application proxy */
 | 
			
		||||
 | 
			
		||||
  g_dbus_proxy_new (bus,
 | 
			
		||||
                    G_DBUS_PROXY_FLAGS_NONE,
 | 
			
		||||
                    NULL, /* interface info */
 | 
			
		||||
                    name_owner,
 | 
			
		||||
                    object_path,
 | 
			
		||||
                    "org.gtk.Application",
 | 
			
		||||
                    state->dbus_cancellable,
 | 
			
		||||
                    on_dbus_proxy_gotten,
 | 
			
		||||
                    g_object_ref (self));
 | 
			
		||||
 | 
			
		||||
  g_object_notify (G_OBJECT (self), "dbus-id");
 | 
			
		||||
 | 
			
		||||
  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->app_proxy);
 | 
			
		||||
  g_clear_object (&state->remote_actions);
 | 
			
		||||
  g_clear_object (&state->remote_menu);
 | 
			
		||||
  g_clear_object (&state->muxer);
 | 
			
		||||
 | 
			
		||||
  g_free (state->dbus_name);
 | 
			
		||||
  state->dbus_name = NULL;
 | 
			
		||||
 | 
			
		||||
  g_bus_unwatch_name (state->name_watcher_id);
 | 
			
		||||
  state->name_watcher_id = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * shell_app_get_pids:
 | 
			
		||||
 * @app: a #ShellApp
 | 
			
		||||
@@ -1167,13 +1396,30 @@ 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->app_proxy);
 | 
			
		||||
  g_clear_object (&state->remote_actions);
 | 
			
		||||
  g_clear_object (&state->remote_menu);
 | 
			
		||||
  g_clear_object (&state->muxer);
 | 
			
		||||
  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 +1595,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 +1648,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_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_MODEL,
 | 
			
		||||
                                                        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);
 | 
			
		||||
@@ -79,6 +83,8 @@ int shell_app_compare_by_name (ShellApp *app, ShellApp *other);
 | 
			
		||||
 | 
			
		||||
int shell_app_compare (ShellApp *app, ShellApp *other);
 | 
			
		||||
 | 
			
		||||
void shell_app_update_window_actions (ShellApp *app, MetaWindow *window);
 | 
			
		||||
 | 
			
		||||
G_END_DECLS
 | 
			
		||||
 | 
			
		||||
#endif /* __SHELL_APP_H__ */
 | 
			
		||||
 
 | 
			
		||||
@@ -413,6 +413,9 @@ update_focus_app (ShellWindowTracker *self)
 | 
			
		||||
  new_focus_win = meta_display_get_focus_window (shell_global_get_display (shell_global_get ()));
 | 
			
		||||
  new_focus_app = new_focus_win ? shell_window_tracker_get_window_app (self, new_focus_win) : NULL;
 | 
			
		||||
 | 
			
		||||
  if (new_focus_app)
 | 
			
		||||
    shell_app_update_window_actions (new_focus_app, new_focus_win);
 | 
			
		||||
 | 
			
		||||
  set_focus_app (self, new_focus_app);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										104
									
								
								src/test-gapplication.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										104
									
								
								src/test-gapplication.js
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,104 @@
 | 
			
		||||
#!/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_toggle(action) {
 | 
			
		||||
    action.set_state(GLib.Variant.new('b', !action.state.deep_unpack()));
 | 
			
		||||
    print ("Toggled");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function do_action_state_change(action) {
 | 
			
		||||
    print ("Action '" + action.name + "' has now state " + action.state.print(true));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function main() {
 | 
			
		||||
    Gtk.init(null, null);
 | 
			
		||||
 | 
			
		||||
    let app = new Gtk.Application({ application_id: 'org.gnome.Shell.GtkApplicationTest' });
 | 
			
		||||
    app.connect('activate', function() {
 | 
			
		||||
	print ("Activated");
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let action = new Gio.SimpleAction({ name: 'one' });
 | 
			
		||||
    action.connect('activate', do_action);
 | 
			
		||||
    app.add_action(action);
 | 
			
		||||
 | 
			
		||||
    let action = new Gio.SimpleAction({ name: 'two' });
 | 
			
		||||
    action.connect('activate', do_action);
 | 
			
		||||
    app.add_action(action);
 | 
			
		||||
 | 
			
		||||
    let action = new Gio.SimpleAction({ name: 'toggle', state: GLib.Variant.new('b', false) });
 | 
			
		||||
    action.connect('activate', do_action_toggle);
 | 
			
		||||
    action.connect('notify::state', do_action_state_change);
 | 
			
		||||
    app.add_action(action);
 | 
			
		||||
 | 
			
		||||
    let action = new Gio.SimpleAction({ name: 'disable', enabled: false });
 | 
			
		||||
    action.set_enabled(false);
 | 
			
		||||
    action.connect('activate', do_action);
 | 
			
		||||
    app.add_action(action);
 | 
			
		||||
 | 
			
		||||
    let action = new Gio.SimpleAction({ name: 'parameter-int', parameter_type: GLib.VariantType.new('u') });
 | 
			
		||||
    action.connect('activate', do_action_param);
 | 
			
		||||
    app.add_action(action);
 | 
			
		||||
 | 
			
		||||
    let action = new Gio.SimpleAction({ name: 'parameter-string', parameter_type: GLib.VariantType.new('s') });
 | 
			
		||||
    action.connect('activate', do_action_param);
 | 
			
		||||
    app.add_action(action);
 | 
			
		||||
 | 
			
		||||
    let menu = new Gio.Menu();
 | 
			
		||||
    menu.append('An action', 'app.one');
 | 
			
		||||
 | 
			
		||||
    let section = new Gio.Menu();
 | 
			
		||||
    section.append('Another action', 'app.two');
 | 
			
		||||
    section.append('Same as above', 'app.two');
 | 
			
		||||
    menu.append_section(null, section);
 | 
			
		||||
 | 
			
		||||
    // another section, to check separators
 | 
			
		||||
    section = new Gio.Menu();
 | 
			
		||||
    section.append('Checkbox', 'app.toggle');
 | 
			
		||||
    section.append('Disabled', 'app.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:\\', 'app.parameter-string::c:\\');
 | 
			
		||||
    submenu.append('Open /home', 'app.parameter-string::/home');
 | 
			
		||||
    menu.append_submenu('Recent files', submenu);
 | 
			
		||||
 | 
			
		||||
    let item = Gio.MenuItem.new('Say 42', null);
 | 
			
		||||
    item.set_action_and_target_value('app.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('app.parameter-int', GLib.Variant.new('u', 43));
 | 
			
		||||
    menu.append_item(item);
 | 
			
		||||
 | 
			
		||||
    app.set_app_menu(menu);
 | 
			
		||||
 | 
			
		||||
    let window = null;
 | 
			
		||||
 | 
			
		||||
    app.connect_after('startup', function(app) {
 | 
			
		||||
	window = new Gtk.ApplicationWindow({ title: "Test Application", application: app });
 | 
			
		||||
    });
 | 
			
		||||
    app.connect('activate', function(app) {
 | 
			
		||||
	window.present();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    app.run(null);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
main();
 | 
			
		||||
		Reference in New Issue
	
	Block a user