Subject: 29.0.50; [PATCH] Make 'server-stop-automatically' into a defcustom

From: Jim Porter <[email-masked]>
Date: Mon, 28 Nov 2022 20:23:17 -0800

The manual says:

   The Emacs server can optionally be stopped automatically when
   certain conditions are met.  To do this, call the function
   @code{server-stop-automatically} in your init file (@pxref{Init
   File}), with one of the following arguments...

It'd be nice if this were a defcustom so that people who prefer the 
Customize interface could use that instead of editing their init files. 
Here's a patch for that.

I've tried to make sure this is as robust as possible, so that 
everything is always properly set, especially when adjusting the value 
in the Customize UI. It should even work correctly if a user stopped the 
server temporarily and restarted it (you might do this to clear out old 
clients, do some work, and then make the server available again). Maybe 
this last bit is a little paranoid, but it was minimal extra work 
compared to getting the Customize part working.

One question though: should this only go on the master branch, or should 
it go into the 29 branch? To me, it seems like it could go either way, 
though I think it'd be nice to make this easier for users in 29. I'll do 
whatever the maintainers think is best though.

If it goes on the master branch only, I'll add back the function form of 
'server-stop-automatically' for compatibility, and then mark it obsolete.

From d1273ad3170514bc28460b32886a6b4d7615742b Mon Sep 17 00:00:00 2001
From: Jim Porter <[email-masked]>
Date: Sun, 27 Nov 2022 22:21:10 -0800
Subject: [PATCH] Make 'server-stop-automatically' into a defcustom

* lisp/server.el (server-stop-automatically): Convert the variable to
a defcustom.  Remove the function.
(server-stop-automatically--timer): New variable.
(server-apply-stop-automatically): New function.
(server-stop, server-start): Call 'server-apply-stop-automatically'.
(server-save-buffers-kill-terminal): Adjust
'server-stop-automatically' conditional.
(server-stop-automatically--handle-delete-frame)
(server-stop-automatically--maybe-kill-emacs): Update docstrings.

* doc/emacs/misc.texi (Emacs Server): Update documentation.
---
 doc/emacs/misc.texi | 13 +++----
 lisp/server.el      | 89 +++++++++++++++++++++++++++++----------------
 2 files changed, 63 insertions(+), 39 deletions(-)

diff --git a/doc/emacs/misc.texi b/doc/emacs/misc.texi
index 29c0bed19c..e3c013acb1 100644
--- a/doc/emacs/misc.texi
+++ b/doc/emacs/misc.texi
@@ -1808,26 +1808,25 @@ Emacs Server
   emacs --daemon=foo
 @end example
 
-@findex server-stop-automatically
+@vindex server-stop-automatically
   The Emacs server can optionally be stopped automatically when
-certain conditions are met.  To do this, call the function
-@code{server-stop-automatically} in your init file (@pxref{Init
-File}), with one of the following arguments:
+certain conditions are met.  To do this, set the option
+@code{server-stop-automatically} to one of the following values:
 
 @itemize
 @item
-With the argument @code{empty}, the server is stopped when it has no
+With the value @code{empty}, the server is stopped when it has no
 clients, no unsaved file-visiting buffers and no running processes
 anymore.
 
 @item
-With the argument @code{delete-frame}, when the last client frame is
+With the value @code{delete-frame}, when the last client frame is
 being closed, you are asked whether each unsaved file-visiting buffer
 must be saved and each unfinished process can be stopped, and if so,
 the server is stopped.
 
 @item
-With the argument @code{kill-terminal}, when the last client frame is
+With the value @code{kill-terminal}, when the last client frame is
 being closed with @kbd{C-x C-c} (@code{save-buffers-kill-terminal}),
 you are asked whether each unsaved file-visiting buffer must be saved
 and each unfinished process can be stopped, and if so, the server is
diff --git a/lisp/server.el b/lisp/server.el
index 1b027f88ce..dac07dfa35 100644
--- a/lisp/server.el
+++ b/lisp/server.el
@@ -273,6 +273,11 @@ server-client-instructions
   :version "28.1"
   :type 'boolean)
 
+(defvar server-stop-automatically)      ; Defined below to avoid recursive load.
+
+(defvar server-stop-automatically--timer nil
+  "The timer object for `server-stop-automatically--maybe-kill-emacs'.")
+
 ;; We do not use `temporary-file-directory' here, because emacsclient
 ;; does not read the init file.
 (defvar server-socket-dir
@@ -636,7 +641,8 @@ server-stop
       (setq stopped-p t
             server-process nil
             server-mode nil
-            global-minor-modes (delq 'server-mode global-minor-modes)))
+            global-minor-modes (delq 'server-mode global-minor-modes))
+      (server-apply-stop-automatically))
     (unwind-protect
         ;; Delete the socket files made by previous server
         ;; invocations.
@@ -757,6 +763,7 @@ server-start
 			 (list :family 'local
 			       :service server-file
 			       :plist '(:authenticated t)))))
+          (server-apply-stop-automatically)
 	  (unless server-process (error "Could not start server process"))
           (server-log "Started server")
 	  (process-put server-process :server-file server-file)
@@ -1769,9 +1776,6 @@ server-switch-buffer
     (when server-raise-frame
       (select-frame-set-input-focus (window-frame)))))
 
-(defvar server-stop-automatically nil
-  "Internal status variable for `server-stop-automatically'.")
-
 ;;;###autoload
 (defun server-save-buffers-kill-terminal (arg)
   ;; Called from save-buffers-kill-terminal in files.el.
@@ -1780,7 +1784,8 @@ server-save-buffers-kill-terminal
 
 If emacsclient was started with a list of filenames to edit, then
 only these files will be asked to be saved."
-  (if server-stop-automatically
+  (if (and (daemonp)
+           (memq server-stop-automatically '(kill-terminal delete-frame))
       (server-stop-automatically--handle-delete-frame (selected-frame))
     (let ((proc (frame-parameter nil 'client)))
       (cond ((eq proc 'nowait)
@@ -1805,7 +1810,7 @@ server-save-buffers-kill-terminal
 	    (t (error "Invalid client frame"))))))
 
 (defun server-stop-automatically--handle-delete-frame (frame)
-  "Handle deletion of FRAME when `server-stop-automatically' is used."
+  "Handle deletion of FRAME when `server-stop-automatically' is `delete-frame'."
   (when server-stop-automatically
     (if (if (and (processp (frame-parameter frame 'client))
 		 (eq this-command 'save-buffers-kill-terminal))
@@ -1828,7 +1833,7 @@ server-stop-automatically--handle-delete-frame
 	  (delete-frame frame)))))
 
 (defun server-stop-automatically--maybe-kill-emacs ()
-  "Handle closing of Emacs daemon when `server-stop-automatically' is used."
+  "Handle closing of Emacs daemon when `server-stop-automatically' is `empty'."
   (unless (cdr (frame-list))
     (when (and
 	   (not (memq t (mapcar (lambda (b)
@@ -1842,41 +1847,61 @@ server-stop-automatically--maybe-kill-emacs
 				(process-list)))))
       (kill-emacs))))
 
-;;;###autoload
-(defun server-stop-automatically (arg)
-  "Automatically stop server as specified by ARG.
-
-If ARG is the symbol `empty', stop the server when it has no
+(defun server-apply-stop-automatically ()
+  "Apply the current value of `server-stop-automatically'.
+This function adds or removes the necessary helpers to manage
+stopping the Emacs server automatically, depending on the whether
+the server is running or not.  This function only applies when
+running Emacs as a daemon."
+  (when (daemonp)
+    (let (empty-timer-p delete-frame-p)
+      (when server-process
+        (pcase server-stop-automatically
+          ('empty        (setq empty-timer-p t))
+          ('delete-frame (setq delete-frame-p t))))
+      ;; Start or stop the timer.
+      (if empty-timer-p
+          (unless server-stop-automatically--timer
+            (setq server-stop-automatically--timer
+                  (run-with-timer
+                   10 2
+		   #'server-stop-automatically--maybe-kill-emacs)))
+        (when server-stop-automatically--timer
+          (cancel-timer server-stop-automatically--timer)
+          (setq server-stop-automatically--timer nil)))
+      ;; Add or remove the delete-frame hook.
+      (if delete-frame-p
+          (add-hook 'delete-frame-functions
+		    #'server-stop-automatically--handle-delete-frame)
+        (remove-hook 'delete-frame-functions
+                     #'server-stop-automatically--handle-delete-frame)))))
+
+(defcustom server-stop-automatically nil
+  "If non-nil, stop the server under the requested conditions.
+
+If this is the symbol `empty', stop the server when it has no
 remaining clients, no remaining unsaved file-visiting buffers,
 and no running processes with a `query-on-exit' flag.
 
-If ARG is the symbol `delete-frame', ask the user when the last
+If this is the symbol `delete-frame', ask the user when the last
 frame is deleted whether each unsaved file-visiting buffer must
 be saved and each running process with a `query-on-exit' flag
 can be stopped, and if so, stop the server itself.
 
-If ARG is the symbol `kill-terminal', ask the user when the
+If this is the symbol `kill-terminal', ask the user when the
 terminal is killed with \\[save-buffers-kill-terminal] \
 whether each unsaved file-visiting
 buffer must be saved and each running process with a `query-on-exit'
-flag can be stopped, and if so, stop the server itself.
-
-Any other value of ARG will cause this function to signal an error.
-
-This function is meant to be called from the user init file."
-  (when (daemonp)
-    (setq server-stop-automatically arg)
-    (cond
-     ((eq arg 'empty)
-      (setq server-stop-automatically nil)
-      (run-with-timer 10 2
-		      #'server-stop-automatically--maybe-kill-emacs))
-     ((eq arg 'delete-frame)
-      (add-hook 'delete-frame-functions
-		#'server-stop-automatically--handle-delete-frame))
-     ((eq arg 'kill-terminal))
-     (t
-      (error "Unexpected argument")))))
+flag can be stopped, and if so, stop the server itself."
+  :type '(choice
+          (const :tag "Never" nil)
+          (const :tag "When empty" empty)
+          (const :tag "When killing last terminal" kill-terminal)
+          (const :tag "When killing last terminal or frame" delete-frame))
+  :set (lambda (symbol value)
+         (set-default symbol value)
+         (server-apply-stop-automatically))
+  :version "29.1")
 
 (define-key ctl-x-map "#" 'server-edit)
 
-- 
2.25.1