summaryrefslogtreecommitdiff
path: root/modules/wip.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-10-18 11:12:04 -0500
committerCraig Jennings <c@cjennings.net>2025-10-18 11:12:04 -0500
commit2c587787432d5172cbf0ecdb5b6864b2be26cfd5 (patch)
tree02d1584531811120bd9f229f654b643a7ce9d62d /modules/wip.el
parent5a91b6e644bcb62297a0cc0ab3b95d7244a85714 (diff)
feat: system commands: logout, reboot, restart emacs, etc.
Diffstat (limited to 'modules/wip.el')
-rw-r--r--modules/wip.el134
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 ---------------------------