diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index bee168738..d76ab5348 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -1495,6 +1495,24 @@ StScrollBar StButton#vhandle:active { background: #2e3436 url(message-tray-background.png); background-repeat: repeat; height: 72px; + + padding-right: 8px; +} + +.system-tray-icons { + border-radius: 8px; + border: 1px solid #4c4c4c; +} + +.system-tray-icon-button { + padding: 8px; + + width: 22px; + height: 22px; +} + +.system-tray-icon-button:hover { + background: 1px solid #4c4c4c; } .message-tray-summary { diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index 5f5c16ab9..41a08e194 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -1070,7 +1070,7 @@ const Source = new Lang.Class({ }, get isClearable() { - return !this.trayIcon && !this.isChat; + return !this.isChat; }, countUpdated: function() { @@ -1631,6 +1631,71 @@ const MessageTrayIndicator = new Lang.Class({ }, }); +const SystemTrayIconButton = new Lang.Class({ + Name: 'SystemTrayIconButton', + + _init: function(trayIcon) { + this._trayIcon = trayIcon; + + this.actor = new St.Button({ style_class: 'system-tray-icon-button', + track_hover: true, + can_focus: true, + reactive: true }); + this.actor.connect('clicked', Lang.bind(this, this._onClicked)); + this.actor.set_child(this._trayIcon); + + this._trayIcon.connect('destroy', Lang.bind(this, function() { + this.actor.set_child(null); + this.actor.destroy(); + })); + }, + + _onClicked: function() { + let event = Clutter.get_current_event(); + + let id = global.stage.connect('deactivate', Lang.bind(this, function() { + global.stage.disconnect(id); + this._trayIcon.click(event); + })); + + this.emit('clicked'); + + Main.overview.hide(); + return true; + }, +}); +Signals.addSignalMethods(SystemTrayIconButton.prototype); + +const SystemTraySection = new Lang.Class({ + Name: 'SystemTraySection', + + _init: function(tray) { + this._tray = tray; + + this.actor = new St.BoxLayout({ style_class: 'system-tray-icons', + y_align: Clutter.ActorAlign.CENTER, + y_expand: true }); + + 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_screen(global.screen, this.actor); + }, + + _onTrayIconAdded: function(manager, trayIcon) { + let button = new SystemTrayIconButton(trayIcon); + button.connect('clicked', Lang.bind(this, function() { + this._tray.close(); + })); + this.actor.add_child(button.actor); + }, + + _onTrayIconRemoved: function(manager, trayIcon) { + trayIcon.destroy(); + }, +}); + const MessageTray = new Lang.Class({ Name: 'MessageTray', @@ -1683,13 +1748,18 @@ const MessageTray = new Lang.Class({ return Clutter.EVENT_PROPAGATE; })); global.focus_manager.add_group(this.actor); - this._summary = new St.BoxLayout({ style_class: 'message-tray-summary', - reactive: true, - x_align: Clutter.ActorAlign.END, - x_expand: true, - y_align: Clutter.ActorAlign.CENTER, - y_expand: true }); - this.actor.add_actor(this._summary); + + this._summaryGroup = new St.BoxLayout({ x_align: Clutter.ActorAlign.END, + x_expand: true, + y_align: Clutter.ActorAlign.CENTER, + y_expand: true }); + this.actor.add_child(this._summaryGroup); + + this._summary = new St.BoxLayout({ style_class: 'message-tray-summary' }); + this._summaryGroup.add_child(this._summary); + + this._systemTray = new SystemTraySection(this); + this._summaryGroup.add_child(this._systemTray.actor); this._summaryMotionId = 0; diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js index 9b2d29c04..ed8006c8a 100644 --- a/js/ui/notificationDaemon.js +++ b/js/ui/notificationDaemon.js @@ -105,16 +105,10 @@ const FdoNotificationDaemon = new Lang.Class({ this._nextNotificationId = 1; - this._trayManager = new Shell.TrayManager(); - this._trayIconAddedId = this._trayManager.connect('tray-icon-added', Lang.bind(this, this._onTrayIconAdded)); - this._trayIconRemovedId = this._trayManager.connect('tray-icon-removed', Lang.bind(this, this._onTrayIconRemoved)); - Shell.WindowTracker.get_default().connect('notify::focus-app', Lang.bind(this, this._onFocusAppChanged)); Main.overview.connect('hidden', Lang.bind(this, this._onFocusAppChanged)); - - this._trayManager.manage_screen(global.screen, Main.messageTray.actor); }, _imageForNotificationData: function(hints) { @@ -155,28 +149,23 @@ const FdoNotificationDaemon = new Lang.Class({ return null; }, - _lookupSource: function(title, pid, trayIcon) { + _lookupSource: function(title, pid) { for (let i = 0; i < this._sources.length; i++) { let source = this._sources[i]; - if (source.pid == pid && - (source.initialTitle == title || source.trayIcon || trayIcon)) + if (source.pid == pid && source.initialTitle == title) return source; } return null; }, // Returns the source associated with ndata.notification if it is set. - // If the existing or requested source is associated with a tray icon - // and passed in pid matches a pid of an existing source, the title - // match is ignored to enable representing a tray icon and notifications - // from the same application with a single source. // // If no existing source is found, a new source is created as long as // pid is provided. // // Either a pid or ndata.notification is needed to retrieve or // create a source. - _getSource: function(title, pid, ndata, sender, trayIcon) { + _getSource: function(title, pid, ndata, sender) { if (!pid && !(ndata && ndata.notification)) return null; @@ -187,13 +176,13 @@ const FdoNotificationDaemon = new Lang.Class({ if (ndata && ndata.notification) return ndata.notification.source; - let source = this._lookupSource(title, pid, trayIcon); + let source = this._lookupSource(title, pid); if (source) { source.setTitle(title); return source; } - let source = new FdoNotificationDaemonSource(title, pid, sender, trayIcon, ndata ? ndata.hints['desktop-entry'] : null); + let source = new FdoNotificationDaemonSource(title, pid, sender, ndata ? ndata.hints['desktop-entry'] : null); this._sources.push(source); source.connect('destroy', Lang.bind(this, function() { @@ -485,26 +474,15 @@ const FdoNotificationDaemon = new Lang.Class({ this._dbusImpl.emit_signal('ActionInvoked', GLib.Variant.new('(us)', [id, action])); }, - - _onTrayIconAdded: function(o, icon) { - let source = this._getSource(icon.title || icon.wm_class || C_("program", "Unknown"), icon.pid, null, null, icon); - }, - - _onTrayIconRemoved: function(o, icon) { - let source = this._lookupSource(null, icon.pid, true); - if (source) - source.destroy(); - } }); const FdoNotificationDaemonSource = new Lang.Class({ Name: 'FdoNotificationDaemonSource', Extends: MessageTray.Source, - _init: function(title, pid, sender, trayIcon, appId) { + _init: function(title, pid, sender, appId) { // Need to set the app before chaining up, so // methods called from the parent constructor can find it - this.trayIcon = trayIcon; this.pid = pid; this.app = this._getApp(appId); @@ -524,12 +502,6 @@ const FdoNotificationDaemonSource = new Lang.Class({ Lang.bind(this, this._onNameVanished)); else this._nameWatcherId = 0; - - if (this.trayIcon) { - // Try again finding the app, using the WM_CLASS from the tray icon - this._setSummaryIcon(this.trayIcon); - this.useNotificationIcon = false; - } }, _createPolicy: function() { @@ -545,17 +517,15 @@ const FdoNotificationDaemonSource = new Lang.Class({ // Destroy the notification source when its sender is removed from DBus. // Only do so if this.app is set to avoid removing "notify-send" sources, senders // of which аre removed from DBus immediately. - // Sender being removed from DBus would normally result in a tray icon being removed, - // so allow the code path that handles the tray icon being removed to handle that case. - if (!this.trayIcon && this.app) + if (this.app) this.destroy(); }, processNotification: function(notification, gicon) { if (gicon) this._gicon = gicon; - if (!this.trayIcon) - this.iconUpdated(); + + this.iconUpdated(); let tracker = Shell.WindowTracker.get_default(); if (this.app && tracker.focus_app == this.app) @@ -564,29 +534,6 @@ const FdoNotificationDaemonSource = new Lang.Class({ this.notify(notification); }, - handleSummaryClick: function(button) { - if (!this.trayIcon) - return false; - - let event = Clutter.get_current_event(); - - // Left clicks are passed through only where there aren't unacknowledged - // notifications, so it possible to open them in summary mode; right - // clicks are always forwarded, as the right click menu is not useful for - // tray icons - if (button == 1 && - this.notifications.length > 0) - return false; - - let id = global.stage.connect('deactivate', Lang.bind(this, function () { - global.stage.disconnect(id); - this.trayIcon.click(event); - })); - - Main.overview.hide(); - return true; - }, - _getApp: function(appId) { let app; @@ -594,16 +541,6 @@ const FdoNotificationDaemonSource = new Lang.Class({ if (app != null) return app; - if (this.trayIcon) { - app = Shell.AppSystem.get_default().lookup_startup_wmclass(this.trayIcon.wm_class); - if (app != null) - return app; - - app = Shell.AppSystem.get_default().lookup_desktop_wmclass(this.trayIcon.wm_class); - if (app != null) - return app; - } - if (appId) { app = Shell.AppSystem.get_default().lookup_app(appId + '.desktop'); if (app != null) @@ -629,8 +566,7 @@ const FdoNotificationDaemonSource = new Lang.Class({ }, _lastNotificationRemoved: function() { - if (!this.trayIcon) - this.destroy(); + this.destroy(); }, openApp: function() { @@ -651,11 +587,7 @@ const FdoNotificationDaemonSource = new Lang.Class({ }, createIcon: function(size) { - if (this.trayIcon) { - return new Clutter.Clone({ width: size, - height: size, - source: this.trayIcon }); - } else if (this.app) { + if (this.app) { return this.app.create_icon_texture(size); } else if (this._gicon) { return new St.Icon({ gicon: this._gicon,