aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/custom-buffer-file.el6
-rw-r--r--modules/dwim-shell-config.el3
-rw-r--r--modules/mail-config.el34
-rw-r--r--modules/music-config.el5
-rw-r--r--modules/system-commands.el22
-rw-r--r--modules/system-defaults.el5
-rw-r--r--modules/system-lib.el11
7 files changed, 73 insertions, 13 deletions
diff --git a/modules/custom-buffer-file.el b/modules/custom-buffer-file.el
index 6c3e6c6e..25b4a418 100644
--- a/modules/custom-buffer-file.el
+++ b/modules/custom-buffer-file.el
@@ -48,6 +48,7 @@
;; mm-decode for email viewing (mm-handle-type is a macro, needs early require)
(require 'mm-decode)
(require 'external-open) ;; for cj/xdg-open, cj/open-this-file-with
+(require 'system-lib) ;; cj/confirm-strong (overwrite confirms), used below
;; cj/kill-buffer-and-window and cj/kill-other-window-buffer defined in undead-buffers.el
(declare-function cj/kill-buffer-and-window "undead-buffers")
@@ -156,7 +157,7 @@ When called interactively, prompts for confirmation if target file exists."
(condition-case _
(cj/--move-buffer-and-file dir nil)
(file-already-exists
- (if (yes-or-no-p (format "File %s exists; overwrite? " target))
+ (if (cj/confirm-strong (format "File %s exists; overwrite? " target))
(cj/--move-buffer-and-file dir t)
(message "File not moved"))))))
@@ -196,7 +197,7 @@ When called interactively, prompts for confirmation if target file exists."
(condition-case err
(cj/--rename-buffer-and-file new-name nil)
(file-already-exists
- (if (yes-or-no-p (format "File %s exists; overwrite? " new-name))
+ (if (cj/confirm-strong (format "File %s exists; overwrite? " new-name))
(cj/--rename-buffer-and-file new-name t)
(message "File not renamed")))
(error
@@ -338,7 +339,6 @@ Do not save the deleted text in the kill ring."
(kill-new (buffer-name))
(message "Copied: %s" (buffer-name)))
-(require 'system-lib)
(declare-function ansi-color-apply-on-region "ansi-color")
(defun cj/--diff-with-difftastic (file1 file2 buffer)
diff --git a/modules/dwim-shell-config.el b/modules/dwim-shell-config.el
index 57eea706..655c8d85 100644
--- a/modules/dwim-shell-config.el
+++ b/modules/dwim-shell-config.el
@@ -98,6 +98,7 @@
;;; Code:
(require 'cl-lib)
+(require 'system-lib) ;; cj/confirm-strong (permanent file destruction confirm)
;; --------------------------- Password-file helpers ---------------------------
@@ -801,7 +802,7 @@ switching off the .7z format to gpg-wrapped tar."
Uses =shred -u= so the file is unlinked after overwriting, matching the
\"delete\" the command name and prompt promise."
(interactive)
- (when (yes-or-no-p "This will permanently destroy files. Continue? ")
+ (when (cj/confirm-strong "This will permanently destroy files. Continue? ")
(dwim-shell-command-on-marked-files
"Secure delete"
"shred -vfzu -n 3 '<<f>>'"
diff --git a/modules/mail-config.el b/modules/mail-config.el
index f71d6eeb..dfc0c4e0 100644
--- a/modules/mail-config.el
+++ b/modules/mail-config.el
@@ -48,6 +48,31 @@
(defvar message-send-mail-function nil)
(defvar message-sendmail-envelope-from nil)
+(declare-function mu4e-message-field "mu4e-message")
+
+;; Refile (archive) target dispatch. A per-context `mu4e-refile-folder' string
+;; is unsafe: mu4e context :vars are sticky, so a value set when one context is
+;; active leaks into a later context that doesn't set its own -- archiving one
+;; account's mail into another's folder. A single function evaluated per
+;; message at refile time avoids that. Only cmail has a real synced Archive
+;; folder; the Gmail-backed accounts (gmail, dmail) sync no archive maildir, so
+;; refiling them would move mail into an unsynced, server-invisible folder
+;; (silent loss) -- signal instead.
+(defun cj/mu4e--refile-folder-for-maildir (maildir)
+ "Return the refile (archive) folder for MAILDIR, or signal when none exists.
+MAILDIR is a mu4e :maildir string such as \"/cmail/INBOX\"."
+ (cond
+ ((not (stringp maildir))
+ (user-error "Cannot refile: message has no maildir"))
+ ((string-prefix-p "/cmail" maildir) "/cmail/Archive")
+ (t
+ (user-error "No archive folder syncs for this account; refile disabled to avoid moving mail into an unsynced folder"))))
+
+(defun cj/mu4e--refile-folder (msg)
+ "Refile-folder function for `mu4e-refile-folder'.
+Dispatch on MSG's maildir via `cj/mu4e--refile-folder-for-maildir'."
+ (cj/mu4e--refile-folder-for-maildir (and msg (mu4e-message-field msg :maildir))))
+
(defcustom cj/smtpmail-debug-enabled nil
"Non-nil means enable verbose SMTP transport debug logging.
@@ -217,7 +242,8 @@ Prompts user for the action when executing."
:vars '((user-mail-address . "c@cjennings.net")
(user-full-name . "Craig Jennings")
(mu4e-drafts-folder . "/cmail/Drafts")
- (mu4e-sent-folder . "/cmail/Sent")))
+ (mu4e-sent-folder . "/cmail/Sent")
+ (mu4e-trash-folder . "/cmail/Trash")))
(make-mu4e-context
:name "deepsat.com"
@@ -232,6 +258,12 @@ Prompts user for the action when executing."
(mu4e-starred-folder . "/dmail/Starred")
(mu4e-trash-folder . "/dmail/Trash")))))
+ ;; Refile target is computed per message (see `cj/mu4e--refile-folder'), not
+ ;; set per context, because mu4e context :vars are sticky and would leak one
+ ;; account's archive folder into another. cmail archives to /cmail/Archive;
+ ;; gmail/dmail signal rather than move mail into an unsynced folder.
+ (setq mu4e-refile-folder #'cj/mu4e--refile-folder)
+
(setq mu4e-maildir-shortcuts
'(("/cmail/Inbox" . ?i)
("/cmail/Sent" . ?s)
diff --git a/modules/music-config.el b/modules/music-config.el
index fd619d8c..799db133 100644
--- a/modules/music-config.el
+++ b/modules/music-config.el
@@ -95,6 +95,7 @@
(require 'user-constants)
(require 'keybindings) ;; provides cj/custom-keymap
(require 'cj-window-toggle-lib) ;; side-window size memory (F10 toggle)
+(require 'system-lib) ;; cj/confirm-strong (overwrite confirms)
;;; Settings (no Customize)
@@ -371,7 +372,7 @@ Offers completion over existing names but allows new names."
(filename (if (string-suffix-p ".m3u" chosen) chosen (concat chosen ".m3u")))
(full (expand-file-name filename cj/music-m3u-root)))
(when (and (file-exists-p full)
- (not (yes-or-no-p (format "Overwrite %s? " filename))))
+ (not (cj/confirm-strong (format "Overwrite %s? " filename))))
(user-error "Aborted saving playlist"))
(with-current-buffer (cj/music--ensure-playlist-buffer)
(let ((emms-source-playlist-ask-before-overwrite nil))
@@ -924,7 +925,7 @@ For URL tracks: decoded URL."
(file (expand-file-name (concat safe "_Radio.m3u") cj/music-m3u-root))
(content (format "#EXTM3U\n#EXTINF:-1,%s\n%s\n" name url)))
(when (and (file-exists-p file)
- (not (yes-or-no-p (format "Overwrite %s? " (file-name-nondirectory file)))))
+ (not (cj/confirm-strong (format "Overwrite %s? " (file-name-nondirectory file)))))
(user-error "Aborted creating radio station"))
(with-temp-file file
(insert content))
diff --git a/modules/system-commands.el b/modules/system-commands.el
index dba4d40e..44ac3ae8 100644
--- a/modules/system-commands.el
+++ b/modules/system-commands.el
@@ -9,7 +9,7 @@
;; Eager reason: registers the C-; ! system-command keymap; high-impact commands
;; that should run only by command (command-loaded target).
;; Top-level side effects: defines a system-command keymap under cj/custom-keymap.
-;; Runtime requires: keybindings, rx.
+;; Runtime requires: keybindings, host-environment, rx.
;; Direct test load: yes (requires keybindings explicitly).
;;
;; System commands for logout, lock, suspend, shutdown, reboot, and Emacs
@@ -17,7 +17,7 @@
;;
;; Commands include:
;; - Logout (terminate user session)
-;; - Lock screen (slock)
+;; - Lock screen (hyprlock on Wayland, slock on X11)
;; - Suspend (systemctl suspend)
;; - Shutdown (systemctl poweroff)
;; - Reboot (systemctl reboot)
@@ -34,6 +34,14 @@
;; the load-time reference void if anything required `system-commands'
;; before `keybindings'. Make the dependency explicit.
(require 'keybindings)
+;; `host-environment' provides `env-wayland-p', referenced at load time by the
+;; `lockscreen-cmd' defvar below to pick the session-appropriate locker. A hard
+;; require keeps the module loadable on its own (tests, byte-compile) rather
+;; than relying on init.el's load order.
+(require 'host-environment)
+;; `system-lib' provides `cj/confirm-strong', used at runtime by the `strong'
+;; confirm branch of `cj/system-cmd' for irreversible actions (shutdown/reboot).
+(require 'system-lib)
(eval-when-compile (require 'subr-x))
(require 'rx)
@@ -71,7 +79,7 @@ If CMD is deemed dangerous, ask for confirmation."
;; Strong confirm for irreversible actions (shutdown, reboot):
;; require an explicit "yes", so a stray RET/space can't trigger them.
((eq confirm 'strong)
- (unless (yes-or-no-p (format "Really run %s (%s)? " label cmdstr))
+ (unless (cj/confirm-strong (format "Really run %s (%s)? " label cmdstr))
(user-error "Aborted")))
;; Quick (Y/n) confirm for recoverable actions (logout, suspend).
(confirm
@@ -102,7 +110,13 @@ actions like shutdown and reboot), nil for no confirmation."
;; Define system commands
(cj/defsystem-command cj/system-cmd-logout logout-cmd "loginctl terminate-user $(whoami)" t)
-(cj/defsystem-command cj/system-cmd-lock lockscreen-cmd "slock")
+;; slock is X11-only and can't grab a Wayland session. On Wayland, lock via
+;; the session manager (`loginctl lock-session') rather than spawning a locker
+;; directly: logind emits the Lock signal, hypridle catches it and runs its
+;; lock_cmd (hyprlock), the same path idle/before-sleep locking already uses.
+;; X11 machines keep slock.
+(cj/defsystem-command cj/system-cmd-lock lockscreen-cmd
+ (if (env-wayland-p) "loginctl lock-session" "slock"))
(cj/defsystem-command cj/system-cmd-suspend suspend-cmd "systemctl suspend" t)
(cj/defsystem-command cj/system-cmd-shutdown shutdown-cmd "systemctl poweroff" strong)
(cj/defsystem-command cj/system-cmd-reboot reboot-cmd "systemctl reboot" strong)
diff --git a/modules/system-defaults.el b/modules/system-defaults.el
index eccc6c35..1703b1bf 100644
--- a/modules/system-defaults.el
+++ b/modules/system-defaults.el
@@ -200,8 +200,9 @@ appears only once per session."
(setq confirm-nonexistent-file-or-buffer nil) ;; don't ask if a file I visit with C-x C-f or C-x b doesn't exist
(setq ad-redefinition-action 'accept) ;; silence warnings about advised functions getting redefined.
(setq large-file-warning-threshold nil) ;; open files regardless of size
-(fset 'yes-or-no-p 'y-or-n-p) ;; require a single letter for binary answers
-(setq use-short-answers t) ;; same as above with Emacs 28+
+(setq use-short-answers t) ;; single-key y/n for ordinary yes-or-no-p prompts
+ ;; (irreversible actions use `cj/confirm-strong', which
+ ;; forces a typed "yes" by binding this nil for that call)
(setq auto-revert-verbose nil) ;; turn off auto revert messages
(setq custom-safe-themes t) ;; treat all themes as safe (stop asking)
(setq server-client-instructions nil) ;; I already know what to do when done with the frame
diff --git a/modules/system-lib.el b/modules/system-lib.el
index 333c15ee..9e25be5b 100644
--- a/modules/system-lib.el
+++ b/modules/system-lib.el
@@ -130,5 +130,16 @@ Callers that must have a secret layer their own error on top."
(secret (plist-get (car (apply #'auth-source-search spec)) :secret)))
(if (functionp secret) (funcall secret) secret)))
+;; ---------------------------- Strong Confirmation ----------------------------
+
+(defun cj/confirm-strong (prompt)
+ "Ask PROMPT, requiring a full typed \"yes\" or \"no\" answer.
+For irreversible actions -- file destruction, overwrites, power-off. The
+global default makes `yes-or-no-p' a single keystroke (`use-short-answers'
+is t); this binds it to nil for the one call so the prompt demands the
+long-form answer, keeping a stray RET or space from confirming."
+ (let ((use-short-answers nil))
+ (yes-or-no-p prompt)))
+
(provide 'system-lib)
;;; system-lib.el ends here