diff options
| author | Craig Jennings <c@cjennings.net> | 2025-10-18 11:12:04 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-10-18 11:12:04 -0500 |
| commit | 2c587787432d5172cbf0ecdb5b6864b2be26cfd5 (patch) | |
| tree | 02d1584531811120bd9f229f654b643a7ce9d62d /modules | |
| parent | 5a91b6e644bcb62297a0cc0ab3b95d7244a85714 (diff) | |
feat: system commands: logout, reboot, restart emacs, etc.
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/wip.el | 134 |
1 files changed, 124 insertions, 10 deletions
diff --git a/modules/wip.el b/modules/wip.el index 6c7c8845..b55774b0 100644 --- a/modules/wip.el +++ b/modules/wip.el @@ -1,18 +1,132 @@ ;;; wip.el --- test code -*- lexical-binding: t; coding: utf-8; -*- ;; author: Craig Jennings <c@cjennings.net> - +;; ;;; Commentary: - -;; This is where to put config code you're working on before it's tested and stable. -;; Include this at the very end of your init.el. This way, if something does break, -;; and it will, most of your Emacs config is loaded. - -;; Once you've tested (and time-tested) the code here, graduate it into the proper -;; section of your config above. - +;; +;; Work-in-progress and experimental code testing area. This file contains +;; code that is being actively developed or tested before being promoted +;; to stable configuration modules. Functions here may be incomplete, +;; buggy, or subject to significant changes. Include this file at the end +;; of init.el so that if something breaks, most of the Emacs config has +;; already loaded. Once code has been tested and proven stable, graduate +;; it into the appropriate configuration module. Do not rely on this code +;; for production use. +;; ;;; Code: -(require 'user-constants) +(eval-when-compile (require 'user-constants)) +(eval-when-compile (require 'keybindings)) +(eval-when-compile (require 'subr-x)) ;; for system commands +(require 'rx) ;; for system commands + +;; ------------------------------ 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")))) + +;;;###autoload +(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 camdstr) + '(?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) ;; --------------------------- Org Upcoming Modeline --------------------------- |
