notification: Use the same Source for showing system notifications

This drops all subclasses of `MessageTray.Source` that were used to
display system notifications. Now the `Source` returned from
`MessageTray.getSystemSource()` is used.
Ensure also that properties and methods that where set on the `Source`
are moved to the `Notification` object itself.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3156>
This commit is contained in:
Julian Sparber 2024-02-02 15:42:31 +01:00 committed by Marge Bot
parent 3faf1caead
commit c1ff00c01b
9 changed files with 65 additions and 178 deletions

View File

@ -6,7 +6,6 @@ import GObject from 'gi://GObject';
import St from 'gi://St'; import St from 'gi://St';
import * as GnomeSession from '../../misc/gnomeSession.js'; import * as GnomeSession from '../../misc/gnomeSession.js';
import * as Main from '../main.js';
import * as MessageTray from '../messageTray.js'; import * as MessageTray from '../messageTray.js';
Gio._promisify(Gio.Mount.prototype, 'guess_content_type'); Gio._promisify(Gio.Mount.prototype, 'guess_content_type');
@ -165,7 +164,7 @@ class AutorunManager {
class AutorunDispatcher { class AutorunDispatcher {
constructor(manager) { constructor(manager) {
this._manager = manager; this._manager = manager;
this._sources = []; this._notifications = new Map();
this._settings = new Gio.Settings({schema_id: SETTINGS_SCHEMA}); this._settings = new Gio.Settings({schema_id: SETTINGS_SCHEMA});
} }
@ -185,26 +184,16 @@ class AutorunDispatcher {
return AutorunSetting.ASK; return AutorunSetting.ASK;
} }
_getSourceForMount(mount) { _addNotification(mount, apps) {
let filtered = this._sources.filter(source => source.mount === mount); // Only show a new notification if there isn't already an existing one
if (this._notifications.has(mount))
// we always make sure not to add two sources for the same
// mount in addMount(), so it's safe to assume filtered.length
// is always either 1 or 0.
if (filtered.length === 1)
return filtered[0];
return null;
}
_addSource(mount, apps) {
// if we already have a source showing for this
// mount, return
if (this._getSourceForMount(mount))
return; return;
// add a new source const source = MessageTray.getSystemSource();
this._sources.push(new AutorunSource(this._manager, mount, apps)); const notification = new AutorunNotification(source, mount, apps);
notification.connect('destroy', () => this._notifications.delete(mount));
this._notifications.set(mount, notification);
source.showNotification(notification);
} }
addMount(mount, apps, contentTypes) { addMount(mount, apps, contentTypes) {
@ -241,58 +230,29 @@ class AutorunDispatcher {
// we fallback here also in case the settings did not specify 'ask', // we fallback here also in case the settings did not specify 'ask',
// but we failed launching the default app or the default file manager // but we failed launching the default app or the default file manager
if (!success) if (!success)
this._addSource(mount, apps); this._addNotification(mount, apps);
} }
removeMount(mount) { removeMount(mount) {
let source = this._getSourceForMount(mount); this._notifications.get(mount)?.destroy();
// if we aren't tracking this mount, don't do anything
if (!source)
return;
// destroy the notification source
source.destroy();
} }
} }
const AutorunSource = GObject.registerClass(
class AutorunSource extends MessageTray.Source {
constructor(manager, mount, apps) {
super({
title: mount.get_name(),
icon: mount.get_icon(),
});
this._manager = manager;
this.mount = mount;
this.apps = apps;
this._notification = new AutorunNotification(this._manager, this);
// add ourselves as a source, and popup the notification
Main.messageTray.add(this);
this.showNotification(this._notification);
}
_createPolicy() {
return new MessageTray.NotificationApplicationPolicy('org.gnome.Nautilus');
}
});
const AutorunNotification = GObject.registerClass( const AutorunNotification = GObject.registerClass(
class AutorunNotification extends MessageTray.Notification { class AutorunNotification extends MessageTray.Notification {
_init(manager, source) { constructor(source, mount, apps) {
super._init(source, source.title); super(source, mount.get_name());
this._manager = manager; this.gicon = mount.get_icon();
this._mount = source.mount;
this._mount = mount;
this._apps = apps;
} }
createBanner() { createBanner() {
let banner = new MessageTray.NotificationBanner(this); let banner = new MessageTray.NotificationBanner(this);
this.source.apps.forEach(app => { this._apps.forEach(app => {
let actor = this._buttonForApp(app); let actor = this._buttonForApp(app);
if (actor) if (actor)

View File

@ -11,7 +11,6 @@ import St from 'gi://St';
import * as Signals from '../../misc/signals.js'; import * as Signals from '../../misc/signals.js';
import * as Dialog from '../dialog.js'; import * as Dialog from '../dialog.js';
import * as Main from '../main.js';
import * as MessageTray from '../messageTray.js'; import * as MessageTray from '../messageTray.js';
import * as ModalDialog from '../modalDialog.js'; import * as ModalDialog from '../modalDialog.js';
import * as ShellEntry from '../shellEntry.js'; import * as ShellEntry from '../shellEntry.js';
@ -740,12 +739,6 @@ class NetworkAgent {
} }
_showNotification(requestId, connection, settingName, hints, flags) { _showNotification(requestId, connection, settingName, hints, flags) {
const source = new MessageTray.Source({
title: _('Network Manager'),
iconName: 'network-transmit-receive',
});
source.policy = new MessageTray.NotificationApplicationPolicy('gnome-network-panel');
let title, body; let title, body;
let connectionSetting = connection.get_setting_connection(); let connectionSetting = connection.get_setting_connection();
@ -788,7 +781,9 @@ class NetworkAgent {
return; return;
} }
let notification = new MessageTray.Notification(source, title, body); const source = new MessageTray.getSystemSource();
const notification = new MessageTray.Notification(source, title, body);
notification.iconName = 'dialog-password-symbolic';
notification.connect('activated', () => { notification.connect('activated', () => {
notification.answered = true; notification.answered = true;
@ -802,7 +797,6 @@ class NetworkAgent {
delete this._notifications[requestId]; delete this._notifications[requestId];
}); });
Main.messageTray.add(source);
source.showNotification(notification); source.showNotification(notification);
} }

View File

@ -274,8 +274,7 @@ async function _initializeUI() {
if (lookingGlass?.isOpen) if (lookingGlass?.isOpen)
return; // assume user action return; // assume user action
const source = new MessageTray.SystemNotificationSource(); const source = MessageTray.getSystemSource();
messageTray.add(source);
const notification = new MessageTray.Notification(source, const notification = new MessageTray.Notification(source,
_('System was put in unsafe mode'), _('System was put in unsafe mode'),
_('Apps now have unrestricted access')); _('Apps now have unrestricted access'));
@ -615,8 +614,7 @@ export function loadTheme() {
* @param {string} details Additional information * @param {string} details Additional information
*/ */
export function notify(msg, details) { export function notify(msg, details) {
let source = new MessageTray.SystemNotificationSource(); const source = MessageTray.getSystemSource();
messageTray.add(source);
let notification = new MessageTray.Notification(source, msg, details); let notification = new MessageTray.Notification(source, msg, details);
notification.setTransient(true); notification.setTransient(true);
source.showNotification(notification); source.showNotification(notification);

View File

@ -1348,17 +1348,3 @@ export function getSystemSource() {
return systemNotificationSource; return systemNotificationSource;
} }
export const SystemNotificationSource = GObject.registerClass(
class SystemNotificationSource extends Source {
constructor() {
super({
title: _('System Information'),
iconName: 'dialog-information-symbolic',
});
}
open() {
this.destroy();
}
});

View File

@ -28,41 +28,29 @@ const DND_WINDOW_SWITCH_TIMEOUT = 750;
const OVERVIEW_ACTIVATION_TIMEOUT = 0.5; const OVERVIEW_ACTIVATION_TIMEOUT = 0.5;
class ShellInfo { class ShellInfo {
constructor() {
this._source = null;
}
setMessage(text, options) { setMessage(text, options) {
options = Params.parse(options, { options = Params.parse(options, {
undoCallback: null, undoCallback: null,
forFeedback: false, forFeedback: false,
}); });
const source = MessageTray.getSystemSource();
let undoCallback = options.undoCallback; let undoCallback = options.undoCallback;
let forFeedback = options.forFeedback; let forFeedback = options.forFeedback;
if (this._source == null) { if (!this._notification) {
this._source = new MessageTray.SystemNotificationSource(); this._notification = new MessageTray.Notification(source, text, null);
this._source.connect('destroy', () => { this._notification.setTransient(true);
this._source = null; this._notification.setForFeedback(forFeedback);
}); this._notification.connect('destroy', () => delete this._notification);
Main.messageTray.add(this._source);
}
let notification = null;
if (this._source.notifications.length === 0) {
notification = new MessageTray.Notification(this._source, text, null);
notification.setTransient(true);
notification.setForFeedback(forFeedback);
} else { } else {
notification = this._source.notifications[0]; this._notification.update(text, null, {clear: true});
notification.update(text, null, {clear: true});
} }
if (undoCallback) if (undoCallback)
notification.addAction(_('Undo'), () => undoCallback()); this._notification.addAction(_('Undo'), () => undoCallback());
this._source.showNotification(notification); source.showNotification(this._notification);
} }
} }

View File

@ -11,7 +11,6 @@ import St from 'gi://St';
import * as Animation from './animation.js'; import * as Animation from './animation.js';
import * as CheckBox from './checkBox.js'; import * as CheckBox from './checkBox.js';
import * as Dialog from './dialog.js'; import * as Dialog from './dialog.js';
import * as Main from './main.js';
import * as MessageTray from './messageTray.js'; import * as MessageTray from './messageTray.js';
import * as ModalDialog from './modalDialog.js'; import * as ModalDialog from './modalDialog.js';
import * as Params from '../misc/params.js'; import * as Params from '../misc/params.js';
@ -181,61 +180,44 @@ export class ShellMountOperation {
} }
_onShowUnmountProgress(op, message, timeLeft, bytesLeft) { _onShowUnmountProgress(op, message, timeLeft, bytesLeft) {
if (!this._notifier)
this._notifier = new ShellUnmountNotifier();
if (bytesLeft === 0) if (bytesLeft === 0)
this._notifier.done(message); this._showUnmountNotificationDone(message);
else else
this._notifier.show(message); this._showUnmountNotification(message);
} }
borrowDialog() { borrowDialog() {
this._dialog?.disconnectObject(this); this._dialog?.disconnectObject(this);
return this._dialog; return this._dialog;
} }
_createNotification(title, body) {
this._notification?.destroy();
const source = MessageTray.getSystemSource();
this._notification = new MessageTray.Notification(source, title, body);
this._notification.setTransient(true);
this._notification.iconName = 'media-removable-symbolic';
this._notification.connect('destroy', () => delete this._notification);
source.showNotification(this._notification);
}
_showUnmountNotificationDone(message) {
if (message)
this._createNotification(message, null);
}
_showUnmountNotification(message) {
const [title, body] = message.split('\n', 2);
if (!this._notification)
this._createNotification(title, body);
else
this._notification.update(title, body);
}
} }
const ShellUnmountNotifier = GObject.registerClass(
class ShellUnmountNotifier extends MessageTray.Source {
constructor() {
super({
iconName: 'media-removable',
});
this._notification = null;
Main.messageTray.add(this);
}
show(message) {
let [header, text] = message.split('\n', 2);
if (!this._notification) {
this._notification = new MessageTray.Notification(this, header, text);
this._notification.setTransient(true);
this._notification.setUrgency(MessageTray.Urgency.CRITICAL);
} else {
this._notification.update(header, text);
}
this.showNotification(this._notification);
}
done(message) {
if (this._notification) {
this._notification.destroy();
this._notification = null;
}
if (message) {
let notification = new MessageTray.Notification(this, message, null);
notification.setTransient(true);
this.showNotification(notification);
}
}
});
const ShellMountQuestionDialog = GObject.registerClass({ const ShellMountQuestionDialog = GObject.registerClass({
Signals: {'response': {param_types: [GObject.TYPE_INT]}}, Signals: {'response': {param_types: [GObject.TYPE_INT]}},
}, class ShellMountQuestionDialog extends ModalDialog.ModalDialog { }, class ShellMountQuestionDialog extends ModalDialog.ModalDialog {

View File

@ -2036,16 +2036,11 @@ class Indicator extends SystemIndicator {
_onActivationFailed() { _onActivationFailed() {
this._notification?.destroy(); this._notification?.destroy();
const source = new MessageTray.Source({ const source = MessageTray.getSystemSource();
title: _('Network Manager'),
iconName: 'network-error-symbolic',
});
source.policy =
new MessageTray.NotificationApplicationPolicy('gnome-network-panel');
this._notification = new MessageTray.Notification(source, this._notification = new MessageTray.Notification(source,
_('Connection failed'), _('Connection failed'),
_('Activation of network connection failed')); _('Activation of network connection failed'));
this._notification.iconName = 'network-error-symbolic';
this._notification.setUrgency(MessageTray.Urgency.HIGH); this._notification.setUrgency(MessageTray.Urgency.HIGH);
this._notification.setTransient(true); this._notification.setTransient(true);
this._notification.connect('destroy', this._notification.connect('destroy',

View File

@ -236,7 +236,6 @@ class Indicator extends SystemIndicator {
Main.sessionMode.connect('updated', this._sync.bind(this)); Main.sessionMode.connect('updated', this._sync.bind(this));
this._sync(); this._sync();
this._source = null;
this._perm = null; this._perm = null;
this._createPermission(); this._createPermission();
} }
@ -254,27 +253,13 @@ class Indicator extends SystemIndicator {
this._client.close(); this._client.close();
} }
_ensureSource() {
if (!this._source) {
this._source = new MessageTray.Source({
title: _('Thunderbolt'),
iconName: 'thunderbolt-symbolic',
});
this._source.connect('destroy', () => (this._source = null));
Main.messageTray.add(this._source);
}
return this._source;
}
_notify(title, body) { _notify(title, body) {
if (this._notification) if (this._notification)
this._notification.destroy(); this._notification.destroy();
let source = this._ensureSource(); const source = MessageTray.getSystemSource();
this._notification = new MessageTray.Notification(source, title, body); this._notification = new MessageTray.Notification(source, title, body);
this._notification.iconName = 'thunderbolt-symbolic';
this._notification.setUrgency(MessageTray.Urgency.HIGH); this._notification.setUrgency(MessageTray.Urgency.HIGH);
this._notification.connect('destroy', () => { this._notification.connect('destroy', () => {
this._notification = null; this._notification = null;

View File

@ -50,8 +50,7 @@ export async function run() {
// notification // notification
console.debug('Show notification message'); console.debug('Show notification message');
const source = new MessageTray.SystemNotificationSource(); const source = new MessageTray.getSystemSource();
Main.messageTray.add(source);
Scripting.scriptEvent('notificationShowStart'); Scripting.scriptEvent('notificationShowStart');
source.connect('notification-show', source.connect('notification-show',