[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
This commit is contained in:
Dan Winship 2010-08-09 12:33:34 -04:00
parent 82e6f9c8d6
commit cbf2cbac61
5 changed files with 164 additions and 87 deletions

View File

@ -34,6 +34,7 @@ const ShellDBus = imports.ui.shellDBus;
const TelepathyClient = imports.ui.telepathyClient; const TelepathyClient = imports.ui.telepathyClient;
const WindowManager = imports.ui.windowManager; const WindowManager = imports.ui.windowManager;
const Magnifier = imports.ui.magnifier; const Magnifier = imports.ui.magnifier;
const StatusIconDispatcher = imports.ui.statusIconDispatcher;
const DEFAULT_BACKGROUND_COLOR = new Clutter.Color(); const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
DEFAULT_BACKGROUND_COLOR.from_pixel(0x2266bbff); DEFAULT_BACKGROUND_COLOR.from_pixel(0x2266bbff);
@ -55,6 +56,7 @@ let modalCount = 0;
let modalActorFocusStack = []; let modalActorFocusStack = [];
let uiGroup = null; let uiGroup = null;
let magnifier = null; let magnifier = null;
let statusIconDispatcher = null;
let _errorLogStack = []; let _errorLogStack = [];
let _startDate; let _startDate;
@ -125,6 +127,7 @@ function start() {
overview = new Overview.Overview(); overview = new Overview.Overview();
chrome = new Chrome.Chrome(); chrome = new Chrome.Chrome();
magnifier = new Magnifier.Magnifier(); magnifier = new Magnifier.Magnifier();
statusIconDispatcher = new StatusIconDispatcher.StatusIconDispatcher();
panel = new Panel.Panel(); panel = new Panel.Panel();
wm = new WindowManager.WindowManager(); wm = new WindowManager.WindowManager();
messageTray = new MessageTray.MessageTray(); messageTray = new MessageTray.MessageTray();

View File

@ -104,6 +104,9 @@ NotificationDaemon.prototype = {
this._notifications = {}; this._notifications = {};
this._busProxy = new Bus(); 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', Shell.WindowTracker.get_default().connect('notify::focus-app',
Lang.bind(this, this._onFocusAppChanged)); Lang.bind(this, this._onFocusAppChanged));
Main.overview.connect('hidden', 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, Notify: function(appName, replacesId, icon, summary, body,
actions, hints, timeout) { actions, hints, timeout) {
let id; let id;
@ -238,21 +258,12 @@ NotificationDaemon.prototype = {
this._senderToPid[sender] = pid; this._senderToPid[sender] = pid;
source = this._sources[pid]; source = this._sources[pid];
if (!source) { if (!source)
source = new Source(appName, pid); source = this._newSource(appName, pid);
source.connect('clicked', Lang.bind(this, source.connect('destroy', Lang.bind(this,
function() { function() {
source.destroy(); delete this._senderToPid[sender];
})); }));
source.connect('destroy', Lang.bind(this,
function() {
delete this._sources[pid];
delete this._senderToPid[sender];
}));
this._sources[pid] = source;
Main.messageTray.add(source);
}
this._notifyForSource(source, ndata); this._notifyForSource(source, ndata);
})); }));
@ -291,7 +302,7 @@ NotificationDaemon.prototype = {
notification.setUrgent(hints.urgency == Urgency.CRITICAL); 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); source.notify(notification, sourceIconActor);
}, },
@ -359,6 +370,19 @@ NotificationDaemon.prototype = {
'org.freedesktop.Notifications', 'org.freedesktop.Notifications',
'ActionInvoked', 'us', 'ActionInvoked', 'us',
[id, action]); [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) { _init: function(title, pid) {
MessageTray.Source.prototype._init.call(this, title); MessageTray.Source.prototype._init.call(this, title);
this.app = Shell.WindowTracker.get_default().get_app_from_pid(pid); this._pid = pid;
if (this.app) { this._setApp();
if (this.app)
this.title = this.app.get_name(); this.title = this.app.get_name();
this._setSummaryIcon(this.app.create_icon_texture(this.ICON_SIZE)); else
} this.useNotificationIcon = true;
}, },
notify: function(notification, icon) { notify: function(notification, icon) {
if (icon) if (!this.app)
this._setApp();
if (!this.app && icon)
this._setSummaryIcon(icon); this._setSummaryIcon(icon);
MessageTray.Source.prototype.notify.call(this, notification); 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() { clicked: function() {
this.openApp(); this.openApp();
MessageTray.Source.prototype.clicked.call(this); MessageTray.Source.prototype.clicked.call(this);

View File

@ -31,12 +31,6 @@ const SPINNER_UPDATE_TIMEOUT = 130;
const SPINNER_SPEED = 0.02; const SPINNER_SPEED = 0.02;
const STANDARD_TRAY_ICON_ORDER = ['a11y', 'keyboard', 'volume', 'bluetooth', 'network', 'battery']; 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 = { const STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION = {
'a11y': imports.ui.status.accessibility.ATIndicator 'a11y': imports.ui.status.accessibility.ATIndicator
}; };
@ -799,14 +793,6 @@ Panel.prototype = {
/* right */ /* 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 // System status applets live in statusBox, while legacy tray icons
// live in trayBox // live in trayBox
// The trayBox is hidden when there are no tray icons. // The trayBox is hidden when there are no tray icons.
@ -831,17 +817,8 @@ Panel.prototype = {
this._menus.addMenu(indicator.menu); this._menus.addMenu(indicator.menu);
} }
this._traymanager = new Shell.TrayManager(); Main.statusIconDispatcher.connect('status-icon-added', Lang.bind(this, this._onTrayIconAdded));
this._traymanager.connect('tray-icon-added', Lang.bind(this, this._onTrayIconAdded)); Main.statusIconDispatcher.connect('status-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
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);
this._statusmenu = new StatusMenu.StatusMenuButton(); this._statusmenu = new StatusMenu.StatusMenuButton();
this._menus.addMenu(this._statusmenu.menu); this._menus.addMenu(this._statusmenu.menu);
@ -885,56 +862,39 @@ Panel.prototype = {
}); });
}, },
_onTrayIconAdded: function(o, icon) { _onTrayIconAdded: function(o, icon, role) {
let wmClass = icon.wm_class.toLowerCase();
icon.height = PANEL_ICON_SIZE; icon.height = PANEL_ICON_SIZE;
let role = STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass]; if (STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION[role]) {
if (!role) { // This icon is legacy, and replaced by a Shell version
// Unknown icons go first in undefined order // 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); 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. // Make sure the trayBox is shown.
this._trayBox.show(); this._trayBox.show();
this._recomputeTraySize();
}, },
// By default, tray icons have a spacing of TRAY_SPACING. However this _onTrayIconRemoved: function(o, icon) {
// starts to fail if we have too many as can sadly happen; just jump down this._trayBox.remove_actor(icon);
// 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');
}, },
_addRipple : function(delay, time, startScale, startOpacity, finalScale, finalOpacity) { _addRipple : function(delay, time, startScale, startOpacity, finalScale, finalOpacity) {

View File

@ -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);

View File

@ -507,6 +507,21 @@ gnome_shell_plugin_xevent_filter (MutterPlugin *plugin,
} }
#endif #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; return clutter_x11_handle_event (xev) != CLUTTER_X11_FILTER_CONTINUE;
} }