diff --git a/data/dbus-interfaces/org.gtk.Notifications.xml b/data/dbus-interfaces/org.gtk.Notifications.xml index 56a6cecbe..02cd31765 100644 --- a/data/dbus-interfaces/org.gtk.Notifications.xml +++ b/data/dbus-interfaces/org.gtk.Notifications.xml @@ -9,5 +9,12 @@ + + + + + + + diff --git a/js/gdm/loginDialog.js b/js/gdm/loginDialog.js index 593f8f582..c9c5ae487 100644 --- a/js/gdm/loginDialog.js +++ b/js/gdm/loginDialog.js @@ -1081,7 +1081,7 @@ export const LoginDialog = GObject.registerClass({ title: _('Login Attempt Timed Out'), body: _('Login took too long, please try again'), urgency: MessageTray.Urgency.CRITICAL, - isTransient: true, + displayHint: MessageTray.DisplayHint.TRANSIENT, }); this._conflictingSessionNotification.connect('destroy', () => { this._conflictingSessionNotification = null; diff --git a/js/ui/appFavorites.js b/js/ui/appFavorites.js index 576df3800..86e5743b4 100644 --- a/js/ui/appFavorites.js +++ b/js/ui/appFavorites.js @@ -1,11 +1,10 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- +import * as MessageTray from './messageTray.js'; import Shell from 'gi://Shell'; import * as ParentalControlsManager from '../misc/parentalControlsManager.js'; import * as Signals from '../misc/signals.js'; -import * as Main from './main.js'; - // In alphabetical order const RENAMED_DESKTOP_IDS = { 'baobab.desktop': 'org.gnome.baobab.desktop', @@ -161,13 +160,11 @@ class AppFavorites extends Signals.EventEmitter { if (!this._addFavorite(appId, pos)) return; - let app = Shell.AppSystem.get_default().lookup_app(appId); + const app = Shell.AppSystem.get_default().lookup_app(appId); - let msg = _('%s has been pinned to the dash.').format(app.get_name()); - Main.overview.setMessage(msg, { - forFeedback: true, - undoCallback: () => this._removeFavorite(appId), - }); + this.showNotification(_('%s has been pinned to the dash.').format(app.get_name()), + null, + () => this._removeFavorite(appId)); } addFavorite(appId) { @@ -196,11 +193,21 @@ class AppFavorites extends Signals.EventEmitter { if (!this._removeFavorite(appId)) return; - let msg = _('%s has been unpinned from the dash.').format(app.get_name()); - Main.overview.setMessage(msg, { - forFeedback: true, - undoCallback: () => this._addFavorite(appId, pos), + this.showNotification(_('%s has been unpinned from the dash.').format(app.get_name()), + null, + () => this._addFavorite(appId, pos)); + } + + showNotification(title, body, undoCallback) { + const source = MessageTray.getSystemSource(); + const notification = new MessageTray.Notification({ + source, + title, + body, + displayHint: MessageTray.DisplayHint.TRANSIENT | MessageTray.DisplayHint.FORCE_IMMEDIATELY, }); + notification.addAction(_('Undo'), () => undoCallback()); + source.addNotification(notification); } } diff --git a/js/ui/calendar.js b/js/ui/calendar.js index 43079ed6d..8dfe108aa 100644 --- a/js/ui/calendar.js +++ b/js/ui/calendar.js @@ -801,6 +801,11 @@ class NotificationMessage extends MessageList.Message { notification.bind_property('gicon', this, 'icon', GObject.BindingFlags.SYNC_CREATE); + notification.bind_property_full('display-hint', + this, 'can-close', + GObject.BindingFlags.SYNC_CREATE, + (bind, value) => [true, !(value & MessageTray.DisplayHint.PERSISTENT)], + null); this._actions = new Map(); this.notification.actions.forEach(action => { @@ -812,10 +817,6 @@ class NotificationMessage extends MessageList.Message { this.notification.activate(); } - canClose() { - return true; - } - _addAction(action) { if (!this._buttonBox) { this._buttonBox = new St.BoxLayout({ diff --git a/js/ui/main.js b/js/ui/main.js index ad0ea962f..7a7a40c48 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -278,7 +278,7 @@ async function _initializeUI() { source, title: _('System was put in unsafe mode'), body: _('Apps now have unrestricted access'), - isTransient: true, + displayHint: MessageTray.DisplayHint.TRANSIENT, }); notification.addAction(_('Undo'), () => (global.context.unsafe_mode = false)); @@ -620,7 +620,7 @@ export function notify(msg, details) { source, title: msg, body: details, - isTransient: true, + displayHint: MessageTray.DisplayHint.TRANSIENT, }); source.addNotification(notification); } diff --git a/js/ui/messageList.js b/js/ui/messageList.js index ddd5abb17..a1a5e4859 100644 --- a/js/ui/messageList.js +++ b/js/ui/messageList.js @@ -405,6 +405,10 @@ export const Message = GObject.registerClass({ 'datetime', 'datetime', 'datetime', GObject.ParamFlags.READWRITE, GLib.DateTime), + 'can-close': GObject.ParamSpec.boolean( + 'can-close', 'can-close', 'can-close', + GObject.ParamFlags.READWRITE, + false), }, Signals: { 'close': {}, @@ -487,7 +491,8 @@ export const Message = GObject.registerClass({ this.connect('destroy', this._onDestroy.bind(this)); this._header.closeButton.connect('clicked', this.close.bind(this)); - this._header.closeButton.visible = this.canClose(); + + this.bind_property('can-close', this._header.closeButton, 'visible', GObject.BindingFlags.SYNC_CREATE); this._header.expandButton.connect('clicked', () => { if (this.expanded) @@ -633,10 +638,6 @@ export const Message = GObject.registerClass({ this.emit('unexpanded'); } - canClose() { - return false; - } - _onDestroy() { } @@ -646,7 +647,7 @@ export const Message = GObject.registerClass({ if (keysym === Clutter.KEY_Delete || keysym === Clutter.KEY_KP_Delete || keysym === Clutter.KEY_BackSpace) { - if (this.canClose()) { + if (this.canClose) { this.close(); return Clutter.EVENT_STOP; } @@ -816,7 +817,7 @@ export const MessageListSection = GObject.registerClass({ } clear() { - let messages = this._messages.filter(msg => msg.canClose()); + let messages = this._messages.filter(msg => msg.canClose); // If there are few messages, letting them all zoom out looks OK if (messages.length < 2) { @@ -854,7 +855,7 @@ export const MessageListSection = GObject.registerClass({ this.notify('empty'); } - let canClear = messages.some(m => m.canClose()); + let canClear = messages.some(m => m.canClose); if (this._canClear !== canClear) { this._canClear = canClear; this.notify('can-clear'); diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index fc07ab4ec..7d9fd3c66 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -78,6 +78,25 @@ export const PrivacyScope = { SYSTEM: 1, }; +/** @enum {number} */ +export const DisplayHint = { + NONE: 0, + // Only show a banner for the message. + TRANSIENT: 1 << 0, + // Don't show a banner but keep the message till dismissed. + NO_BANNER: 1 << 1, + // Show a banner immediately. + FORCE_IMMEDIATELY: 1 << 2, + // Message that can't be dismissed by the user. + PERSISTENT: 1 << 3, + // Message that won't automatically dismissed when an action is invoked. + RESIDENT: 1 << 4, + // Show on the lock screen that a source as a message, but without any content. + SHOW_ON_LOCKSCREEN: 1 << 5, + // Show the message on the lock screen. + SHOW_CONTENT_ON_LOCKSCREEN: 1 << 6, +}; + class FocusGrabber { constructor(actor) { this._actor = actor; @@ -314,11 +333,43 @@ export const NotificationApplicationPolicy = GObject.registerClass({ export const Sound = GObject.registerClass( class Sound extends GObject.Object { - constructor(file, themedName) { - super(); + static newForFile(file) { + const sound = new Sound(); + sound._soundFile = file; + return sound; + } - this._soundFile = file; - this._soundName = themedName; + static newForName(name) { + const sound = new Sound(); + sound._soundName = name; + return sound; + } + + static newForBytes(bytes) { + const sound = new Sound(); + sound._soundBytes = bytes; + return sound; + } + + static deserialize(serializedSound) { + const sound = serializedSound?.unpack(); + + if (!sound || sound === 'silent') + return null; + + if (sound === 'default') + return Sound.newForName('message'); + + if (sound.length !== 2) + return null; + + const [type, data] = sound.map(v => v.unpack()); + if (type === 'file') + return Sound.newForFile(Gio.File.new_for_commandline_arg(data.unpack())); + if (type === 'bytes') + return Sound.newForBytes(data.unpack()); + + return null; } play() { @@ -328,6 +379,22 @@ class Sound extends GObject.Object { player.play_from_theme(this._soundName, _('Notification sound'), null); else if (this._soundFile) player.play_from_file(this._soundFile, _('Notification sound'), null); + else if (this._soundBytes) + this._playSoundBytes(); + } + + async _playSoundBytes() { + const player = global.display.get_sound_player(); + const [file, stream] = Gio.File.new_tmp('XXXXXX-notification-sound'); + await stream.output_stream.write_bytes_async(this._soundBytes, + GLib.PRIORITY_DEFAULT, null); + stream.close_async(GLib.PRIORITY_DEFAULT, null); + player.play_from_file(file, _('Notification sound'), null); + this._tempFile = file; + } + + cleanupTempFile() { + this._tempFile?.delete(null); } }); @@ -408,6 +475,19 @@ export class Notification extends GObject.Object { this.notify('privacy-scope'); } + get displayHint() { + return this._displayHint; + } + + set displayHint(displayHint) { + // TODO: check if the value is in range + if (this._displayHint === displayHint) + return; + + this._displayHint = displayHint; + this.notify('display-hint'); + } + get urgency() { return this._urgency; } @@ -434,7 +514,7 @@ export class Notification extends GObject.Object { // because it is common for such notifications to update themselves with new // information based on the action. We'd like to display the updated information // in place, rather than pop-up a new notification. - if (this.resident) + if (this.displayHint & DisplayHint.RESIDENT) return; this.destroy(); @@ -467,13 +547,14 @@ export class Notification extends GObject.Object { // because it is common for such notifications to update themselves with new // information based on the action. We'd like to display the updated information // in place, rather than pop-up a new notification. - if (this.resident) + if (this.displayHint & DisplayHint.RESIDENT) return; this.destroy(); } destroy(reason = NotificationDestroyedReason.DISMISSED) { + this.sound?.cleanupTempFile(); this.emit('destroy', reason); if (this._updateDatetimeId) @@ -570,13 +651,16 @@ export const Source = GObject.registerClass({ this.countUpdated(); // If acknowledged was set to false try to show the notification again - if (!notification.acknowledged) + if (!notification.acknowledged && !(notification.displayHint & DisplayHint.NO_BANNER)) this.emit('notification-request-banner', notification); }); this.notifications.push(notification); this.emit('notification-added', notification); - this.emit('notification-request-banner', notification); + + if (!(notification.displayHint & DisplayHint.NO_BANNER)) + this.emit('notification-request-banner', notification); + this.countUpdated(); } @@ -597,13 +681,6 @@ export const Source = GObject.registerClass({ // To be overridden by subclasses open() { } - - destroyNonResidentNotifications() { - for (let i = this.notifications.length - 1; i >= 0; i--) { - if (!this.notifications[i].resident) - this.notifications[i].destroy(); - } - } }); SignalTracker.registerDestroyableType(Source); @@ -669,6 +746,11 @@ GObject.registerClass({ 'is-transient', 'is-transient', 'is-transient', GObject.ParamFlags.READWRITE, false), + 'display-hint': GObject.ParamSpec.int( + 'display-hint', 'display-hint', 'display-hint', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT, + 0, GLib.MAXINT32, + DisplayHint.NONE), }, Signals: { 'action-added': {param_types: [Action]}, @@ -1063,7 +1145,9 @@ export const MessageTray = GObject.registerClass({ let nextNotification = this._notificationQueue[0] || null; if (hasNotifications && nextNotification) { let limited = this._busy || Main.layoutManager.primaryMonitor.inFullscreen; - let showNextNotification = !limited || nextNotification.forFeedback || nextNotification.urgency === Urgency.CRITICAL; + let showNextNotification = !limited || + nextNotification.displayHint & DisplayHint.FORCE_IMMEDIATELY || + nextNotification.urgency === Urgency.CRITICAL; if (showNextNotification) this._showNotification(); } @@ -1254,7 +1338,7 @@ export const MessageTray = GObject.registerClass({ _hideNotificationCompleted() { let notification = this._notification; this._notification = null; - if (!this._notificationRemoved && notification.isTransient) + if (!this._notificationRemoved && notification.displayHint & DisplayHint.TRANSIENT) notification.destroy(NotificationDestroyedReason.EXPIRED); this._pointerInNotification = false; diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js index ff4c26caf..b2f142793 100644 --- a/js/ui/notificationDaemon.js +++ b/js/ui/notificationDaemon.js @@ -197,16 +197,18 @@ class FdoNotificationDaemon { const gicon = this._imageForNotificationData(hints); - const soundFile = 'sound-file' in hints - ? Gio.File.new_for_path(hints['sound-file']) : null; + let sound; + if ('sound-file' in hints) + sound = MessageTray.Sound.newForFile(Gio.File.new_for_path(hints['sound-file'])); + else if ('sound-name' in hints) + sound = MessageTray.Sound.newForName(hints['sound-name']); notification.set({ title: summary, body, gicon, useBodyMarkup: true, - sound: new MessageTray.Sound(soundFile, hints['sound-name']), - acknowledged: false, + sound, }); notification.clearActions(); @@ -248,15 +250,20 @@ class FdoNotificationDaemon { notification.urgency = MessageTray.Urgency.CRITICAL; break; } - notification.resident = !!hints.resident; + + if (hints.resident) + notification.displayHint |= MessageTray.DisplayHint.RESIDENT; + // 'transient' is a reserved keyword in JS, so we have to retrieve the value // of the 'transient' hint with hints['transient'] rather than hints.transient - notification.isTransient = !!hints['transient']; + if (hints['transient']) + notification.displayHint |= MessageTray.DisplayHint.TRANSIENT; - let privacyScope = hints['x-gnome-privacy-scope'] || 'user'; - notification.privacyScope = privacyScope === 'system' - ? MessageTray.PrivacyScope.SYSTEM - : MessageTray.PrivacyScope.USER; + if (hints['x-gnome-privacy-scope'] === 'system') + notification.displayHint |= MessageTray.DisplayHint.SHOW_CONTENT_ON_LOCKSCREEN; + else + // Always show the notification on the lockscreen but without any content + notification.displayHint |= MessageTray.DisplayHint.SHOW_ON_LOCKSCREEN; // Only fallback to 'app-icon' when the source doesn't have a valid app const sourceGIcon = source.app ? null : this._iconForNotificationData(appIcon); @@ -357,18 +364,11 @@ class FdoNotificationDaemonSource extends MessageTray.Source { this.notify('icon'); } - let tracker = Shell.WindowTracker.get_default(); - // Acknowledge notifications that are resident and their app has the - // current focus so that we don't show a banner. - if (notification.resident && this.app && tracker.focus_app === this.app) - notification.acknowledged = true; - this.addNotification(notification); } open() { this.openApp(); - this.destroyNonResidentNotifications(); } openApp() { @@ -409,31 +409,41 @@ const GtkNotificationDaemonNotification = GObject.registerClass( class GtkNotificationDaemonNotification extends MessageTray.Notification { constructor(source, id, notification) { super({source}); - this._serialized = GLib.Variant.new('a{sv}', notification); - this.id = id; + this.id = id; + this.update(notification); + } + + update(notification) { const { title, body, + 'markup-body': markupBody, icon: gicon, urgent, priority, buttons, + sound, 'default-action': defaultAction, 'default-action-target': defaultActionTarget, + 'display-hint': displayHint, timestamp: time, } = notification; - if (priority) { - let urgency = PRIORITY_URGENCY_MAP[priority.unpack()]; - this.urgency = urgency !== undefined ? urgency : MessageTray.Urgency.NORMAL; - } else if (urgent) { - this.urgency = urgent.unpack() - ? MessageTray.Urgency.CRITICAL - : MessageTray.Urgency.NORMAL; - } else { - this.urgency = MessageTray.Urgency.NORMAL; - } + const urgency = PRIORITY_URGENCY_MAP[priority?.unpack()] ?? + urgent?.unpack() ? MessageTray.Urgency.CRITICAL : MessageTray.Urgency.NORMAL; + + this.set({ + title: title.unpack(), + body: markupBody?.unpack() ?? body?.unpack() ?? null, + useBodyMarkup: !!markupBody, + gicon: gicon + ? Gio.icon_deserialize(gicon) : null, + sound: sound ? MessageTray.Sound.deserialize(sound) : null, + datetime: time + ? GLib.DateTime.new_from_unix_local(time.unpack()) : null, + urgency, + }); if (buttons) { buttons.deepUnpack().forEach(button => { @@ -443,37 +453,55 @@ class GtkNotificationDaemonNotification extends MessageTray.Notification { }); } + let showOnLockscreen = true; + let showContentOnLockscreen = true; + if (displayHint) { + for (const hint of displayHint.deepUnpack()) { + if (hint === 'transient') + this.displayHint |= MessageTray.DisplayHint.TRANSIENT; + else if (hint === 'tray') + this.displayHint |= MessageTray.DisplayHint.NO_BANNER; + else if (hint === 'persistent') + this.displayHint |= MessageTray.DisplayHint.PERSISTENT; + else if (hint === 'resident') + this.displayHint |= MessageTray.DisplayHint.RESIDENT; + else if (hint === 'hide-on-lockscreen') + showOnLockscreen = false; + else if (hint === 'hide-content-on-lockscreen') + showContentOnLockscreen = false; + } + } + + if (showOnLockscreen) { + if (showContentOnLockscreen) + this.displayHint |= MessageTray.DisplayHint.SHOW_CONTENT_ON_LOCKSCREEN; + else + this.displayHint |= MessageTray.DisplayHint.SHOW_ON_LOCKSCREEN; + } + + this._serialized = GLib.Variant.new('a{sv}', notification); this._defaultAction = defaultAction?.unpack(); this._defaultActionTarget = defaultActionTarget; - - this.set({ - title: title.unpack(), - body: body?.unpack(), - gicon: gicon - ? Gio.icon_deserialize(gicon) : null, - datetime: time - ? GLib.DateTime.new_from_unix_local(time.unpack()) : null, - }); } - _activateAction(namespacedActionId, target) { - if (namespacedActionId) { - if (namespacedActionId.startsWith('app.')) { - let actionId = namespacedActionId.slice('app.'.length); - this.source.activateAction(actionId, target); - } - } else { - this.source.open(); - } + _activateAction(actionId, target) { + if (actionId.startsWith('app.')) + this.source.activateAction(actionId.slice('app.'.length), target); + else + this.source.emitActionInvoked(this.id, actionId, target); } _onButtonClicked(button) { - let {action, target} = button; + const {action, target} = button; this._activateAction(action.unpack(), target); } activate() { - this._activateAction(this._defaultAction, this._defaultActionTarget); + if (this._defaultAction) + this._activateAction(this._defaultAction, this._defaultActionTarget); + else + this.source.open(); + super.activate(); } @@ -486,7 +514,7 @@ function InvalidAppError() {} export const GtkNotificationDaemonAppSource = GObject.registerClass( class GtkNotificationDaemonAppSource extends MessageTray.Source { - constructor(appId) { + constructor(appId, dbusImpl) { if (!Gio.Application.id_is_valid(appId)) throw new InvalidAppError(); @@ -502,6 +530,7 @@ class GtkNotificationDaemonAppSource extends MessageTray.Source { this._appId = appId; this._app = app; + this._dbusImpl = dbusImpl; this._notifications = {}; this._notificationPending = false; @@ -517,6 +546,22 @@ class GtkNotificationDaemonAppSource extends MessageTray.Source { Main.panel.closeCalendar(); } + emitActionInvoked(notificationId, actionId, target) { + const context = global.create_app_launch_context(0, -1); + const info = this._app.get_app_info(); + const token = context.get_startup_notify_id(info, []); + + this._dbusImpl.emit_signal('ActionInvoked', + GLib.Variant.new('(sssava{sv})', [ + this._appId, + notificationId, + actionId, + target ? [target] : [], + {'activation-token': GLib.Variant.new_string(token)}, + ]) + ); + } + open() { this._app.activate(); Main.overview.hide(); @@ -566,11 +611,11 @@ class GtkNotificationDaemon { constructor() { this._sources = {}; - this._loadNotifications(); - this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GtkNotificationsIface, this); this._dbusImpl.export(Gio.DBus.session, '/org/gtk/Notifications'); + this._loadNotifications(); + Gio.DBus.session.own_name('org.gtk.Notifications', Gio.BusNameOwnerFlags.REPLACE, null, null); } @@ -578,7 +623,7 @@ class GtkNotificationDaemon { if (this._sources[appId]) return this._sources[appId]; - let source = new GtkNotificationDaemonAppSource(appId); + const source = new GtkNotificationDaemonAppSource(appId, this._dbusImpl); source.connect('destroy', () => { delete this._sources[appId]; @@ -656,13 +701,22 @@ class GtkNotificationDaemon { throw e; } - let timestamp = GLib.DateTime.new_now_local().to_unix(); + const timestamp = GLib.DateTime.new_now_local().to_unix(); notificationSerialized['timestamp'] = new GLib.Variant('x', timestamp); + const showAsNew = notificationSerialized['display-hint']?.deepUnpack().some(hint => hint === 'show-as-new'); + const oldNotification = source._notifications[notificationId]; - const notification = new GtkNotificationDaemonNotification(source, - notificationId, - notificationSerialized); - source.addNotification(notification); + if (showAsNew || !oldNotification) { + const notification = new GtkNotificationDaemonNotification( + source, + notificationId, + notificationSerialized + ); + + source.addNotification(notification); + } else { + oldNotification.update(notificationSerialized); + } invocation.return_value(null); } diff --git a/js/ui/overview.js b/js/ui/overview.js index 7e431b68e..fbfd2f2b1 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -16,9 +16,7 @@ export const ANIMATION_TIME = 250; import * as DND from './dnd.js'; import * as LayoutManager from './layout.js'; import * as Main from './main.js'; -import * as MessageTray from './messageTray.js'; import * as OverviewControls from './overviewControls.js'; -import * as Params from '../misc/params.js'; import * as SwipeTracker from './swipeTracker.js'; import * as WindowManager from './windowManager.js'; import * as WorkspaceThumbnail from './workspaceThumbnail.js'; @@ -27,36 +25,6 @@ const DND_WINDOW_SWITCH_TIMEOUT = 750; const OVERVIEW_ACTIVATION_TIMEOUT = 0.5; -class ShellInfo { - setMessage(title, options) { - options = Params.parse(options, { - undoCallback: null, - forFeedback: false, - }); - - const source = MessageTray.getSystemSource(); - let undoCallback = options.undoCallback; - let forFeedback = options.forFeedback; - - if (!this._notification) { - this._notification = new MessageTray.Notification({ - source, - isTransient: true, - forFeedback, - }); - this._notification.connect('destroy', () => delete this._notification); - } - this._notification.set({title}); - - this._notification.clearActions(); - - if (undoCallback) - this._notification.addAction(_('Undo'), () => undoCallback()); - - source.addNotification(this._notification); - } -} - const OverviewActor = GObject.registerClass( class OverviewActor extends St.BoxLayout { _init() { @@ -252,8 +220,6 @@ export class Overview extends Signals.EventEmitter { this._overview._delegate = this; Main.layoutManager.overviewGroup.add_child(this._overview); - this._shellInfo = new ShellInfo(); - Main.layoutManager.connect('monitors-changed', this._relayout.bind(this)); this._relayout(); @@ -275,18 +241,6 @@ export class Overview extends Signals.EventEmitter { this._swipeTracker = swipeTracker; } - // - // options: - // - undoCallback (function): the callback to be called if undo support is needed - // - forFeedback (boolean): whether the message is for direct feedback of a user action - // - setMessage(text, options) { - if (this.isDummy) - return; - - this._shellInfo.setMessage(text, options); - } - _changeShownState(state) { const {allowedTransitions} = OVERVIEW_SHOWN_TRANSITIONS[this._shownState]; diff --git a/js/ui/screenshot.js b/js/ui/screenshot.js index c39603aca..5b966eee1 100644 --- a/js/ui/screenshot.js +++ b/js/ui/screenshot.js @@ -2092,7 +2092,7 @@ export const ScreenshotUI = GObject.registerClass({ title, // Translators: notification body when a screencast was recorded. body: this._screencastPath ? _('Click here to view the video.') : '', - isTransient: true, + displayHint: MessageTray.DisplayHint.TRANSIENT, }); if (this._screencastPath) { @@ -2337,7 +2337,7 @@ function _storeScreenshot(bytes, pixbuf) { body: _('You can paste the image from the clipboard.'), datetime: time, gicon: content, - isTransient: true, + displayHint: MessageTray.DisplayHint.TRANSIENT, }); if (!disableSaveToDisk) { diff --git a/js/ui/shellMountOperation.js b/js/ui/shellMountOperation.js index 27f12fbf9..9774faaa5 100644 --- a/js/ui/shellMountOperation.js +++ b/js/ui/shellMountOperation.js @@ -199,7 +199,7 @@ export class ShellMountOperation { source, title, body, - isTransient: true, + displayHint: MessageTray.DisplayHint.TRANSIENT, iconName: 'media-removable-symbolic', }); diff --git a/js/ui/status/network.js b/js/ui/status/network.js index 404733b74..2f256b8f5 100644 --- a/js/ui/status/network.js +++ b/js/ui/status/network.js @@ -2036,7 +2036,7 @@ class Indicator extends SystemIndicator { body: _('Activation of network connection failed'), iconName: 'network-error-symbolic', urgency: MessageTray.Urgency.HIGH, - isTransient: true, + displayHint: MessageTray.DisplayHint.TRANSIENT, }); this._notification.connect('destroy', () => (this._notification = null)); diff --git a/js/ui/unlockDialog.js b/js/ui/unlockDialog.js index f741b20a6..1233663a0 100644 --- a/js/ui/unlockDialog.js +++ b/js/ui/unlockDialog.js @@ -166,7 +166,9 @@ const NotificationsBox = GObject.registerClass({ _shouldShowDetails(source) { return source.policy.detailsInLockScreen || - source.narrowestPrivacyScope === MessageTray.PrivacyScope.SYSTEM; + source.notifications.every(n => + n.displayHint & MessageTray.DisplayHint.SHOW_CONTENT_ON_LOCKSCREEN + ); } _updateSourceBoxStyle(source, obj, box) { diff --git a/js/ui/windowAttentionHandler.js b/js/ui/windowAttentionHandler.js index e346b1cad..d045f8788 100644 --- a/js/ui/windowAttentionHandler.js +++ b/js/ui/windowAttentionHandler.js @@ -43,7 +43,7 @@ export class WindowAttentionHandler { source, title, body, - forFeedback: true, + displayHint: MessageTray.DisplayHint.FORCE_IMMEDIATELY, }); notification.connect('activated', () => { source.open();