diff options
| -rw-r--r-- | mail-overview/README.md | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/mail-overview/README.md b/mail-overview/README.md new file mode 100644 index 0000000..daf2bf9 --- /dev/null +++ b/mail-overview/README.md @@ -0,0 +1,167 @@ +# mail-overview + +Unread mail across every account, as one waybar number and a drawer behind it. +Clicking the icon opens the drawer; Escape or a click outside closes it. + + ┌──────────────────────────────────────────────────┐ + │ Mail 33 unread │ + ├──────────────────────────────────────────────────┤ + │ ● Account A 2 │ + │ Some Sender Today 06:18 │ + │ [a-list] a subject line that is elided... │ + ├──────────────────────────────────────────────────┤ + │ ● Account B 0 │ + │ ● Account C 31 │ + │ ...three newest threads... │ + │ ● Account D 0 │ + │ ● Account E 0 │ + ├──────────────────────────────────────────────────┤ + │ [Sync now] [Open qtmaildir] │ + └──────────────────────────────────────────────────┘ + +## Running it + + qs -p . + +It is started from `autostart.lua` and reached over IPC, so the shell has to be +running for the waybar click to do anything: + + qs -p ~/Programming/GIT/quickshell/mail-overview ipc call mail toggle + +Write that path out in full in the real config. Waybar's `exec` and `on-click` +have no shell to expand `~`, and neither do Hyprland's Lua strings. + +## The accounts are not listed here + +`qtmaildir.conf` already has one `[account.<key>]` section per account, and +`<key>` is exactly the suffix of the notmuch tag `account-<key>`, with a short +`label` and a `color` alongside. The panel parses that file, in file order, so +adding an account to qtmaildir makes it appear here with no edit to any QML. +The file is watched, so that happens without a restart. + +The `color` is the account's own, from its config section. It marks the dot +beside each row and nothing else: per-account identity is not a palette, and +`Theme` still owns every other colour. + +**Parsing walks lines rather than matching one regex over the file.** The +obvious pattern for a section body, everything up to the next `[`, is wrong +here: several accounts have folders named like `[Gmail]/Bozze`, so the body +ended at that bracket, before the `label` line, and three of five accounts +quietly fell back to displaying their raw key. Nothing errored, because falling +back is a legitimate path for an account with no label. + +## Counting + +Every count is `tag:unread and tag:inbox`. Plain `tag:unread` also counts +archived-but-unread mail and mailing list traffic that was never in the inbox, +which for one account here was the difference between 32 and 41; the +inbox-scoped number is the one that means new mail worth looking at. + +**By the `account-*` tag, never by a `path:` glob.** qtmaildir itself scopes an +account with `path:"<maildir>/**"`, and copying that query would be wrong for +this panel. notmuch deduplicates by message id, so a message that arrived at +two of the configured addresses is one message with two file paths: a glob +counts it under both accounts, and the rows then sum to more than the total the +waybar icon shows. Measured here, five accounts summed to 102 against a global +total of 101. The tag is a property of the message, so it is singular and the +rows always sum to the header. The cost is that such a message appears under +only the account the `post-new` hook attributed it to, which is the right trade +for an overview whose headline figure is a single number. + +## notmuch fails two ways, and only one is detectable + +A query notmuch rejects prints nothing and exits 1: + + notmuch count 'tag:unread and (' + +A query Xapian merely misparses returns a plausible wrong number and exits 0: + + notmuch count 'tag:unread and ((' # 41, exit 0 + notmuch count 'tag:unread and tag:' # 3, exit 0 + +So the exit status is not the test, and neither is any check that a wrong +number could pass. Counts are validated as non-negative integers, which catches +the first case, where empty output would otherwise render as an empty inbox. +The second is not catchable; the defence against it is that the queries are +fixed strings and are never built from anything. + +An unknown count shows a dash, never a zero, for the same reason the VM panel +refuses to substitute libvirt's host-side figures: a number that is actually a +failure reads as a fact. + +## The waybar module + +A `custom` module in **continuous mode**: the script never exits, prints one +JSON line per database commit, and waybar redraws on each line. There is no +`interval`, and no daemon to supervise, because waybar owns the process. + + "custom/mail": { + "format": "<span font='18px'></span> {}", + "return-type": "json", + "exec": "~/Programming/GIT/quickshell/mail-overview/waybar-mail.sh", + "on-click": "qs -p ~/Programming/GIT/quickshell/mail-overview ipc call mail toggle", + "on-click-right": "~/bin/mailsync.sh", + "tooltip": false + } + +The count drops the moment mail is read in qtmaildir and rises the moment +mbsync commits, because both write the database and the watcher sees either. + +Two details in that loop are load-bearing. The watch is on the **xapian +directory**, not on a file inside it: a commit replaces files, and a watch held +on a filename dies with it. And a short sleep coalesces the several writes of +one commit, without which the module redraws three or four times per sync with +intermediate counts. + +The old setup this replaces ran three copies of a python script polling the +Gmail API with stored credentials, covered three of the five accounts, and +opened Thunderbird. Everything it fetched over the network was already in the +local index. + +## Geometry, and the one property that differs from the other panels + +The window is a fullscreen overlay with a dimmed backdrop and the drawer itself +anchored to its top right, which is the idiom `vm-manager` and `appearance` both +use and what gives click-outside-to-close and a focusable item for Escape. + +It differs from those two in one property: `exclusionMode` is `Normal`, not +`Ignore`. Waybar claims an exclusive zone at the top of this screen, so +respecting it places the overlay below the bar with no height constant here to +drift. Measured: waybar at `y=-540 h=42`, this overlay at `y=-498 h=1038`, +starting exactly where the bar ends. The backdrop therefore never dims waybar. + +**Key events reach a focused item, not a window.** Setting +`WlrLayershell.keyboardFocus` is necessary but not sufficient, and +`Keys.onEscapePressed` on a `PanelWindow` never fires, so the focus sits on an +inner `Item`. + +## Thread rows are read-only + +Each account shows its three newest unread threads as author, relative date and +subject. Clicking one launches qtmaildir and nothing more, because `qtmaildir` +accepts no command line arguments: there is no way to tell it which thread to +open. `startup_account` in its config is a static setting, not a flag, so +"open on this account" is unavailable for the same reason. Marking read from +here was rejected rather than deferred: it would write the database behind a +possibly running client. + +"Sync now" runs `~/bin/mailsync.sh`, which is already lock-protected against a +concurrent cron run, and the watcher picks up whatever it commits. + +## Theme and blur + +`Theme.qml` is the shared one: the palette comes from +`~/.cache/wal/udt-palette.qml` and is watched. It is a fourth identical copy, +which is a known loose end, not a palette to grow. + +Frosting is Hyprland's, matched on this window's namespace: + + hl.layer_rule({ + name = "blur-mail", + match = { namespace = "^(quickshell-mail)$" }, + blur = true, + xray = false, + ignore_alpha = 0.1, + }) + +Without the rule it still works, rendering flat translucent. |
