From 098bd4509ba0de91ada3cfdce29439c9754a2d44 Mon Sep 17 00:00:00 2001 From: Giovanni Campagna Date: Tue, 16 Oct 2012 17:08:54 +0200 Subject: [PATCH] MessageTray: introduce configurable per-source notification policy Allow message tray sources to provide a NotificationPolicy object, that will configure how and if the source is displayed. For notification daemon sources, this object is hooked to GSettings. https://bugzilla.gnome.org/show_bug.cgi?id=685926 --- js/ui/components/autorunManager.js | 5 +- js/ui/messageTray.js | 72 ++++++++++++++-- js/ui/notificationDaemon.js | 129 +++++++++++++++++++++++++++++ js/ui/screenShield.js | 10 +-- 4 files changed, 203 insertions(+), 13 deletions(-) diff --git a/js/ui/components/autorunManager.js b/js/ui/components/autorunManager.js index 7709a3d79..6119c3aba 100644 --- a/js/ui/components/autorunManager.js +++ b/js/ui/components/autorunManager.js @@ -293,7 +293,6 @@ const AutorunResidentSource = new Lang.Class({ _init: function(manager) { this.parent(_("Removable Devices"), 'media-removable'); - this.showInLockScreen = false; this._mounts = []; @@ -301,6 +300,10 @@ const AutorunResidentSource = new Lang.Class({ this._notification = new AutorunResidentNotification(this._manager, this); }, + _createPolicy: function() { + return new MessageTray.NotificationPolicy({ showInLockScreen: false }); + }, + buildRightClickMenu: function() { return null; }, diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index eec5a3be8..103248c92 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -248,6 +248,32 @@ function strHasSuffix(string, suffix) { return string.substr(-suffix.length) == suffix; } +// NotificationPolicy: +// An object that holds all bits of configurable policy related to a notification +// source, such as whether to play sound or honour the critical bit. +// +// A notification without a policy object will inherit the default one. +const NotificationPolicy = new Lang.Class({ + Name: 'NotificationPolicy', + + _init: function(params) { + params = Params.parse(params, { enable: true, + enableSound: true, + showBanners: true, + forceExpanded: false, + showInLockScreen: true, + detailsInLockScreen: false + }); + Lang.copyProperties(params, this); + }, + + // Do nothing for the default policy. These methods are only useful for the + // GSettings policy. + store: function() { }, + destroy: function() { } +}); +Signals.addSignalMethods(NotificationPolicy.prototype); + // Notification: // @source: the notification's Source // @title: the title @@ -1089,10 +1115,11 @@ const Source = new Lang.Class({ this.isTransient = false; this.isChat = false; this.isMuted = false; - this.showInLockScreen = true; this.keepTrayOnSummaryClick = false; this.notifications = []; + + this.policy = this._createPolicy(); }, get count() { @@ -1111,6 +1138,10 @@ const Source = new Lang.Class({ this.emit('count-updated'); }, + _createPolicy: function() { + return new NotificationPolicy(); + }, + buildRightClickMenu: function() { let item; let rightClickMenu = new St.BoxLayout({ name: 'summary-right-click-menu', @@ -1200,11 +1231,13 @@ const Source = new Lang.Class({ notify: function(notification) { notification.acknowledged = false; this.pushNotification(notification); - if (!this.isMuted) - this.emit('notify', notification); + + if (!this.isMuted && this.policy.showBanners) + this.emit('notify', notification); }, destroy: function(reason) { + this.policy.destroy(); this.emit('destroy', reason); }, @@ -1300,6 +1333,14 @@ const SummaryItem = new Lang.Class({ global.focus_manager.add_group(this.rightClickMenu); }, + destroy: function() { + // remove the actor from the summary item so it doesn't get destroyed + // with us + this._sourceBox.remove_actor(this._sourceIcon); + + this.actor.destroy(); + }, + _onKeyPress: function(actor, event) { if (event.get_key_symbol() == Clutter.KEY_Up) { actor.emit('clicked', 1); @@ -1682,7 +1723,12 @@ const MessageTray = new Lang.Class({ return; } - this._addSource(source); + // Register that we got a notification for this source + source.policy.store(); + + source.policy.connect('enable-changed', Lang.bind(this, this._onSourceEnableChanged, source)); + source.policy.connect('policy-changed', Lang.bind(this, this._updateState)); + this._onSourceEnableChanged(source.policy, source); }, _addSource: function(source) { @@ -1773,6 +1819,18 @@ const MessageTray = new Lang.Class({ }); }, + _onSourceEnableChanged: function(policy, source) { + let wasEnabled = this.contains(source); + let shouldBeEnabled = policy.enable; + + if (wasEnabled != shouldBeEnabled) { + if (shouldBeEnabled) + this._addSource(source); + else + this._removeSource(source); + } + }, + _onSourceDestroy: function(source) { this._removeSource(source); }, @@ -2303,8 +2361,10 @@ const MessageTray = new Lang.Class({ _updateShowingNotification: function() { this._notification.acknowledged = true; - // We auto-expand notifications with CRITICAL urgency. - if (this._notification.urgency == Urgency.CRITICAL) + // We auto-expand notifications with CRITICAL urgency, or for which the relevant setting + // is on in the control center. + if (this._notification.urgency == Urgency.CRITICAL || + this._notification.source.policy.forceExpanded) this._expandNotification(true); // We tween all notifications to full opacity. This ensures that both new notifications and diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js index e353d8d15..cbe28315a 100644 --- a/js/ui/notificationDaemon.js +++ b/js/ui/notificationDaemon.js @@ -103,6 +103,126 @@ const STANDARD_TRAY_ICON_IMPLEMENTATIONS = { 'ibus-ui-gtk': 'keyboard' }; +const NotificationGenericPolicy = new Lang.Class({ + Name: 'NotificationGenericPolicy', + Extends: MessageTray.NotificationPolicy, + + _init: function() { + // Don't chain to parent, it would try setting + // our properties to the defaults + + this.id = 'generic'; + + this._masterSettings = new Gio.Settings({ schema: 'org.gnome.desktop.notifications' }); + this._masterSettings.connect('changed', Lang.bind(this, this._changed)); + }, + + store: function() { }, + + destroy: function() { + this._masterSettings.run_dispose(); + }, + + _changed: function(settings, key) { + this.emit('policy-changed', key); + }, + + get enable() { + return true; + }, + + get enableSound() { + return true; + }, + + get showBanners() { + return this._masterSettings.get_boolean('show-banners'); + }, + + get forceExpanded() { + return false; + }, + + get showInLockScreen() { + return this._masterSettings.get_boolean('show-in-lock-screen'); + }, + + get detailsInLockScreen() { + return false; + } +}); + +const NotificationApplicationPolicy = new Lang.Class({ + Name: 'NotificationApplicationPolicy', + Extends: MessageTray.NotificationPolicy, + + _init: function(id) { + // Don't chain to parent, it would try setting + // our properties to the defaults + + this.id = id; + this._canonicalId = this._canonicalizeId(id) + + this._masterSettings = new Gio.Settings({ schema: 'org.gnome.desktop.notifications' }); + this._settings = new Gio.Settings({ schema: 'org.gnome.desktop.notifications.application', + path: '/org/gnome/desktop/notifications/application/' + this._canonicalId + '/' }); + + this._masterSettings.connect('changed', Lang.bind(this, this._changed)); + this._settings.connect('changed', Lang.bind(this, this._changed)); + }, + + store: function() { + this._settings.set_string('application-id', this.id + '.desktop'); + + let apps = this._masterSettings.get_strv('application-children'); + if (apps.indexOf(this._canonicalId) < 0) { + apps.push(this._canonicalId); + this._masterSettings.set_strv('application-children', apps); + } + }, + + destroy: function() { + this._masterSettings.run_dispose(); + this._settings.run_dispose(); + }, + + _changed: function(settings, key) { + this.emit('policy-changed', key); + }, + + _canonicalizeId: function(id) { + // Keys are restricted to lowercase alphanumeric characters and dash, + // and two dashes cannot be in succession + return id.toLowerCase().replace(/[^a-z0-9\-]/g, '-').replace(/--+/g, '-'); + }, + + get enable() { + return this._settings.get_boolean('enable'); + }, + + get enableSound() { + return this._settings.get_boolean('enable-sound-alerts'); + }, + + get showBanners() { + return this._masterSettings.get_boolean('show-banners') && + this._settings.get_boolean('show-banners'); + }, + + get forceExpanded() { + return this._settings.get_boolean('force-expanded'); + }, + + get showInLockScreen() { + return this._masterSettings.get_boolean('show-in-lock-screen') && + this._settings.get_boolean('show-in-lock-screen'); + }, + + get detailsInLockScreen() { + return this._settings.get_boolean('details-in-lock-screen'); + } +}); + const NotificationDaemon = new Lang.Class({ Name: 'NotificationDaemon', @@ -546,6 +666,15 @@ const Source = new Lang.Class({ } }, + _createPolicy: function() { + if (this.app) { + let id = this.app.get_id().replace(/\.desktop$/,''); + return new NotificationApplicationPolicy(id); + } else { + return new NotificationGenericPolicy(); + } + }, + _onNameVanished: function() { // 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 diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js index 699fb96f3..38da9df15 100644 --- a/js/ui/screenShield.js +++ b/js/ui/screenShield.js @@ -851,12 +851,10 @@ const ScreenShield = new Lang.Class({ this._lockScreenContents.add_actor(this._lockScreenContentsBox); - if (this._settings.get_boolean('show-notifications')) { - this._notificationsBox = new NotificationsBox(); - this._lockScreenContentsBox.add(this._notificationsBox.actor, { x_fill: true, - y_fill: true, - expand: true }); - } + this._notificationsBox = new NotificationsBox(); + this._lockScreenContentsBox.add(this._notificationsBox.actor, { x_fill: true, + y_fill: true, + expand: true }); this._hasLockScreen = true; },