From cbf2cbac610fa771fd7efcc47c96fd1f8280a9fa Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Mon, 9 Aug 2010 12:33:34 -0400 Subject: [PATCH] [StatusIconDispatcher] Move non-system-tray icons to message tray New StatusIconDispatcher dispatches icons to the system or message tray. Based on a patch from Matt Novenstern https://bugzilla.gnome.org/show_bug.cgi?id=608869 --- js/ui/main.js | 3 ++ js/ui/notificationDaemon.js | 87 +++++++++++++++++++++++++-------- js/ui/panel.js | 92 ++++++++++------------------------- js/ui/statusIconDispatcher.js | 54 ++++++++++++++++++++ src/gnome-shell-plugin.c | 15 ++++++ 5 files changed, 164 insertions(+), 87 deletions(-) create mode 100644 js/ui/statusIconDispatcher.js diff --git a/js/ui/main.js b/js/ui/main.js index a94ef9390..a0287c38e 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -34,6 +34,7 @@ const ShellDBus = imports.ui.shellDBus; const TelepathyClient = imports.ui.telepathyClient; const WindowManager = imports.ui.windowManager; const Magnifier = imports.ui.magnifier; +const StatusIconDispatcher = imports.ui.statusIconDispatcher; const DEFAULT_BACKGROUND_COLOR = new Clutter.Color(); DEFAULT_BACKGROUND_COLOR.from_pixel(0x2266bbff); @@ -55,6 +56,7 @@ let modalCount = 0; let modalActorFocusStack = []; let uiGroup = null; let magnifier = null; +let statusIconDispatcher = null; let _errorLogStack = []; let _startDate; @@ -125,6 +127,7 @@ function start() { overview = new Overview.Overview(); chrome = new Chrome.Chrome(); magnifier = new Magnifier.Magnifier(); + statusIconDispatcher = new StatusIconDispatcher.StatusIconDispatcher(); panel = new Panel.Panel(); wm = new WindowManager.WindowManager(); messageTray = new MessageTray.MessageTray(); diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js index 2165aea8f..899de970c 100644 --- a/js/ui/notificationDaemon.js +++ b/js/ui/notificationDaemon.js @@ -104,6 +104,9 @@ NotificationDaemon.prototype = { this._notifications = {}; this._busProxy = new Bus(); + Main.statusIconDispatcher.connect('message-icon-added', Lang.bind(this, this._onTrayIconAdded)); + Main.statusIconDispatcher.connect('message-icon-removed', Lang.bind(this, this._onTrayIconRemoved)); + Shell.WindowTracker.get_default().connect('notify::focus-app', Lang.bind(this, this._onFocusAppChanged)); Main.overview.connect('hidden', @@ -166,6 +169,23 @@ NotificationDaemon.prototype = { } }, + _newSource: function(title, pid) { + let source = new Source(title, pid); + this._sources[pid] = source; + + source.connect('clicked', Lang.bind(this, + function() { + source.destroy(); + })); + source.connect('destroy', Lang.bind(this, + function() { + delete this._sources[pid]; + })); + + Main.messageTray.add(source); + return source; + }, + Notify: function(appName, replacesId, icon, summary, body, actions, hints, timeout) { let id; @@ -238,21 +258,12 @@ NotificationDaemon.prototype = { this._senderToPid[sender] = pid; source = this._sources[pid]; - if (!source) { - source = new Source(appName, pid); - source.connect('clicked', Lang.bind(this, - function() { - source.destroy(); - })); - source.connect('destroy', Lang.bind(this, - function() { - delete this._sources[pid]; - delete this._senderToPid[sender]; - })); - - this._sources[pid] = source; - Main.messageTray.add(source); - } + if (!source) + source = this._newSource(appName, pid); + source.connect('destroy', Lang.bind(this, + function() { + delete this._senderToPid[sender]; + })); this._notifyForSource(source, ndata); })); @@ -291,7 +302,7 @@ NotificationDaemon.prototype = { notification.setUrgent(hints.urgency == Urgency.CRITICAL); - let sourceIconActor = source.app ? null : this._iconForNotificationData(icon, hints, source.ICON_SIZE); + let sourceIconActor = source.useNotificationIcon ? this._iconForNotificationData(icon, hints, source.ICON_SIZE) : null; source.notify(notification, sourceIconActor); }, @@ -359,6 +370,19 @@ NotificationDaemon.prototype = { 'org.freedesktop.Notifications', 'ActionInvoked', 'us', [id, action]); + }, + + _onTrayIconAdded: function(o, icon) { + let source = this._sources[icon.pid]; + if (!source) + source = this._newSource(icon.title, icon.pid); + source.setTrayIcon(icon); + }, + + _onTrayIconRemoved: function(o, icon) { + let source = this._sources[icon.pid]; + if (source) + source.destroy(); } }; @@ -374,19 +398,40 @@ Source.prototype = { _init: function(title, pid) { MessageTray.Source.prototype._init.call(this, title); - this.app = Shell.WindowTracker.get_default().get_app_from_pid(pid); - if (this.app) { + this._pid = pid; + this._setApp(); + if (this.app) this.title = this.app.get_name(); - this._setSummaryIcon(this.app.create_icon_texture(this.ICON_SIZE)); - } + else + this.useNotificationIcon = true; }, notify: function(notification, icon) { - if (icon) + if (!this.app) + this._setApp(); + if (!this.app && icon) this._setSummaryIcon(icon); MessageTray.Source.prototype.notify.call(this, notification); }, + _setApp: function() { + this.app = Shell.WindowTracker.get_default().get_app_from_pid(this._pid); + if (!this.app) + return; + + // Only override the icon if we were previously using + // notification-based icons (ie, not a trayicon) + if (this.useNotificationIcon) { + this.useNotificationIcon = false; + this._setSummaryIcon(this.app.create_icon_texture (this.ICON_SIZE)); + } + }, + + setTrayIcon: function(icon) { + this._setSummaryIcon(icon); + this.useNotificationIcon = false; + }, + clicked: function() { this.openApp(); MessageTray.Source.prototype.clicked.call(this); diff --git a/js/ui/panel.js b/js/ui/panel.js index 4c3206c9b..057e0e1b9 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -31,12 +31,6 @@ const SPINNER_UPDATE_TIMEOUT = 130; const SPINNER_SPEED = 0.02; const STANDARD_TRAY_ICON_ORDER = ['a11y', 'keyboard', 'volume', 'bluetooth', 'network', 'battery']; -const STANDARD_TRAY_ICON_IMPLEMENTATIONS = { - 'bluetooth-applet': 'bluetooth', - 'gnome-volume-control-applet': 'volume', - 'nm-applet': 'network', - 'gnome-power-manager': 'battery' -}; const STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION = { 'a11y': imports.ui.status.accessibility.ATIndicator }; @@ -799,14 +793,6 @@ Panel.prototype = { /* right */ - // Yet-another-Ubuntu-workaround - we have to kill their - // app-indicators, so that applications fall back to normal - // status icons - // http://bugzilla.gnome.org/show_bug.cgi=id=621382 - let p = new Shell.Process({ args: ['pkill', '-f', - '^([^ ]*/)?indicator-application-service$']}); - p.run(); - // System status applets live in statusBox, while legacy tray icons // live in trayBox // The trayBox is hidden when there are no tray icons. @@ -831,17 +817,8 @@ Panel.prototype = { this._menus.addMenu(indicator.menu); } - this._traymanager = new Shell.TrayManager(); - this._traymanager.connect('tray-icon-added', Lang.bind(this, this._onTrayIconAdded)); - this._traymanager.connect('tray-icon-removed', - Lang.bind(this, function(o, icon) { - trayBox.remove_actor(icon); - - if (trayBox.get_children().length == 0) - trayBox.hide(); - this._recomputeTraySize(); - })); - this._traymanager.manage_stage(global.stage); + Main.statusIconDispatcher.connect('status-icon-added', Lang.bind(this, this._onTrayIconAdded)); + Main.statusIconDispatcher.connect('status-icon-removed', Lang.bind(this, this._onTrayIconRemoved)); this._statusmenu = new StatusMenu.StatusMenuButton(); this._menus.addMenu(this._statusmenu.menu); @@ -885,56 +862,39 @@ Panel.prototype = { }); }, - _onTrayIconAdded: function(o, icon) { - let wmClass = icon.wm_class.toLowerCase(); - + _onTrayIconAdded: function(o, icon, role) { icon.height = PANEL_ICON_SIZE; - let role = STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass]; - if (!role) { - // Unknown icons go first in undefined order + if (STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION[role]) { + // This icon is legacy, and replaced by a Shell version + // Hide it + return; + } + // Figure out the index in our well-known order for this icon + let position = STANDARD_TRAY_ICON_ORDER.indexOf(role); + icon._rolePosition = position; + let children = this._trayBox.get_children(); + let i; + // Walk children backwards, until we find one that isn't + // well-known, or one where we should follow + for (i = children.length - 1; i >= 0; i--) { + let rolePosition = children[i]._rolePosition; + if (!rolePosition || position > rolePosition) { + this._trayBox.insert_actor(icon, i + 1); + break; + } + } + if (i == -1) { + // If we didn't find a position, we must be first this._trayBox.insert_actor(icon, 0); - } else { - if (STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION[role]) { - // This icon is legacy, and replaced by a Shell version - // Hide it - return; - } - icon._role = role; - // Figure out the index in our well-known order for this icon - let position = STANDARD_TRAY_ICON_ORDER.indexOf(role); - icon._rolePosition = position; - let children = this._trayBox.get_children(); - let i; - // Walk children backwards, until we find one that isn't - // well-known, or one where we should follow - for (i = children.length - 1; i >= 0; i--) { - let rolePosition = children[i]._rolePosition; - if (!rolePosition || position > rolePosition) { - this._trayBox.insert_actor(icon, i + 1); - break; - } - } - if (i == -1) { - // If we didn't find a position, we must be first - this._trayBox.insert_actor(icon, 0); - } } // Make sure the trayBox is shown. this._trayBox.show(); - this._recomputeTraySize(); }, - // By default, tray icons have a spacing of TRAY_SPACING. However this - // starts to fail if we have too many as can sadly happen; just jump down - // to a spacing of 8 if we're over 6. - // http://bugzilla.gnome.org/show_bug.cgi?id=590495 - _recomputeTraySize: function () { - if (this._trayBox.get_children().length > 6) - this._trayBox.add_style_pseudo_class('compact'); - else - this._trayBox.remove_style_pseudo_class('compact'); + _onTrayIconRemoved: function(o, icon) { + this._trayBox.remove_actor(icon); }, _addRipple : function(delay, time, startScale, startOpacity, finalScale, finalOpacity) { diff --git a/js/ui/statusIconDispatcher.js b/js/ui/statusIconDispatcher.js new file mode 100644 index 000000000..85d7ffefb --- /dev/null +++ b/js/ui/statusIconDispatcher.js @@ -0,0 +1,54 @@ +/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ + +const Lang = imports.lang; +const Shell = imports.gi.Shell; +const Signals = imports.signals; + +const MessageTray = imports.ui.messageTray; +const NotificationDaemon = imports.ui.notificationDaemon; + +const STANDARD_TRAY_ICON_IMPLEMENTATIONS = { + 'bluetooth-applet': 'bluetooth', + 'gnome-volume-control-applet': 'volume', + 'nm-applet': 'network', + 'gnome-power-manager': 'battery' +}; + +function StatusIconDispatcher() { + this._init(); +} + +StatusIconDispatcher.prototype = { + _init: function() { + this._traymanager = new Shell.TrayManager(); + this._traymanager.connect('tray-icon-added', Lang.bind(this, this._onTrayIconAdded)); + this._traymanager.connect('tray-icon-removed', Lang.bind(this, this._onTrayIconRemoved)); + this._traymanager.manage_stage(global.stage); + + // Yet-another-Ubuntu-workaround - we have to kill their + // app-indicators, so that applications fall back to normal + // status icons + // http://bugzilla.gnome.org/show_bug.cgi=id=621382 + let p = new Shell.Process({ args: ['pkill', '-f', '^([^ ]*/)?indicator-application-service$']}); + p.run(); + }, + + _onTrayIconAdded: function(o, icon) { + let wmClass = icon.wm_class.toLowerCase(); + let role = STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass]; + if (role) + this.emit('status-icon-added', icon, role); + else + this.emit('message-icon-added', icon); + }, + + _onTrayIconRemoved: function(o, icon) { + let wmClass = icon.wm_class.toLowerCase(); + let role = STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass]; + if (role) + this.emit('status-icon-removed', icon); + else + this.emit('message-icon-removed', icon); + } +}; +Signals.addSignalMethods(StatusIconDispatcher.prototype); diff --git a/src/gnome-shell-plugin.c b/src/gnome-shell-plugin.c index 227644f09..91626f9f1 100644 --- a/src/gnome-shell-plugin.c +++ b/src/gnome-shell-plugin.c @@ -507,6 +507,21 @@ gnome_shell_plugin_xevent_filter (MutterPlugin *plugin, } #endif + /* When the pointer leaves the stage to enter a child of the stage + * (like a notification icon), we don't want to produce Clutter leave + * events. But Clutter treats all leave events identically, so we + * need hide the detail = NotifyInferior events from it. + * + * Since Clutter doesn't see any event at all, this does mean that + * it won't produce an enter event on a Clutter actor that surrounds + * the child (unless it gets a MotionNotify before the Enter event). + * Other weirdness is likely also possible. + */ + if ((xev->xany.type == EnterNotify || xev->xany.type == LeaveNotify) + && xev->xcrossing.detail == NotifyInferior + && xev->xcrossing.window == clutter_x11_get_stage_window (CLUTTER_STAGE (clutter_stage_get_default ()))) + return TRUE; + return clutter_x11_handle_event (xev) != CLUTTER_X11_FILTER_CONTINUE; }