/* * This snippet is taken from the ags examples directory. It is licensed under GPLv3. * More information available here: https://github.com/Aylur/ags/blob/main/LICENSE * * Modifications have been made. * */ const notifications = await Service.import("notifications") /** @param {import('resource:///com/github/Aylur/ags/service/notifications.js').Notification} n */ function NotificationIcon({ app_entry, app_icon, image }) { if (image) { return Widget.Box({ css: `background-image: url("${image}");` + "background-size: contain;" + "background-repeat: no-repeat;" + "background-position: center;", }) } let icon = "dialog-information-symbolic" if (Utils.lookUpIcon(app_icon)) icon = app_icon if (app_entry && Utils.lookUpIcon(app_entry)) icon = app_entry return Widget.Box({ child: Widget.Icon(icon), }) } /** @param {import('resource:///com/github/Aylur/ags/service/notifications.js').Notification} n */ function Notification(n) { /* Setting the max width in gtk css is not supported for some reason * so we have to split the length of the lines here by inserting newline * characters where appropriate. */ let body_text = n.body; for (let i = 0; i < body_text.length; i += 40) { let left = body_text.substring(0, i); let right = body_text.substring(i); body_text = left + "\n" + right; } const icon = Widget.Box({ vpack: "start", class_name: "icon", child: NotificationIcon(n), }) const title = Widget.Label({ class_name: "title", xalign: 0, justification: "left", hexpand: true, max_width_chars: 24, truncate: "end", wrap: true, label: n.summary, use_markup: true, }) const body = Widget.Label({ class_name: "body", hexpand: true, use_markup: true, xalign: 0, justification: "left", label: body_text, wrap: true, }) const actions = Widget.Box({ class_name: "actions", children: n.actions.map(({ id, label }) => Widget.Button({ class_name: "action-button", on_clicked: () => { n.invoke(id) n.dismiss() }, hexpand: true, child: Widget.Label(label), })), }) return Widget.EventBox( { attribute: { id: n.id }, on_primary_click: n.dismiss, }, Widget.Box( { class_name: `notification ${n.urgency}`, vertical: true, }, Widget.Box([ icon, Widget.Box( { vertical: true }, title, body, ), ]), actions, ), ) } export function NotificationPopups(monitor = 0) { const list = Widget.Box({ vertical: true, children: notifications.popups.map(Notification), }) function onNotified(_, /** @type {number} */ id) { const n = notifications.getNotification(id) if (n) list.children = [Notification(n), ...list.children] } function onDismissed(_, /** @type {number} */ id) { list.children.find(n => n.attribute.id === id)?.destroy() } list.hook(notifications, onNotified, "notified") .hook(notifications, onDismissed, "dismissed") return Widget.Window({ monitor, name: `notifications${monitor}`, class_name: "notification-popups", anchor: ["top", "right"], child: Widget.Box({ css: "min-width: 2px; min-height: 2px;", class_name: "notifications", vertical: true, child: list, /** this is a simple one liner that could be used instead of hooking into the 'notified' and 'dismissed' signals. but its not very optimized becuase it will recreate the whole list everytime a notification is added or dismissed */ // children: notifications.bind('popups') // .as(popups => popups.map(Notification)) }), }) }