Fix notification centre icons and dismissal
This commit is contained in:
1 parent
77ba1dadff
commit
ad46fe4a53
5 files changed
+50
-17
No files matched your search
@@ -53,6 +53,15 @@ border, so they read against the panel in any theme. An active tile takes the
|
|||||||
accent colour. The Wallpaper tile opens `blob.displays`, the Theme tile opens
|
accent colour. The Wallpaper tile opens `blob.displays`, the Theme tile opens
|
||||||
`blob theme menu`.
|
`blob theme menu`.
|
||||||
|
|
||||||
|
## Notification centre
|
||||||
|
|
||||||
|
`blob.notification-center` lists the notification history, newest first. A
|
||||||
|
toast that expires unseen moves into history; a toast you close or click is
|
||||||
|
deleted instead, so the centre only holds what you missed. The x on an entry
|
||||||
|
removes it through `blob-shell notifications remove <file>`, the bell button
|
||||||
|
toggles silencing, and the trash button clears the whole history with
|
||||||
|
`blob-shell notifications clear`.
|
||||||
|
|
||||||
## Deliberately not ported
|
## Deliberately not ported
|
||||||
|
|
||||||
The tiles still poll shell commands the way the GTK widgets did - `pamixer` for
|
The tiles still poll shell commands the way the GTK widgets did - `pamixer` for
|
||||||
|
|||||||
@@ -1,18 +1,23 @@
|
|||||||
.pragma library
|
.pragma library
|
||||||
|
|
||||||
// Each history file is one line of JSON, so the reader concatenates the
|
// Each history file is one line of JSON, and the reader prefixes every line
|
||||||
// directory and parses line by line. Newest first, by timestamp.
|
// with the file's name and a tab so an entry can be removed later. Newest
|
||||||
|
// first, by timestamp.
|
||||||
function parseHistory(raw) {
|
function parseHistory(raw) {
|
||||||
var lines = String(raw || "").split("\n")
|
var lines = String(raw || "").split("\n")
|
||||||
var entries = []
|
var entries = []
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
var line = lines[i].trim()
|
var line = lines[i].trim()
|
||||||
if (!line) continue
|
if (!line) continue
|
||||||
|
var tab = line.indexOf("\t")
|
||||||
|
var fileName = tab >= 0 ? line.slice(0, tab) : ""
|
||||||
|
var json = tab >= 0 ? line.slice(tab + 1) : line
|
||||||
try {
|
try {
|
||||||
var value = JSON.parse(line)
|
var value = JSON.parse(json)
|
||||||
if (!value || typeof value !== "object") continue
|
if (!value || typeof value !== "object") continue
|
||||||
entries.push({
|
entries.push({
|
||||||
id: value.id || 0,
|
id: value.id || 0,
|
||||||
|
fileName: fileName,
|
||||||
app: value.app || "",
|
app: value.app || "",
|
||||||
summary: value.summary || "",
|
summary: value.summary || "",
|
||||||
body: value.body || "",
|
body: value.body || "",
|
||||||
|
|||||||
@@ -45,8 +45,9 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function clearAll() {
|
function clearAll() {
|
||||||
root.run("blob-shell notifications clearHistory")
|
root.run("blob-shell notifications clear")
|
||||||
root.entries = []
|
root.entries = []
|
||||||
|
refreshTimer.restart()
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSilence() {
|
function toggleSilence() {
|
||||||
@@ -57,16 +58,19 @@ Item {
|
|||||||
function dismissEntry(index) {
|
function dismissEntry(index) {
|
||||||
var entry = root.entries[index]
|
var entry = root.entries[index]
|
||||||
if (!entry) return
|
if (!entry) return
|
||||||
root.run("blob-notify-dismiss " + entry.id)
|
root.run("blob-shell notifications remove " + Util.shellQuote(entry.fileName))
|
||||||
var next = []
|
var next = []
|
||||||
for (var i = 0; i < root.entries.length; i++)
|
for (var i = 0; i < root.entries.length; i++)
|
||||||
if (i !== index) next.push(root.entries[i])
|
if (i !== index) next.push(root.entries[i])
|
||||||
root.entries = next
|
root.entries = next
|
||||||
|
refreshTimer.restart()
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: historyProbe
|
id: historyProbe
|
||||||
command: ["bash", "-c", "cat " + root.historyDir + "/*.json 2>/dev/null || true"]
|
command: ["bash", "-c",
|
||||||
|
"for f in \"$1\"/*.json; do [[ -e $f ]] || continue; printf '%s\\t' \"${f##*/}\"; awk 1 \"$f\"; done",
|
||||||
|
"--", root.historyDir]
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: root.entries = HistoryModel.parseHistory(text)
|
onStreamFinished: root.entries = HistoryModel.parseHistory(text)
|
||||||
}
|
}
|
||||||
@@ -139,13 +143,13 @@ Item {
|
|||||||
spacing: Style.spaceReal(4)
|
spacing: Style.spaceReal(4)
|
||||||
|
|
||||||
CardIconButton {
|
CardIconButton {
|
||||||
icon: root.doNotDisturb ? "" : ""
|
icon: root.doNotDisturb ? "\uf1f6" : "\uf0f3"
|
||||||
active: root.doNotDisturb
|
active: root.doNotDisturb
|
||||||
onActivated: root.toggleSilence()
|
onActivated: root.toggleSilence()
|
||||||
}
|
}
|
||||||
|
|
||||||
CardIconButton {
|
CardIconButton {
|
||||||
icon: ""
|
icon: "\uf1f8"
|
||||||
onActivated: root.clearAll()
|
onActivated: root.clearAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,7 +181,7 @@ Item {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
text: ""
|
text: "\uf0a2"
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
color: Util.alpha(Color.blue, 0.6)
|
color: Util.alpha(Color.blue, 0.6)
|
||||||
font.family: Style.font.family
|
font.family: Style.font.family
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ Rectangle {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
text: ""
|
text: "\uf00d"
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
color: closeHover.hovered ? Color.urgent : Color.magenta
|
color: closeHover.hovered ? Color.urgent : Color.magenta
|
||||||
font.family: Style.font.family
|
font.family: Style.font.family
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ Item {
|
|||||||
readonly property string settingsPath: stateDir + "notifications.json"
|
readonly property string settingsPath: stateDir + "notifications.json"
|
||||||
// One file per on-screen popup, so live toasts survive shell restarts.
|
// One file per on-screen popup, so live toasts survive shell restarts.
|
||||||
// A file exists exactly as long as its popup is showing: written when the
|
// A file exists exactly as long as its popup is showing: written when the
|
||||||
// toast appears, moved into historyDir when it expires, is dismissed, or is
|
// toast appears, moved into historyDir when it expires unseen, and deleted
|
||||||
// acted upon.
|
// when it is dismissed or acted upon.
|
||||||
readonly property string popupStateDir: stateDir + "notifications/"
|
readonly property string popupStateDir: stateDir + "notifications/"
|
||||||
// The notifications that already left the screen, one file each, trimmed to
|
// The notifications that already left the screen, one file each, trimmed to
|
||||||
// the newest historyLimit. This directory IS the history: `showHistory`
|
// the newest historyLimit. This directory IS the history: `showHistory`
|
||||||
@@ -327,12 +327,14 @@ Item {
|
|||||||
// id would dismiss that unrelated notification at the server.
|
// id would dismiss that unrelated notification at the server.
|
||||||
var restored = isRestoredRow(entry)
|
var restored = isRestoredRow(entry)
|
||||||
var ref = !restored && originalId >= 0 ? liveRefs[originalId] : null
|
var ref = !restored && originalId >= 0 ? liveRefs[originalId] : null
|
||||||
// The popup is leaving the screen — for any reason — so its file must not
|
// The popup is leaving the screen, so its file must not survive to the
|
||||||
// survive to the next shell restart. It becomes the newest history entry
|
// next shell restart. An expired toast was missed and becomes the newest
|
||||||
// instead. Rows that never had a file (a history replay, the empty-history
|
// history entry; a dismissed or acted-on one was seen and is deleted.
|
||||||
// placeholder) archive to nothing, which the move tolerates.
|
// Rows that never had a file (a history replay, the empty-history
|
||||||
|
// placeholder) archive or delete nothing, which both jobs tolerate.
|
||||||
if (entry) {
|
if (entry) {
|
||||||
archivePopupFileFor(entry)
|
if (reason === "expire") archivePopupFileFor(entry)
|
||||||
|
else deletePopupFileFor(entry)
|
||||||
if (restored) delete restoredPopups[NotificationLogic.popupFileName(entry)]
|
if (restored) delete restoredPopups[NotificationLogic.popupFileName(entry)]
|
||||||
}
|
}
|
||||||
popupModel.remove(index)
|
popupModel.remove(index)
|
||||||
@@ -585,6 +587,15 @@ Item {
|
|||||||
enqueuePopupFileJob(command, done)
|
enqueuePopupFileJob(command, done)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeHistoryEntry(fileName) {
|
||||||
|
var name = String(fileName || "")
|
||||||
|
if (!/^[A-Za-z0-9._-]+\.json$/.test(name)) return false
|
||||||
|
enqueuePopupFileJob(["bash", "-c",
|
||||||
|
"rm -f \"$1/$2\" \"$3/${2%.json}\"-*", "--",
|
||||||
|
historyDir, name, imagesDir])
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
function clearHistory() {
|
function clearHistory() {
|
||||||
enqueuePopupFileJob(["bash", "-c",
|
enqueuePopupFileJob(["bash", "-c",
|
||||||
"for f in \"$1\"/*.json; do\n" +
|
"for f in \"$1\"/*.json; do\n" +
|
||||||
@@ -888,6 +899,10 @@ Item {
|
|||||||
return "ok"
|
return "ok"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function remove(fileName: string): string {
|
||||||
|
return service.removeHistoryEntry(fileName) ? "ok" : "none"
|
||||||
|
}
|
||||||
|
|
||||||
function dismissAll(): string {
|
function dismissAll(): string {
|
||||||
service.clearPopups()
|
service.clearPopups()
|
||||||
return "ok"
|
return "ok"
|
||||||
|
|||||||
Reference in new issue
Block a user