aboutsummaryrefslogtreecommitdiffstats
path: root/mail-overview/MailPanel.qml
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-13 14:02:05 +0200
committerDanilo M. <danix@danix.xyz>2026-09-13 14:02:05 +0200
commit9fc6ca0d9a9cdba9d5808951ce290e559a9e8554 (patch)
treefab4f7bb606b38efaf00e22937c3c5a0f5cc0f57 /mail-overview/MailPanel.qml
parent87cd101eb3c55d0ad7e8f2142fcce90865da7d39 (diff)
downloadquickshell-9fc6ca0d9a9cdba9d5808951ce290e559a9e8554.tar.gz
quickshell-9fc6ca0d9a9cdba9d5808951ce290e559a9e8554.zip
feat(mail-overview): show mail-watcher health in the drawer
The watcher already writes ~/.local/state/mail-watcher.heartbeat every 60s, so the drawer reads it and shows a dot: green when idling, yellow when a folder gave up, red when the heartbeat is stale or missing. Backoff stays green because it is normal recovery and the watcher's own health check treats it that way. The staleness rule is reimplemented in a few lines rather than shelling out to --status; the heartbeat file is the documented cheap-to-poll surface. Read once per open rather than watched: the heartbeat is written by atomic replace, so an inotify watch held on the old inode dies with it. "Sync now" is removed with its Process. The watcher triggers syncs, the cron tick is the backstop, and waybar's right-click still runs mailsync.sh.
Diffstat (limited to 'mail-overview/MailPanel.qml')
-rw-r--r--mail-overview/MailPanel.qml78
1 files changed, 72 insertions, 6 deletions
diff --git a/mail-overview/MailPanel.qml b/mail-overview/MailPanel.qml
index 7457fb2..0a7f36a 100644
--- a/mail-overview/MailPanel.qml
+++ b/mail-overview/MailPanel.qml
@@ -10,6 +10,7 @@
// GNU General Public License for more details.
import Quickshell
+import Quickshell.Io
import Quickshell.Wayland
import QtQuick
@@ -20,6 +21,45 @@ Scope {
property string monitor: "DP-1"
property bool open: false
+ // mail-watcher's heartbeat, written every 60s. Read once per open: the
+ // drawer is a LazyLoader, so closing and reopening rebuilds the FileView
+ // and reads the current file. Watch is deliberately not used, because the
+ // heartbeat is written by atomic replace (tmpfile + rename) and an inotify
+ // watch held on the old inode dies with it.
+ property bool watcherAlive: false
+ property int watcherDead: 0
+ property int watcherExpected: 0
+
+ // The same staleness rule as mail-watcher's heartbeat_is_healthy: dead
+ // threads or a heartbeat older than 300s mean the watcher needs a look.
+ // Backoff is healthy, so it never turns the dot.
+ function readHeartbeat(payload) {
+ root.watcherAlive = false;
+ root.watcherDead = 0;
+ root.watcherExpected = 0;
+
+ let data = null;
+ try { data = JSON.parse(payload); } catch (e) { return; }
+ if (!data || typeof data.ts !== "string") return;
+
+ const ts = Date.parse(data.ts);
+ if (isNaN(ts) || (Date.now() - ts) / 1000 > 300) return;
+
+ root.watcherAlive = true;
+ root.watcherDead = Number(data.dead) || 0;
+ root.watcherExpected = Number(data.expected) || 0;
+ }
+
+ readonly property color watcherColor:
+ !watcherAlive ? Theme.red
+ : watcherDead > 0 ? Theme.yellow
+ : Theme.green
+
+ readonly property string watcherText:
+ !watcherAlive ? "watcher not running"
+ : watcherDead > 0 ? `${watcherDead} folder(s) dead, check the log`
+ : `watcher ok · ${watcherExpected} folders`
+
readonly property var screenObj:
Quickshell.screens.find(s => s.name === root.monitor) ?? Quickshell.screens[0]
@@ -95,6 +135,13 @@ Scope {
// the drawer.
MouseArea { anchors.fill: parent }
+ FileView {
+ id: heartbeat
+ path: `${Quickshell.env("HOME")}/.local/state/mail-watcher.heartbeat`
+ onLoaded: root.readHeartbeat(text())
+ onLoadFailed: root.readHeartbeat("")
+ }
+
Column {
id: content
anchors { left: parent.left; right: parent.right; top: parent.top; margins: 16 }
@@ -230,17 +277,36 @@ Scope {
Rectangle { width: parent.width; height: 1; color: Qt.alpha(Theme.text, 0.12) }
+ // Watcher health, above the button. Green when idling,
+ // yellow when a folder gave up, red when there is no fresh
+ // heartbeat at all.
+ Item {
+ width: parent.width
+ implicitHeight: 22
+
+ Rectangle {
+ id: watcherDot
+ anchors.verticalCenter: parent.verticalCenter
+ width: 8; height: 8; radius: 4
+ color: root.watcherColor
+ }
+
+ Text {
+ anchors { left: watcherDot.right; leftMargin: 10; verticalCenter: parent.verticalCenter }
+ text: root.watcherText
+ color: Theme.subtext
+ font.family: Theme.fontFamily
+ font.pixelSize: Theme.fontSize - 2
+ }
+ }
+
+ Rectangle { width: parent.width; height: 1; color: Qt.alpha(Theme.text, 0.12) }
+
Row {
anchors.right: parent.right
spacing: 10
Button {
- text: Accounts.syncing ? "Syncing..." : "Sync now"
- enabled: !Accounts.syncing
- onClicked: Accounts.sync()
- }
-
- Button {
text: "Open qtmaildir"
onClicked: { Accounts.openClient(); root.close(); }
}