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