From 7f18d154cd309d0029f223835535ab054746e2da Mon Sep 17 00:00:00 2001 From: rope Date: Sat, 19 Sep 2026 09:38:53 +0100 Subject: [PATCH] Open screenshot notifications in Loupe --- settings/quickshell.nix | 47 +++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/settings/quickshell.nix b/settings/quickshell.nix index d0dc7aa..2b69aa3 100644 --- a/settings/quickshell.nix +++ b/settings/quickshell.nix @@ -1636,16 +1636,14 @@ in } SText { width: parent.width - text: _nc.notif ? (_nc.notif.body || "") : "" + text: bar.isHyprshotNotification(_nc.notif) + ? "Click to annotate in Loupe." + : (_nc.notif ? (_nc.notif.body || "") : "") color: Theme.base04 font.pixelSize: _nc.bodySize elide: Text.ElideRight maximumLineCount: _nc.bodyLines wrapMode: Text.Wrap - // hyprshot's body used to be a noisy save path and was - // hidden. Under --clipboard-only it reads "Image copied - // to the clipboard", which is the only thing correcting - // its own "Screenshot saved" summary — nothing is saved. visible: text !== "" } Row { @@ -3415,11 +3413,39 @@ in } } + function isHyprshotNotification(notif) { + return notif && (notif.appName || "").toLowerCase() === "hyprshot"; + } + + // Hyprshot supplies the saved PNG as an icon and repeats it in + // the body. Quickshell may expose that icon as a themed image + // URL rather than the original path, so keep the body fallback. + function screenshotPath(notif) { + if (!isHyprshotNotification(notif)) return ""; + const marker = "/loupe-screenshots/"; + const candidates = [notif.appIcon || "", notif.image || ""]; + for (let path of candidates) { + if (path.startsWith("file://")) + path = decodeURIComponent(path.substring(7)); + if (path.startsWith("/") && path.includes(marker) && path.endsWith(".png")) + return path; + } + + const body = notif.body || ""; + const markerAt = body.indexOf(marker); + const endAt = body.indexOf(".png", markerAt); + if (markerAt < 0 || endAt < 0) return ""; + const tagAt = body.lastIndexOf("", markerAt); + const startAt = tagAt >= 0 ? tagAt + 3 : body.lastIndexOf(" ", markerAt) + 1; + const path = body.substring(startAt, endAt + 4); + return path.startsWith("/") ? path : ""; + } + // Preview image for a notification: its image hint, else a // file-path appIcon (e.g. album art on disk). "" = none. function notifPreviewSource(notif) { if (!notif) return ""; - if (notif.appName === "Hyprshot") return ""; + if (isHyprshotNotification(notif)) return ""; if (notif.image && notif.image !== "") return notif.image; let a = notif.appIcon || ""; if (a.startsWith("file://")) return a; @@ -3432,7 +3458,7 @@ in // avoid holding full-screen preview textures in the bar. function notifPreviewIcon(notif) { if (!notif) return ""; - return notif.appName === "Hyprshot" ? "camera" : ""; + return isHyprshotNotification(notif) ? "camera" : ""; } // Jump to the window that sent a notification. Toplevel.activate() // goes over wlr-foreign-toplevel — the same path a taskbar uses — @@ -3460,10 +3486,9 @@ in // behind the default action, so fire that too when they send one. function activateNotif(notif) { if (!notif) return; - if (notif.appName === "Hyprshot" && Commands.loupe !== "") { - let path = notif.appIcon || ""; - if (path.startsWith("file://")) path = decodeURIComponent(path.substring(7)); - if (path.startsWith("/") && path.includes("/loupe-screenshots/") && path.endsWith(".png")) { + if (isHyprshotNotification(notif) && Commands.loupe !== "") { + const path = screenshotPath(notif); + if (path !== "") { Quickshell.execDetached([Commands.loupe, path]); return; }