[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:
parent
82e6f9c8d6
commit
cbf2cbac61
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
54
js/ui/statusIconDispatcher.js
Normal file
54
js/ui/statusIconDispatcher.js
Normal 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);
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user