diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-18 11:00:53 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-18 11:00:53 +0200 |
| commit | a880134140f7ab8195c6651c2f64f614855c8dce (patch) | |
| tree | d4cacc3777bb0bbef636c96894791661af456ce8 /desktop/modules/mail/mail-notify.sh | |
| parent | b2fd9aba5711a5863c8700c8b21c83c53e14b39d (diff) | |
| download | quickshell-a880134140f7ab8195c6651c2f64f614855c8dce.tar.gz quickshell-a880134140f7ab8195c6651c2f64f614855c8dce.zip | |
mail: open accounts and messages in qtmaildir from the drawer and notifications
qtmaildir now takes --account, --thread and --message, and hands them to
a running window over its socket, so the drawer and the notifications can
open the exact thing they show instead of just launching the client.
mail-open.sh is the one place that turns an account and a thread into
selectors, shared by both callers. It looks up the thread's newest unread
message at click time rather than when the row or notification was built:
by then it may have been read, and an empty lookup falls back to the whole
thread instead of a miss in qtmaildir's status bar.
The notification never had a working click: dunstify is not installed, so
mail-notify.sh always took its notify-send fallback, which carried no
action. It now uses notify-send -A NAME=Text, one action per listed thread
(keyed by thread id, which is hex and so never holds the "=" separator)
plus the account as default. The round trip through notifyd was checked:
notifyctl action makes notify-send print the picked name.
On the page, the account header opens the account and a thread row its
newest unread message. Rows use TapHandler and HoverHandler because a
MouseArea cannot fill a Column. Launches are detached: a first launch is
the client itself, and the old reused Process would have killed it on the
next click. Opening anything now closes the drawer rather than going back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/modules/mail/mail-notify.sh')
| -rwxr-xr-x | desktop/modules/mail/mail-notify.sh | 73 |
1 files changed, 45 insertions, 28 deletions
diff --git a/desktop/modules/mail/mail-notify.sh b/desktop/modules/mail/mail-notify.sh index 968799c..0052a8d 100755 --- a/desktop/modules/mail/mail-notify.sh +++ b/desktop/modules/mail/mail-notify.sh @@ -32,6 +32,7 @@ set -u STATE="${MAIL_NOTIFY_STATE:-$HOME/.local/state/mail-notify.lastmod}" CONFIG="${MAIL_NOTIFY_CONFIG:-$HOME/.config/qtmaildir/qtmaildir.conf}" SCOPE='tag:unread and tag:inbox' +OPEN="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/mail-open.sh" # How many threads a notification body lists before eliding into "+N more". # Three matches the drawer's own --limit=3. @@ -119,6 +120,25 @@ build_body() { printf '\n' } +# One action per listed thread, "<thread-id>=<label>" per line, +# followed by the account itself as the default action. The balloon shell +# offers them in a picker on click. +# +# The key is the thread id, not a Message-ID: thread ids are hex, so a key +# never carries an "=" or a quote, and mail-open.sh finds the message when +# the action is picked. The label is plain text for rofi, so no escaping, but +# it is flattened to one line and shortened, since it is a menu entry. +build_actions() { + local json="$1" label="$2" + + printf '%s' "$json" | jq -r ' + .[] | select(.thread | test("^[0-9a-f]+$")) + | .thread + "=" + ((.authors // "(unknown)") + " — " + (.subject // "(no subject)") + | gsub("[\r\n]+"; " ") | .[0:80]) + ' 2>/dev/null + printf 'default=Open %s\n' "$label" +} + # The last revision this script notified up to, or empty when there is none # to trust. Empty means "seed silently": record where we are now and notify # nothing. @@ -158,41 +178,38 @@ write_state() { # Sends one notification for one account. # -# dunstify rather than notify-send because actions need it. A stack tag per -# account means a second batch for the same account replaces the first rather -# than stacking, which is what "one notification per account" has to mean when -# mail keeps arriving. +# notify-send, whose -A takes NAME=Text and prints the picked NAME, which is +# all the actions need; dunstify is not installed here. NAME is split on the +# first "=", so a label may contain one. A stack tag per account means a +# second batch for the same account replaces the first rather than stacking, +# which is what "one notification per account" has to mean when mail keeps +# arriving. # # Normal urgency and an explicit 10s timeout, deliberately not -u critical: -# on most dunst configurations critical notifications never expire, which -# would leave mail popups on screen until clicked. +# critical notifications commonly never expire, which would leave mail popups +# on screen until clicked. # -# The click cannot open the account it belongs to. qtmaildir accepts no -# command line arguments and startup_account is a static config setting, not -# a flag, which is the same limitation the drawer's thread rows already have. +# The picked action opens qtmaildir through mail-open.sh: a thread's key opens +# its newest unread message, "default" the account. notify_account() { - local label="$1" key="$2" count="$3" body="$4" - - # -a carries "New Mail" because the user's dunst format renders %a as the - # bold heading line, with %s italic below it. - if ! command -v dunstify >/dev/null 2>&1; then - # No actions available, but a notification without a click is still - # worth having. - notify-send -a "New Mail" -u normal -t 10000 -i "$MAIL_ICON" \ - "$label ($count)" "$body" - return 0 - fi + local label="$1" key="$2" count="$3" body="$4" rows="$5" + local acts=() line picked + + while IFS= read -r line; do acts+=(-A "$line"); done < <(build_actions "$rows" "$label") - # Backgrounded because -b blocks until the notification is dismissed or - # clicked. Without this the loop would stall for the full timeout on + # Backgrounded because -A waits until the notification is dismissed or + # acted on. Without this the loop would stall for the full timeout on # every account, and a five-account batch would take most of a minute. + # -a carries "New Mail" because it renders as the bold heading line. ( - if [[ "$(dunstify -a "New Mail" -i "$MAIL_ICON" -u normal -t 10000 -b \ + picked="$(notify-send -a "New Mail" -i "$MAIL_ICON" -u normal -t 10000 \ -h "string:x-dunst-stack-tag:mail-$key" \ - -A "default,open" \ - "$label ($count)" "$body")" == "default" ]]; then - "$HOME/bin/qtmaildir" & - fi + "${acts[@]}" \ + "$label ($count)" "$body")" + case "$picked" in + default) "$OPEN" "$key" & ;; + [0-9a-f]*) "$OPEN" "$key" "$picked" & ;; + esac ) >/dev/null 2>&1 & } @@ -243,7 +260,7 @@ tick() { rows="$(notmuch search --format=json --limit="$ROWS" \ --sort=newest-first "$query" 2>/dev/null)" || rows="[]" - notify_account "$label" "$key" "$count" "$(build_body "$rows" "$count")" + notify_account "$label" "$key" "$count" "$(build_body "$rows" "$count")" "$rows" done < <(parse_accounts < "$CONFIG") # Written only after every account is done. A single account whose count |
