Compare commits
	
		
			11 Commits
		
	
	
		
			3.3.92
			...
			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