diff options
| author | Craig Jennings <c@cjennings.net> | 2025-10-27 21:05:06 -0500 | 
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-10-27 21:05:06 -0500 | 
| commit | d77ca19cf7106a0eecbff1588c13b8b52b98b85f (patch) | |
| tree | eea65ea265d4a11a9c4888357f09f5b471737a45 /modules/system-commands.el | |
| parent | a8a6bb0b6a49ba83903c84ad0790585c6e40f2b8 (diff) | |
refactor: Rename custom-file-buffer to custom-buffer-file
Renamed the module 'custom-file-buffer' to 'custom-buffer-file' to
ensure consistency across the codebase. This change affects module
imports and test files. Additionally, new module
'system-commands.el' has been created to handle system power and
session management commands, removing these functionalities from
'wip.el'.
Diffstat (limited to 'modules/system-commands.el')
| -rw-r--r-- | modules/system-commands.el | 138 | 
1 files changed, 138 insertions, 0 deletions
| diff --git a/modules/system-commands.el b/modules/system-commands.el new file mode 100644 index 00000000..fb8c0611 --- /dev/null +++ b/modules/system-commands.el @@ -0,0 +1,138 @@ +;;; system-commands.el --- System power and session management -*- lexical-binding: t; coding: utf-8; -*- +;; author: Craig Jennings <c@cjennings.net> +;; +;;; Commentary: +;; +;; System commands for logout, lock, suspend, shutdown, reboot, and Emacs +;; exit/restart. Provides both a keymap (C-; !) and a completing-read menu. +;; +;; Commands include: +;; - Logout (terminate user session) +;; - Lock screen (slock) +;; - Suspend (systemctl suspend) +;; - Shutdown (systemctl poweroff) +;; - Reboot (systemctl reboot) +;; - Exit Emacs (kill-emacs) +;; - Restart Emacs (via systemctl --user restart emacs.service) +;; +;; Dangerous commands (logout, suspend, shutdown, reboot) require confirmation. +;; +;;; Code: + +(eval-when-compile (require 'keybindings)) +(eval-when-compile (require 'subr-x)) +(require 'rx) + +;; ------------------------------ System Commands ------------------------------ + +(defun cj/system-cmd--resolve (cmd) +  "Return (values symbol-or-nil command-string label) for CMD." +  (cond +   ((symbolp cmd) +    (let ((val (and (boundp cmd) (symbol-value cmd)))) +      (unless (and (stringp val) (not (string-empty-p val))) +        (user-error "Variable %s is not a non-empty string" cmd)) +      (list cmd val (symbol-name cmd)))) +   ((stringp cmd) +    (let ((s (string-trim cmd))) +      (when (string-empty-p s) (user-error "Command string is empty")) +      (list nil s "command"))) +   (t (user-error "Error: cj/system-cmd expects a string or a symbol")))) + +(defun cj/system-cmd (cmd) +  "Run CMD (string or symbol naming a string) detached via the shell. +Shell expansions like $(...) are supported. Output is silenced. +If CMD is deemed dangerous, ask for confirmation." +  (interactive (list (read-shell-command "System command: "))) +  (pcase-let ((`(,sym ,cmdstr ,label) (cj/system-cmd--resolve cmd))) +    (when (and sym (get sym 'cj/system-confirm) +               (memq (read-char-choice +                      (format "Run %s now (%s)? (Y/n) " label cmdstr) +                      '(?y ?Y ?n ?N ?\r ?\n ?\s)) +                     '(?n ?N))) +      (user-error "Aborted")) +    (let ((proc (start-process-shell-command "cj/system-cmd" nil +                                             (format "nohup %s >/dev/null 2>&1 &" cmdstr)))) +      (set-process-query-on-exit-flag proc nil) +      (set-process-sentinel proc #'ignore) +      (message "Running %s..." label)))) + +(defmacro cj/defsystem-command (name var cmdstr &optional confirm) +  "Define VAR with CMDSTR and interactive command NAME to run it. +If CONFIRM is non-nil, mark VAR to always require confirmation." +  (declare (indent defun)) +  `(progn +     (defvar ,var ,cmdstr) +     ,(when confirm `(put ',var 'cj/system-confirm t)) +     (defun ,name () +       ,(format "Run %s via `cj/system-cmd'." var) +       (interactive) +       (cj/system-cmd ',var)))) + +;; 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") +(cj/defsystem-command cj/system-cmd-suspend  suspend-cmd "systemctl suspend" t) +(cj/defsystem-command cj/system-cmd-shutdown shutdown-cmd "systemctl poweroff" t) +(cj/defsystem-command cj/system-cmd-reboot   reboot-cmd "systemctl reboot" t) + +(defun cj/system-cmd-exit-emacs () +  "Exit Emacs server and all clients." +  (interactive) +  (when (memq (read-char-choice +               "Exit Emacs? (Y/n) " +               '(?y ?Y ?n ?N ?\r ?\n ?\s)) +              '(?n ?N)) +    (user-error "Aborted")) +  (kill-emacs)) + +(defun cj/system-cmd-restart-emacs () +  "Restart Emacs server after saving buffers." +  (interactive) +  (when (memq (read-char-choice +               "Restart Emacs? (Y/n) " +               '(?y ?Y ?n ?N ?\r ?\n ?\s)) +              '(?n ?N)) +    (user-error "Aborted")) +  (save-some-buffers) +  ;; Start the restart process before killing Emacs +  (run-at-time 0.5 nil +               (lambda () +                 (call-process-shell-command +                  "systemctl --user restart emacs.service && emacsclient -c" +                  nil 0))) +  (run-at-time 1 nil #'kill-emacs) +  (message "Restarting Emacs...")) + +(defvar-keymap cj/system-command-map +  :doc "Keymap for system commands." +  "L" #'cj/system-cmd-logout +  "r" #'cj/system-cmd-reboot +  "s" #'cj/system-cmd-shutdown +  "S" #'cj/system-cmd-suspend +  "l" #'cj/system-cmd-lock +  "E" #'cj/system-cmd-exit-emacs +  "e" #'cj/system-cmd-restart-emacs) +(keymap-set cj/custom-keymap "!" cj/system-command-map) + +(defun cj/system-command-menu () +  "Present system commands via \='completing-read\='." +  (interactive) +  (let* ((commands '(("Logout System"   . cj/system-cmd-logout) +                     ("Lock Screen"     . cj/system-cmd-lock) +                     ("Suspend System"  . cj/system-cmd-suspend) +                     ("Shutdown System" . cj/system-cmd-shutdown) +                     ("Reboot System"   . cj/system-cmd-reboot) +                     ("Exit Emacs"      . cj/system-cmd-exit-emacs) +                     ("Restart Emacs"   . cj/system-cmd-restart-emacs))) +         (choice (completing-read "System command: " commands nil t))) +    (when-let ((cmd (alist-get choice commands nil nil #'equal))) +      (call-interactively cmd)))) + +(keymap-set cj/custom-keymap "!" #'cj/system-command-menu) + +(with-eval-after-load 'which-key +  (which-key-add-key-based-replacements "C-; !" "system commands")) + +(provide 'system-commands) +;;; system-commands.el ends here | 
