aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-11 12:07:04 -0500
committerCraig Jennings <c@cjennings.net>2026-06-11 12:07:04 -0500
commit9afc61288d0de7c7e2649a2730c57e642ac77c01 (patch)
tree31a64022677aaf61686f97e6b366bd36a838dd28 /modules
parent4003f40ca129f434f85f14e5dfe13655c4e15258 (diff)
downloaddotemacs-9afc61288d0de7c7e2649a2730c57e642ac77c01.tar.gz
dotemacs-9afc61288d0de7c7e2649a2730c57e642ac77c01.zip
feat(signal): route message toasts through the notify script
Incoming messages now notify through cj/signel--notify, installed as the fork's signel-notify-function. It suppresses the toast while that chat is in the selected window of a focused frame, collapses and truncates the body to 120 characters, and sends through the notify script (info type, --silent unless cj/signel-notify-sound is set). Without the script on PATH it falls back to notifications-notify and warns at load. The decisions are in the Notification slice addendum of docs/design/signal-client.org.
Diffstat (limited to 'modules')
-rw-r--r--modules/signal-config.el51
1 files changed, 50 insertions, 1 deletions
diff --git a/modules/signal-config.el b/modules/signal-config.el
index 317e3520..7e980b62 100644
--- a/modules/signal-config.el
+++ b/modules/signal-config.el
@@ -17,6 +17,9 @@
(require 'seq)
(require 'keybindings) ;; provides cj/custom-keymap + cj/register-prefix-map
+(require 'system-lib) ;; for cj/executable-find-or-warn
+
+(declare-function notifications-notify "notifications")
(defun cj/signal--jstr (value)
"Return VALUE if it is a non-blank string, else nil.
@@ -102,6 +105,46 @@ window of a focused frame."
(buffer-name (window-buffer (selected-window)))
(cj/signal--frame-focused-p))))
+;;; Notifications
+
+(defcustom cj/signel-notify-sound nil
+ "When non-nil, incoming-message notifications play the notify script's sound.
+Nil (the default) passes --silent so the toast is visual only."
+ :type 'boolean
+ :group 'signel)
+
+(defconst cj/signal--notify-body-max 120
+ "Maximum character length of a desktop-notification body.
+Longer message text truncates to this length ending in an ellipsis;
+the full text is always in the chat buffer.")
+
+(defun cj/signal--format-notify-body (text)
+ "Collapse whitespace in TEXT and truncate it for a notification body.
+Whitespace runs (including newlines) become single spaces, the result
+is trimmed, and anything over `cj/signal--notify-body-max' characters
+truncates to that length with a trailing ellipsis."
+ (let ((flat (string-trim (replace-regexp-in-string "[ \t\n\r]+" " " text))))
+ (if (<= (length flat) cj/signal--notify-body-max)
+ flat
+ (concat (substring flat 0 (1- cj/signal--notify-body-max)) "…"))))
+
+(defun cj/signel--notify (chat-id sender body)
+ "Raise a desktop notification for an incoming Signal message.
+Suppressed via `cj/signal--should-notify-p' when the user is actively
+viewing CHAT-ID. Routes through the external notify script when it is
+on PATH (type info, sound gated by `cj/signel-notify-sound'), falling
+back to `notifications-notify' otherwise. SENDER names the title;
+BODY is formatted by `cj/signal--format-notify-body'. Installed as
+`signel-notify-function' in the use-package :config below."
+ (when (cj/signal--should-notify-p chat-id)
+ (let ((title (format "Signal: %s" sender))
+ (text (cj/signal--format-notify-body body))
+ (script (executable-find "notify")))
+ (if script
+ (apply #'start-process "signel-notify" nil script "info" title text
+ (unless cj/signel-notify-sound (list "--silent")))
+ (notifications-notify :title title :body text)))))
+
;;; signel — fork integration
(defcustom cj/signal-private-config-file
@@ -126,7 +169,13 @@ time."
(signel-auto-open-buffer nil)
:config
(when (file-readable-p cj/signal-private-config-file)
- (load cj/signal-private-config-file nil t)))
+ (load cj/signal-private-config-file nil t))
+ ;; Route incoming-message notifications through cj/signel--notify
+ ;; (suppression + notify script + truncation); warn once at load when
+ ;; the script is missing — the runtime path still falls back to
+ ;; notifications-notify, so messages are never silently dropped.
+ (setq signel-notify-function #'cj/signel--notify)
+ (cj/executable-find-or-warn "notify" "Signal desktop notifications via the notify script (falling back to notifications-notify)" 'signal-config))
;; Chat buffers (named `*Signel: <id>*') open in the bottom 30% of the
;; frame rather than wherever display-buffer's fallback rule picks.