FdoNotificationDaemon: Actually associate a Source with an app
Actually link a notification source with an app instead of just to its app name and PID, which in many cases don't really identify an app. E.g. for portal applications the PID points to the xdg-desktop-portal. Use the app when ever possible but keep using the app name and PID as a fallback. Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3173>
This commit is contained in:
parent
d63013f075
commit
b1d2a5bac8
@ -35,8 +35,9 @@ class FdoNotificationDaemon {
|
|||||||
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(FdoNotificationsIface, this);
|
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(FdoNotificationsIface, this);
|
||||||
this._dbusImpl.export(Gio.DBus.session, '/org/freedesktop/Notifications');
|
this._dbusImpl.export(Gio.DBus.session, '/org/freedesktop/Notifications');
|
||||||
|
|
||||||
this._sources = [];
|
this._sourcesForApp = new Map();
|
||||||
this._notifications = {};
|
this._sourceForPidAndName = new Map();
|
||||||
|
this._notifications = new Map();
|
||||||
|
|
||||||
this._nextNotificationId = 1;
|
this._nextNotificationId = 1;
|
||||||
}
|
}
|
||||||
@ -72,54 +73,69 @@ class FdoNotificationDaemon {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_lookupSource(title, pid) {
|
_getApp(pid, appId, appName) {
|
||||||
for (let i = 0; i < this._sources.length; i++) {
|
const appSys = Shell.AppSystem.get_default();
|
||||||
let source = this._sources[i];
|
let app;
|
||||||
if (source.pid === pid && source.initialTitle === title)
|
|
||||||
return source;
|
app = Shell.WindowTracker.get_default().get_app_from_pid(pid);
|
||||||
}
|
if (!app && appId)
|
||||||
return null;
|
app = appSys.lookup_app(`${appId}.desktop`);
|
||||||
|
|
||||||
|
if (!app)
|
||||||
|
app = appSys.lookup_app(`${appName}.desktop`);
|
||||||
|
|
||||||
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the source associated with ndata.notification if it is set.
|
// Returns the source associated with an app.
|
||||||
// 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
|
// If no existing source is found a new one is created.
|
||||||
// pid is provided.
|
_getSourceForApp(sender, app) {
|
||||||
_getSource(title, pid, ndata, sender) {
|
let source = this._sourcesForApp.get(app);
|
||||||
if (!pid && !(ndata && ndata.notification))
|
|
||||||
throw new Error('Either a pid or ndata.notification is needed');
|
|
||||||
|
|
||||||
// We use notification's source for the notifications we still have
|
|
||||||
// around that are getting replaced because we don't keep sources
|
|
||||||
// for transient notifications in this._sources, but we still want
|
|
||||||
// the notification associated with them to get replaced correctly.
|
|
||||||
if (ndata && ndata.notification)
|
|
||||||
return ndata.notification.source;
|
|
||||||
|
|
||||||
let source = this._lookupSource(title, pid);
|
|
||||||
if (source)
|
if (source)
|
||||||
return source;
|
return source;
|
||||||
|
|
||||||
const appId = ndata?.hints['desktop-entry'];
|
source = new FdoNotificationDaemonSource(sender, app);
|
||||||
source = new FdoNotificationDaemonSource(title, pid, sender, appId);
|
|
||||||
|
|
||||||
this._sources.push(source);
|
if (app) {
|
||||||
source.connect('destroy', () => {
|
this._sourcesForApp.set(app, source);
|
||||||
let index = this._sources.indexOf(source);
|
source.connect('destroy', () => {
|
||||||
if (index >= 0)
|
this._sourcesForApp.delete(app);
|
||||||
this._sources.splice(index, 1);
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
Main.messageTray.add(source);
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the source associated with a pid and the app name.
|
||||||
|
//
|
||||||
|
// If no existing source is found, a new one is created.
|
||||||
|
_getSourceForPidAndName(sender, pid, appName) {
|
||||||
|
const key = `${pid}${appName}`;
|
||||||
|
let source = this._sourceForPidAndName.get(key);
|
||||||
|
|
||||||
|
if (source)
|
||||||
|
return source;
|
||||||
|
|
||||||
|
source = new FdoNotificationDaemonSource(sender, null);
|
||||||
|
|
||||||
|
// Only check whether we have a PID since it's enough to identify
|
||||||
|
// uniquely an app and "" is a valid app name.
|
||||||
|
if (pid) {
|
||||||
|
this._sourceForPidAndName.set(key, source);
|
||||||
|
source.connect('destroy', () => {
|
||||||
|
this._sourceForPidAndName.delete(key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Main.messageTray.add(source);
|
Main.messageTray.add(source);
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
NotifyAsync(params, invocation) {
|
NotifyAsync(params, invocation) {
|
||||||
let [appName, replacesId, appIcon, summary, body, actions, hints, timeout] = params;
|
let [appName, replacesId, appIcon, summary, body, actions, hints, timeout_] = params;
|
||||||
let id;
|
let id;
|
||||||
|
|
||||||
for (let hint in hints) {
|
for (let hint in hints) {
|
||||||
@ -143,42 +159,26 @@ class FdoNotificationDaemon {
|
|||||||
hints['image-data'] = hints['icon_data'];
|
hints['image-data'] = hints['icon_data'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const ndata = {
|
let source, notification;
|
||||||
appName,
|
if (replacesId !== 0 && this._notifications.has(replacesId)) {
|
||||||
appIcon,
|
notification = this._notifications.get(replacesId);
|
||||||
summary,
|
source = notification.source;
|
||||||
body,
|
id = replacesId;
|
||||||
actions,
|
|
||||||
hints,
|
|
||||||
timeout,
|
|
||||||
};
|
|
||||||
if (replacesId !== 0 && this._notifications[replacesId]) {
|
|
||||||
ndata.id = id = replacesId;
|
|
||||||
ndata.notification = this._notifications[replacesId].notification;
|
|
||||||
} else {
|
} else {
|
||||||
replacesId = 0;
|
const sender = hints['x-shell-sender'];
|
||||||
ndata.id = id = this._nextNotificationId++;
|
const pid = hints['x-shell-sender-pid'];
|
||||||
}
|
const appId = hints['desktop-entry'];
|
||||||
this._notifications[id] = ndata;
|
const app = this._getApp(pid, appId, appName);
|
||||||
|
|
||||||
const sender = hints['x-shell-sender'];
|
id = this._nextNotificationId++;
|
||||||
const pid = hints['x-shell-sender-pid'];
|
source = app
|
||||||
|
? this._getSourceForApp(sender, app)
|
||||||
|
: this._getSourceForPidAndName(sender, pid, appName);
|
||||||
|
|
||||||
let source = this._getSource(appName, pid, ndata, sender, null);
|
|
||||||
this._notifyForSource(source, ndata);
|
|
||||||
|
|
||||||
return invocation.return_value(GLib.Variant.new('(u)', [id]));
|
|
||||||
}
|
|
||||||
|
|
||||||
_notifyForSource(source, ndata) {
|
|
||||||
const {appIcon, summary, body, actions, hints} = ndata;
|
|
||||||
let {notification} = ndata;
|
|
||||||
|
|
||||||
if (notification == null) {
|
|
||||||
notification = new MessageTray.Notification(source);
|
notification = new MessageTray.Notification(source);
|
||||||
ndata.notification = notification;
|
this._notifications.set(id, notification);
|
||||||
notification.connect('destroy', (n, reason) => {
|
notification.connect('destroy', (n, reason) => {
|
||||||
delete this._notifications[ndata.id];
|
this._notifications.delete(id);
|
||||||
let notificationClosedReason;
|
let notificationClosedReason;
|
||||||
switch (reason) {
|
switch (reason) {
|
||||||
case MessageTray.NotificationDestroyedReason.EXPIRED:
|
case MessageTray.NotificationDestroyedReason.EXPIRED:
|
||||||
@ -191,7 +191,7 @@ class FdoNotificationDaemon {
|
|||||||
notificationClosedReason = NotificationClosedReason.APP_CLOSED;
|
notificationClosedReason = NotificationClosedReason.APP_CLOSED;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this._emitNotificationClosed(ndata.id, notificationClosedReason);
|
this._emitNotificationClosed(id, notificationClosedReason);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,8 +216,8 @@ class FdoNotificationDaemon {
|
|||||||
hasDefaultAction = true;
|
hasDefaultAction = true;
|
||||||
} else {
|
} else {
|
||||||
notification.addAction(label, () => {
|
notification.addAction(label, () => {
|
||||||
this._emitActivationToken(source, ndata.id);
|
this._emitActivationToken(source, id);
|
||||||
this._emitActionInvoked(ndata.id, actionId);
|
this._emitActionInvoked(id, actionId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,8 +225,8 @@ class FdoNotificationDaemon {
|
|||||||
|
|
||||||
if (hasDefaultAction) {
|
if (hasDefaultAction) {
|
||||||
notification.connect('activated', () => {
|
notification.connect('activated', () => {
|
||||||
this._emitActivationToken(source, ndata.id);
|
this._emitActivationToken(source, id);
|
||||||
this._emitActionInvoked(ndata.id, 'default');
|
this._emitActionInvoked(id, 'default');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
notification.connect('activated', () => {
|
notification.connect('activated', () => {
|
||||||
@ -257,16 +257,14 @@ class FdoNotificationDaemon {
|
|||||||
|
|
||||||
// Only fallback to 'app-icon' when the source doesn't have a valid app
|
// Only fallback to 'app-icon' when the source doesn't have a valid app
|
||||||
const sourceGIcon = source.app ? null : this._iconForNotificationData(appIcon);
|
const sourceGIcon = source.app ? null : this._iconForNotificationData(appIcon);
|
||||||
source.processNotification(notification, sourceGIcon);
|
source.processNotification(notification, appName, sourceGIcon);
|
||||||
|
|
||||||
|
return invocation.return_value(GLib.Variant.new('(u)', [id]));
|
||||||
}
|
}
|
||||||
|
|
||||||
CloseNotification(id) {
|
CloseNotification(id) {
|
||||||
let ndata = this._notifications[id];
|
const notification = this._notifications.get(id);
|
||||||
if (ndata) {
|
notification?.destroy(MessageTray.NotificationDestroyedReason.SOURCE_CLOSED);
|
||||||
if (ndata.notification)
|
|
||||||
ndata.notification.destroy(MessageTray.NotificationDestroyedReason.SOURCE_CLOSED);
|
|
||||||
delete this._notifications[id];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GetCapabilities() {
|
GetCapabilities() {
|
||||||
@ -316,30 +314,15 @@ class FdoNotificationDaemon {
|
|||||||
|
|
||||||
export const FdoNotificationDaemonSource = GObject.registerClass(
|
export const FdoNotificationDaemonSource = GObject.registerClass(
|
||||||
class FdoNotificationDaemonSource extends MessageTray.Source {
|
class FdoNotificationDaemonSource extends MessageTray.Source {
|
||||||
constructor(title, pid, sender, appId) {
|
constructor(sender, app) {
|
||||||
const appSys = Shell.AppSystem.get_default();
|
|
||||||
let app;
|
|
||||||
|
|
||||||
app = Shell.WindowTracker.get_default().get_app_from_pid(pid);
|
|
||||||
if (!app && appId)
|
|
||||||
app = appSys.lookup_app(`${appId}.desktop`);
|
|
||||||
|
|
||||||
if (!app)
|
|
||||||
app = appSys.lookup_app(`${title}.desktop`);
|
|
||||||
|
|
||||||
// Use app name as title if available, instead of whatever is provided
|
|
||||||
// through libnotify (usually garbage)
|
|
||||||
super({
|
super({
|
||||||
title: app?.get_name() ?? title,
|
|
||||||
policy: MessageTray.NotificationPolicy.newForApp(app),
|
policy: MessageTray.NotificationPolicy.newForApp(app),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.pid = pid;
|
|
||||||
this.initialTitle = title;
|
|
||||||
this.app = app;
|
this.app = app;
|
||||||
|
this._appName = null;
|
||||||
this._appIcon = null;
|
this._appIcon = null;
|
||||||
|
|
||||||
|
|
||||||
if (sender) {
|
if (sender) {
|
||||||
this._nameWatcherId = Gio.DBus.session.watch_name(sender,
|
this._nameWatcherId = Gio.DBus.session.watch_name(sender,
|
||||||
Gio.BusNameWatcherFlags.NONE,
|
Gio.BusNameWatcherFlags.NONE,
|
||||||
@ -360,13 +343,17 @@ class FdoNotificationDaemonSource extends MessageTray.Source {
|
|||||||
this.destroy();
|
this.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
processNotification(notification, appIcon) {
|
processNotification(notification, appName, appIcon) {
|
||||||
|
if (!this.app && appName) {
|
||||||
|
this._appName = appName;
|
||||||
|
this.notify('title');
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.app && appIcon) {
|
if (!this.app && appIcon) {
|
||||||
this._appIcon = appIcon;
|
this._appIcon = appIcon;
|
||||||
this.notify('icon');
|
this.notify('icon');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let tracker = Shell.WindowTracker.get_default();
|
let tracker = Shell.WindowTracker.get_default();
|
||||||
if (notification.resident && this.app && tracker.focus_app === this.app)
|
if (notification.resident && this.app && tracker.focus_app === this.app)
|
||||||
this.pushNotification(notification);
|
this.pushNotification(notification);
|
||||||
@ -397,6 +384,10 @@ class FdoNotificationDaemonSource extends MessageTray.Source {
|
|||||||
super.destroy();
|
super.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return this.app?.get_name() ?? this._appName;
|
||||||
|
}
|
||||||
|
|
||||||
get icon() {
|
get icon() {
|
||||||
return this.app?.get_icon() ?? this._appIcon;
|
return this.app?.get_icon() ?? this._appIcon;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user