unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#51377: Automatically exit server when it has no remaining clients
@ 2021-10-24 15:15 Gregory Heytings
  2021-10-24 16:03 ` Jim Porter
  0 siblings, 1 reply; 16+ messages in thread
From: Gregory Heytings @ 2021-10-24 15:15 UTC (permalink / raw)
  To: 51377

[-- Attachment #1: Type: text/plain, Size: 258 bytes --]


As discussed in 
https://lists.gnu.org/archive/html/emacs-devel/2021-10/msg01465.html , an 
option to stop the Emacs server when it has no clients anymore (which is 
similar to what some other apps do, for example browsers) would be useful.

Patch attached.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Type: text/x-diff; name=Option-to-stop-emacs-server-when-it-has-no-clients-a.patch, Size: 2352 bytes --]

From d3860d6ce9c0d55b416b294cff6be67f734d9b84 Mon Sep 17 00:00:00 2001
From: Gregory Heytings <gregory@heytings.org>
Date: Sun, 24 Oct 2021 15:08:31 +0000
Subject: [PATCH] Option to stop emacs server when it has no clients anymore.

* lisp/server.el (server-stop-when-no-clients): New function.
* doc/emacs/misc.texi (Emacs Server): Document the new function,
and mention that an Emacs server can be started with emacsclient.
---
 doc/emacs/misc.texi | 10 ++++++++++
 lisp/server.el      |  9 +++++++++
 2 files changed, 19 insertions(+)

diff --git a/doc/emacs/misc.texi b/doc/emacs/misc.texi
index 810d212021..a68632831a 100644
--- a/doc/emacs/misc.texi
+++ b/doc/emacs/misc.texi
@@ -1697,6 +1697,11 @@ Emacs Server
 calls @code{server-start} after initialization and does not open an
 initial frame.  It then waits for edit requests from clients.
 
+@item
+Run the command @code{emacsclient} with the @samp{--alternate-editor=""}
+command-line option.  This starts an Emacs daemon only if no Emacs daemon
+is already running.
+
 @cindex systemd unit file
 @item
 If your operating system uses @command{systemd} to manage startup,
@@ -1763,6 +1768,11 @@ Emacs Server
   emacs --daemon=foo
 @end example
 
+@findex server-stop-when-no-clients
+  If you want to automatically stop the Emacs server when it has no
+clients anymore, put the expression @code{(server-stop-when-no-clients)}
+in your init file (@pxref{Init File}).
+
 @findex server-eval-at
   If you have defined a server by a unique server name, it is possible
 to connect to the server from another Emacs instance and evaluate Lisp
diff --git a/lisp/server.el b/lisp/server.el
index 6359a76199..fb1cb886e1 100644
--- a/lisp/server.el
+++ b/lisp/server.el
@@ -1746,6 +1746,15 @@ server-save-buffers-kill-terminal
 	     (server-delete-client proc)))
 	  (t (error "Invalid client frame")))))
 
+;;;###autoload
+(defun server-stop-when-no-clients ()
+  "Automatically stop server when it has no remaining clients.
+This function is meant to be put in init files."
+  (when (daemonp)
+    (run-with-timer 10 2 (lambda ()
+                          (unless (cdr (frame-list))
+                            (save-buffers-kill-emacs))))))
+
 (define-key ctl-x-map "#" 'server-edit)
 
 (defun server-unload-function ()
-- 
2.33.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* bug#51377: Automatically exit server when it has no remaining clients
  2021-10-24 15:15 bug#51377: Automatically exit server when it has no remaining clients Gregory Heytings
@ 2021-10-24 16:03 ` Jim Porter
  2021-10-24 16:14   ` Jim Porter
  2021-10-24 16:32   ` Gregory Heytings
  0 siblings, 2 replies; 16+ messages in thread
From: Jim Porter @ 2021-10-24 16:03 UTC (permalink / raw)
  To: Gregory Heytings, 51377

On 10/24/2021 8:15 AM, Gregory Heytings wrote:
> 
> As discussed in 
> https://lists.gnu.org/archive/html/emacs-devel/2021-10/msg01465.html , 
> an option to stop the Emacs server when it has no clients anymore (which 
> is similar to what some other apps do, for example browsers) would be 
> useful.
> 
> Patch attached.

I'm not sure I understand the patch; `save-buffers-kill-emacs' prompts 
the user to save buffers before killing Emacs, but how would that work 
in a daemon when there are no frames remaining?

I use a slightly-modified form of the following in my configuration, 
which I'm working on hooking up to a flag to remember if the daemon was 
started lazily:

--------------------------------------------------

(defun save-buffers-kill-terminal (&optional arg)
   "Offer to save each buffer, then kill the current connection.
If the current frame has no client or is the last client of a daemon,
kill Emacs itself using `save-buffers-kill-emacs'.

With prefix ARG, silently save all file-visiting buffers, then kill.

If emacsclient was started with a list of filenames to edit, then
only these files will be asked to be saved."
   (interactive "P")
   ;; Only kill the terminal if the current frame is a client. However, if
   ;; Emacs was started as a daemon and this is the last client, kill Emacs
   ;; entirely.
   (if-let ((this-client (frame-parameter nil 'client))
            ((not (and (daemonp)
                       (equal server-clients (list this-client))))))
       (server-save-buffers-kill-terminal arg)
     (save-buffers-kill-emacs arg)))

(defun server-kill-emacs-query-function ()
   "Ask before exiting Emacs if it has live clients.
If Emacs was started as a daemon and the only live client is the
current frame's client, don't bother asking."
   (let ((ignored-client (and (daemonp) (frame-parameter nil 'client))))
     (or (not (seq-some (lambda (client)
                          (unless (eq ignored-client client)
                            (seq-some #'buffer-live-p
                                      (process-get client 'buffers))))
                        server-clients))
         (yes-or-no-p "This Emacs session has clients; exit anyway? "))))

--------------------------------------------------





^ permalink raw reply	[flat|nested] 16+ messages in thread

* bug#51377: Automatically exit server when it has no remaining clients
  2021-10-24 16:03 ` Jim Porter
@ 2021-10-24 16:14   ` Jim Porter
  2021-10-24 16:32   ` Gregory Heytings
  1 sibling, 0 replies; 16+ messages in thread
From: Jim Porter @ 2021-10-24 16:14 UTC (permalink / raw)
  To: Gregory Heytings, 51377

On 10/24/2021 9:03 AM, Jim Porter wrote:
> I use a slightly-modified form of the following in my configuration, 
> which I'm working on hooking up to a flag to remember if the daemon was 
> started lazily:
[snip]

Note: the code I posted doesn't handle `emacsclient -n' (I don't use 
that option, so it doesn't come up in my configuration), but it 
shouldn't be hard to handle that case once I have a real patch put together.





^ permalink raw reply	[flat|nested] 16+ messages in thread

* bug#51377: Automatically exit server when it has no remaining clients
  2021-10-24 16:03 ` Jim Porter
  2021-10-24 16:14   ` Jim Porter
@ 2021-10-24 16:32   ` Gregory Heytings
  2021-10-24 18:08     ` Jim Porter
  1 sibling, 1 reply; 16+ messages in thread
From: Gregory Heytings @ 2021-10-24 16:32 UTC (permalink / raw)
  To: Jim Porter; +Cc: 51377


>
> I'm not sure I understand the patch; `save-buffers-kill-emacs' prompts 
> the user to save buffers before killing Emacs, but how would that work 
> in a daemon when there are no frames remaining?
>

There is one (invisible) frame remaining: the daemon frame.  And when 
there are no other frames (graphical or non-graphical) ones, there's 
nothing to save, save-buffer-kill-emacs just kills emacs.  The patch just 
checks every two seconds if there are remaining frames (apart from the 
daemon one), and if not, it kills emacs.  And it works with and without 
-n.





^ permalink raw reply	[flat|nested] 16+ messages in thread

* bug#51377: Automatically exit server when it has no remaining clients
  2021-10-24 16:32   ` Gregory Heytings
@ 2021-10-24 18:08     ` Jim Porter
  2021-10-24 18:43       ` Gregory Heytings
  0 siblings, 1 reply; 16+ messages in thread
From: Jim Porter @ 2021-10-24 18:08 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: 51377

On 10/24/2021 9:32 AM, Gregory Heytings wrote:
>> I'm not sure I understand the patch; `save-buffers-kill-emacs' prompts 
>> the user to save buffers before killing Emacs, but how would that work 
>> in a daemon when there are no frames remaining?
> 
> There is one (invisible) frame remaining: the daemon frame.  And when 
> there are no other frames (graphical or non-graphical) ones, there's 
> nothing to save, save-buffer-kill-emacs just kills emacs.

I don't think this is true in general. The docstring for 
`server-save-buffers-kill-terminal' says: "If emacsclient was started 
with a list of filenames to edit, then only these files will be asked to 
be saved." As a result, some files with unsaved changes may still exist, 
so we'd want to prompt about those *before* the last frame is closed.

To see this in action:

   $ emacs -Q --daemon
   $ emacsclient foo.txt
   C-x C-f bar.txt
   ;; Make some edits
   C-x C-c
   ;; Exits immediately without prompting about saving bar.txt

If `save-buffers-kill-emacs' were called after that, the Emacs daemon 
would be killed, losing the edits to bar.txt.





^ permalink raw reply	[flat|nested] 16+ messages in thread

* bug#51377: Automatically exit server when it has no remaining clients
  2021-10-24 18:08     ` Jim Porter
@ 2021-10-24 18:43       ` Gregory Heytings
  2021-10-24 19:39         ` Jim Porter
  0 siblings, 1 reply; 16+ messages in thread
From: Gregory Heytings @ 2021-10-24 18:43 UTC (permalink / raw)
  To: Jim Porter; +Cc: 51377

[-- Attachment #1: Type: text/plain, Size: 221 bytes --]


>
> If `save-buffers-kill-emacs' were called after that, the Emacs daemon 
> would be killed, losing the edits to bar.txt.
>

Indeed, you are correct.  Updated patch attached, which also takes care of 
running processes.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Type: text/x-diff; name=Option-to-automatically-stop-Emacs-server.patch, Size: 2983 bytes --]

From b699c16bac38923e43992edf27039a5d9bcca4f1 Mon Sep 17 00:00:00 2001
From: Gregory Heytings <gregory@heytings.org>
Date: Sun, 24 Oct 2021 18:40:39 +0000
Subject: [PATCH] Option to automatically stop Emacs server.

* lisp/server.el (server-stop-automatically): New function.
* doc/emacs/misc.texi (Emacs Server): Document the new function.
Also mention that an Emacs server can be started with emacsclient.
---
 doc/emacs/misc.texi | 11 +++++++++++
 lisp/server.el      | 23 +++++++++++++++++++++++
 2 files changed, 34 insertions(+)

diff --git a/doc/emacs/misc.texi b/doc/emacs/misc.texi
index 5123a716dc..893e5f1843 100644
--- a/doc/emacs/misc.texi
+++ b/doc/emacs/misc.texi
@@ -1697,6 +1697,11 @@ Emacs Server
 calls @code{server-start} after initialization and does not open an
 initial frame.  It then waits for edit requests from clients.
 
+@item
+Run the command @code{emacsclient} with the @samp{--alternate-editor=""}
+command-line option.  This starts an Emacs daemon only if no Emacs daemon
+is already running.
+
 @cindex systemd unit file
 @item
 If your operating system uses @command{systemd} to manage startup,
@@ -1763,6 +1768,12 @@ Emacs Server
   emacs --daemon=foo
 @end example
 
+@findex server-stop-automatically
+  If you want to automatically stop the Emacs server when it has no
+clients, no unsaved buffers and no running processes anymore, put the
+expression @code{(server-stop-automatically)} in your init file
+(@pxref{Init File}).
+
 @findex server-eval-at
   If you have defined a server by a unique server name, it is possible
 to connect to the server from another Emacs instance and evaluate Lisp
diff --git a/lisp/server.el b/lisp/server.el
index 6359a76199..944f1a3dce 100644
--- a/lisp/server.el
+++ b/lisp/server.el
@@ -1746,6 +1746,29 @@ server-save-buffers-kill-terminal
 	     (server-delete-client proc)))
 	  (t (error "Invalid client frame")))))
 
+;;;###autoload
+(defun server-stop-automatically ()
+  "Automatically stop server when possible.
+The server is stopped when it has no remaining clients, no remaining
+unsaved buffers, and no running processes with a query-on-exit flag.
+This function is meant to be put in init files."
+  (when (daemonp)
+    (run-with-timer
+     10 2
+     (lambda ()
+       (unless (cdr (frame-list))
+         (when (and
+                (not (memq t (mapcar (lambda (b)
+                                       (and (buffer-file-name b)
+			                    (buffer-modified-p b)))
+                                     (buffer-list))))
+                (not (memq t (mapcar (lambda (p)
+		                       (and (memq (process-status p)
+				                  '(run stop open listen))
+			                    (process-query-on-exit-flag p)))
+		                     (process-list)))))
+           (kill-emacs)))))))
+
 (define-key ctl-x-map "#" 'server-edit)
 
 (defun server-unload-function ()
-- 
2.33.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* bug#51377: Automatically exit server when it has no remaining clients
  2021-10-24 18:43       ` Gregory Heytings
@ 2021-10-24 19:39         ` Jim Porter
  2021-10-24 20:42           ` Gregory Heytings
  2021-10-24 21:40           ` Jim Porter
  0 siblings, 2 replies; 16+ messages in thread
From: Jim Porter @ 2021-10-24 19:39 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: 51377

[-- Attachment #1: Type: text/plain, Size: 3075 bytes --]

On 10/24/2021 11:43 AM, Gregory Heytings wrote:
>>
>> If `save-buffers-kill-emacs' were called after that, the Emacs daemon 
>> would be killed, losing the edits to bar.txt.
> 
> Indeed, you are correct.  Updated patch attached, which also takes care 
> of running processes.

Yeah, that looks like it should avoid any data loss. However, it's not 
the behavior I'd personally expect. As I understand it, this will just 
keep the Emacs daemon alive if there are any unsaved files. I'd find it 
easier to use if Emacs warned the user about unsaved files before 
killing the last client. Then, Emacs can safely kill the daemon once 
I've confirmed that that's what I want.

I've attached a lightly-tested patch series that implements things in 
the way that I'd be happiest with. There are a few points I should 
explain though.

First, I chose to add a new --lazy-daemon option (I'm not sure about the 
name; feel free to suggest a better one). This makes it possible to 
distinguish whether the daemon was started on demand via `emacsclient'. 
I chose a command-line option rather than a command accepted by the 
server protocol because all the other server protocol commands can be 
used any time a new `emacsclient' is started; this option only makes 
sense when starting the daemon (since it describes how the daemon was 
started). I also chose to add this as a "third" daemon type rather than 
an entirely separate flag since I don't think there's any reason to 
support a lazily-created foreground daemon.

In the second patch, I added a minor performance optimization to 
`server-kill-emacs-query-function'. Now it stops once it finds the first 
live client. This patch isn't strictly required though, and I could 
rework things to exclude it.

Next, I updated `server-save-buffers-kill-terminal' to check if the 
server is a lazy daemon and if the current client is the last one: if 
so, it calls `save-buffers-kill-emacs', which then calls an improved 
`server-kill-emacs-query-function' that knows to avoid prompting 
unnecessarily when this is the last client.

Notably, the change to `server-save-buffers-kill-terminal' alters the 
behavior of killing a frame created by `emacsclient -n'. Looking at the 
code before, I don't see how it could possibly have been the right 
behavior: if there were multiple frames open, it only closed *one* 
frame, even if all of those frames were created after a single call of 
`emacsclient -n' (e.g. by calling `make-frame-command'). If it was the 
*last* frame, it killed Emacs, even if the user explicitly called `emacs 
--daemon' on system boot and wants the daemon to live forever. I've 
changed the behavior so that when you call this from a `nowait' frame, 
it kills Emacs if a) it's a lazy daemon and b) there are no other 
clients. That's a lot closer to the behavior when calling this from a 
"normal" client frame.

Finally, these patches would all require documentation improvements to 
merge. However, it didn't seem like a good use of my time to do that 
until people agree that this is the right strategy overall.

[-- Attachment #2: 0001-Add-lazy-daemon-option-used-to-start-a-daemon-on-dem.patch --]
[-- Type: text/plain, Size: 6469 bytes --]

From ffa4fb0843a4db7a09519099e304e72cbffaf4b7 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sun, 24 Oct 2021 10:42:28 -0700
Subject: [PATCH 1/3] Add --lazy-daemon option, used to start a daemon
 on-demand via emacsclient

src/emacs.c (main, Fdaemon_initialized): Handle '--lazy-daemon'.
(Fdaemon_type): New function.
(syms_of_emacs): Add Sdaemon_type.
lib-src/emacsclient.c (start_daemon_and_retry_set_socket):
Use '--lazy-daemon' when starting daemon.
---
 lib-src/emacsclient.c |  8 ++++----
 src/emacs.c           | 46 ++++++++++++++++++++++++++++++++++++-------
 2 files changed, 43 insertions(+), 11 deletions(-)

diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c
index cff3cec2a7..40ebfa0513 100644
--- a/lib-src/emacsclient.c
+++ b/lib-src/emacsclient.c
@@ -1756,7 +1756,7 @@ start_daemon_and_retry_set_socket (void)
   else
     {
       char emacs[] = "emacs";
-      char daemon_option[] = "--daemon";
+      char daemon_option[] = "--lazy-daemon";
       char *d_argv[3];
       d_argv[0] = emacs;
       d_argv[1] = daemon_option;
@@ -1764,8 +1764,8 @@ start_daemon_and_retry_set_socket (void)
 # ifdef SOCKETS_IN_FILE_SYSTEM
       if (socket_name != NULL)
 	{
-	  /* Pass  --daemon=socket_name as argument.  */
-	  const char *deq = "--daemon=";
+	  /* Pass  --lazy-daemon=socket_name as argument.  */
+	  const char *deq = "--lazy-daemon=";
 	  char *daemon_arg = xmalloc (strlen (deq)
 				      + strlen (socket_name) + 1);
 	  strcpy (stpcpy (daemon_arg, deq), socket_name);
@@ -1790,7 +1790,7 @@ start_daemon_and_retry_set_socket (void)
      it is ready to accept client connections, by asserting an event
      whose name is known to the daemon (defined by nt/inc/ms-w32.h).  */
 
-  if (!CreateProcess (NULL, (LPSTR)"emacs --daemon", NULL, NULL, FALSE,
+  if (!CreateProcess (NULL, (LPSTR)"emacs --lazy-daemon", NULL, NULL, FALSE,
                       CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
     {
       char* msg = NULL;
diff --git a/src/emacs.c b/src/emacs.c
index a24543a586..e41b4abc49 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -245,6 +245,9 @@ #define MAIN_PROGRAM
 --chdir DIR                 change to directory DIR\n\
 --daemon, --bg-daemon[=NAME] start a (named) server in the background\n\
 --fg-daemon[=NAME]          start a (named) server in the foreground\n\
+--lazy-daemon[=NAME]        start a (named) server in the background\
+                              and record that it was started on-demand\n\
+                              by emacsclient\n\
 --debug-init                enable Emacs Lisp debugger for init file\n\
 --display, -d DISPLAY       use X server DISPLAY\n\
 ",
@@ -1635,12 +1638,17 @@ main (int argc, char **argv)
     {
       daemon_type = 2;          /* background */
     }
+  else if (argmatch (argv, argc, "-lazy-daemon", "--lazy-daemon", 12, NULL, &skip_args)
+      || argmatch (argv, argc, "-lazy-daemon", "--lazy-daemon", 12, &dname_arg, &skip_args))
+    {
+      daemon_type = 3;           /* background, lazy */
+    }
 
 
   if (daemon_type > 0)
     {
 #ifndef DOS_NT
-      if (daemon_type == 2)
+      if (daemon_type == 2 || daemon_type == 3)
         {
           /* Start as a background daemon: fork a new child process which
              will run the rest of the initialization code, then exit.
@@ -1668,7 +1676,7 @@ main (int argc, char **argv)
               fputs ("Cannot pipe!\n", stderr);
               exit (1);
             }
-        } /* daemon_type == 2 */
+        } /* daemon_type == 2 || daemon_type == 3 */
 
 #ifdef HAVE_LIBSYSTEMD
       /* Read the number of sockets passed through by systemd.  */
@@ -1692,7 +1700,7 @@ main (int argc, char **argv)
 	     stderr);
 #endif /* USE_GTK */
 
-      if (daemon_type == 2)
+      if (daemon_type == 2 || daemon_type == 3)
         {
           pid_t f;
 #ifndef DAEMON_MUST_EXEC
@@ -1747,8 +1755,9 @@ main (int argc, char **argv)
                 char fdStr[80];
                 int fdStrlen =
                   snprintf (fdStr, sizeof fdStr,
-                            "--bg-daemon=\n%d,%d\n%s", daemon_pipe[0],
-                            daemon_pipe[1], dname_arg ? dname_arg : "");
+                            "--%s-daemon=\n%d,%d\n%s", daemon_pipe[0],
+			    daemon_type == 2 ? "bg" : "lazy", daemon_pipe[1],
+			    dname_arg ? dname_arg : "");
 
                 if (! (0 <= fdStrlen && fdStrlen < sizeof fdStr))
                   {
@@ -1785,7 +1794,7 @@ main (int argc, char **argv)
           emacs_close (daemon_pipe[0]);
 
           setsid ();
-        } /* daemon_type == 2 */
+        } /* daemon_type == 2 || daemon_type == 3 */
 #elif defined(WINDOWSNT)
       /* Indicate that we want daemon mode.  */
       w32_daemon_event = CreateEvent (NULL, TRUE, FALSE, W32_DAEMON_EVENT);
@@ -2372,6 +2381,7 @@ main (int argc, char **argv)
   { "-daemon", "--daemon", 99, 0 },
   { "-bg-daemon", "--bg-daemon", 99, 0 },
   { "-fg-daemon", "--fg-daemon", 99, 0 },
+  { "-lazy-daemon", "--lazy-daemon", 99, 0 },
   { "-help", "--help", 90, 0 },
   { "-nl", "--no-loadup", 70, 0 },
   { "-nsl", "--no-site-lisp", 65, 0 },
@@ -3128,6 +3138,27 @@ DEFUN ("daemonp", Fdaemonp, Sdaemonp, 0, 0, 0,
     return Qnil;
 }
 
+DEFUN ("daemon-type", Fdaemon_type, Sdaemon_type, 0, 0, 0,
+       doc: /* If emacs was started as a daemon, return the type of daemon.
+The result is one of `foreground', `background', or `lazy'. */)
+  (void)
+{
+  switch (abs (daemon_type))
+    {
+    case 0:
+      return Qnil;
+    case 1:
+      return intern_c_string ("foreground");
+    case 2:
+      return intern_c_string ("background");
+    case 3:
+      return intern_c_string ("lazy");
+    default:
+      error ("Unrecognized daemon type");
+    }
+}
+
+
 DEFUN ("daemon-initialized", Fdaemon_initialized, Sdaemon_initialized, 0, 0, 0,
        doc: /* Mark the Emacs daemon as being initialized.
 This finishes the daemonization process by doing the other half of detaching
@@ -3153,7 +3184,7 @@ DEFUN ("daemon-initialized", Fdaemon_initialized, Sdaemon_initialized, 0, 0, 0,
 #endif /* HAVE_LIBSYSTEMD */
     }
 
-  if (daemon_type == 2)
+  if (daemon_type == 2 || daemon_type == 3)
     {
       int nfd;
 
@@ -3211,6 +3242,7 @@ syms_of_emacs (void)
   defsubr (&Sinvocation_name);
   defsubr (&Sinvocation_directory);
   defsubr (&Sdaemonp);
+  defsubr (&Sdaemon_type);
   defsubr (&Sdaemon_initialized);
 
   DEFVAR_LISP ("command-line-args", Vcommand_line_args,
-- 
2.25.1


[-- Attachment #3: 0002-Stop-searching-for-live-clients-once-we-find-one.patch --]
[-- Type: text/plain, Size: 1231 bytes --]

From df292aeaa73c01a7685ff34c9e0265df859d2d42 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sun, 24 Oct 2021 11:21:23 -0700
Subject: [PATCH 2/3] Stop searching for live clients once we find one

lisp/server.el (server-kill-emacs-query-function): Use 'seq-some'
to search for live clients.
---
 lisp/server.el | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/lisp/server.el b/lisp/server.el
index 5306a54776..5988560c83 100644
--- a/lisp/server.el
+++ b/lisp/server.el
@@ -1581,12 +1581,10 @@ server-done
 
 (defun server-kill-emacs-query-function ()
   "Ask before exiting Emacs if it has live clients."
-  (or (not (let (live-client)
-             (dolist (proc server-clients)
-               (when (memq t (mapcar #'buffer-live-p
-                                     (process-get proc 'buffers)))
-                 (setq live-client t)))
-             live-client))
+  (or (not (seq-some (lambda (proc)
+                       (seq-some #'buffer-live-p
+                                 (process-get proc 'buffers)))
+                     server-clients))
       (yes-or-no-p "This Emacs session has clients; exit anyway? ")))
 
 (defun server-kill-buffer ()
-- 
2.25.1


[-- Attachment #4: 0003-When-killing-the-last-client-attached-to-a-lazy-daem.patch --]
[-- Type: text/plain, Size: 4190 bytes --]

From 26777b5fc13b15b33571617378001f9f0600f733 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sun, 24 Oct 2021 12:11:37 -0700
Subject: [PATCH 3/3] When killing the last client attached to a lazy daemon,
 kill the daemon too

lisp/server.el (server-save-buffers-kill-terminal): Kill Emacs when
connected to a lazy daemon and there are no other clients.
(server-kill-emacs-query-function): Don't warn about killing Emacs
when not necessary.
---
 lisp/server.el | 61 +++++++++++++++++++++++++++++---------------------
 1 file changed, 35 insertions(+), 26 deletions(-)

diff --git a/lisp/server.el b/lisp/server.el
index 5988560c83..0cbabba621 100644
--- a/lisp/server.el
+++ b/lisp/server.el
@@ -1580,12 +1580,17 @@ server-done
     (server-buffer-done (current-buffer))))
 
 (defun server-kill-emacs-query-function ()
-  "Ask before exiting Emacs if it has live clients."
-  (or (not (seq-some (lambda (proc)
-                       (seq-some #'buffer-live-p
-                                 (process-get proc 'buffers)))
-                     server-clients))
-      (yes-or-no-p "This Emacs session has clients; exit anyway? ")))
+  "Ask before exiting Emacs if it has live clients.
+If Emacs was started as a lazy daemon and the only live client is the
+current frame's client, don't bother asking."
+  (let ((ignored-proc (and (eq (daemon-type) 'lazy)
+                           (frame-parameter nil 'client))))
+    (or (not (seq-some (lambda (proc)
+                         (unless (eq ignored-proc proc)
+                           (seq-some #'buffer-live-p
+                                     (process-get proc 'buffers))))
+                       server-clients))
+        (yes-or-no-p "This Emacs session has clients; exit anyway? "))))
 
 (defun server-kill-buffer ()
   "Remove the current buffer from its clients' buffer list.
@@ -1721,28 +1726,32 @@ server-save-buffers-kill-terminal
 With ARG non-nil, silently save all file-visiting buffers, then kill.
 
 If emacsclient was started with a list of filenames to edit, then
-only these files will be asked to be saved."
+only these files will be asked to be saved.
+
+If Emacs was started as a lazy daemon and this is the last client
+connected to it, this will call `save-buffers-kill-emacs'."
   (let ((proc (frame-parameter nil 'client)))
-    (cond ((eq proc 'nowait)
+    (unless (or (eq proc 'nowait) (processp proc))
+      (error "Invalid client frame"))
+    (if (and (eq (daemon-type) 'lazy)
+             (equal server-clients (unless (eq proc 'nowait) (list proc))))
+        ;; If we're the last client connected to a lazy daemon, kill Emacs.
+        (save-buffers-kill-emacs arg)
+      (if (eq proc 'nowait)
 	   ;; Nowait frames have no client buffer list.
-	   (if (cdr (frame-list))
-	       (progn (save-some-buffers arg)
-		      (delete-frame))
-	     ;; If we're the last frame standing, kill Emacs.
-	     (save-buffers-kill-emacs arg)))
-	  ((processp proc)
-	   (let ((buffers (process-get proc 'buffers)))
-	     (save-some-buffers
-	      arg (if buffers
-                      ;; Only files from emacsclient file list.
-		      (lambda () (memq (current-buffer) buffers))
-                    ;; No emacsclient file list: don't override
-                    ;; `save-some-buffers-default-predicate' (unless
-                    ;; ARG is non-nil), since we're not killing
-                    ;; Emacs (unlike `save-buffers-kill-emacs').
-		    (and arg t)))
-	     (server-delete-client proc)))
-	  (t (error "Invalid client frame")))))
+	  (progn (save-some-buffers arg)
+		 (delete-frame))
+        (let ((buffers (process-get proc 'buffers)))
+	  (save-some-buffers
+	   arg (if buffers
+                   ;; Only files from emacsclient file list.
+		   (lambda () (memq (current-buffer) buffers))
+                 ;; No emacsclient file list: don't override
+                 ;; `save-some-buffers-default-predicate' (unless ARG
+                 ;; is non-nil), since we're not killing Emacs (unlike
+                 ;; `save-buffers-kill-emacs').
+		 (and arg t)))
+	  (server-delete-client proc))))))
 
 (define-key ctl-x-map "#" 'server-edit)
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* bug#51377: Automatically exit server when it has no remaining clients
  2021-10-24 19:39         ` Jim Porter
@ 2021-10-24 20:42           ` Gregory Heytings
  2021-10-24 21:19             ` Jim Porter
  2021-10-24 21:40           ` Jim Porter
  1 sibling, 1 reply; 16+ messages in thread
From: Gregory Heytings @ 2021-10-24 20:42 UTC (permalink / raw)
  To: Jim Porter; +Cc: 51377


>
> Yeah, that looks like it should avoid any data loss. However, it's not 
> the behavior I'd personally expect. As I understand it, this will just 
> keep the Emacs daemon alive if there are any unsaved files.
>

Indeed.  And if there are any running processes.  This is I think the 
least surprising behavior for users who use the Emacs daemon.

>
> I'd find it easier to use if Emacs warned the user about unsaved files 
> before killing the last client. Then, Emacs can safely kill the daemon 
> once I've confirmed that that's what I want.
>

I'm not sure I understand why this would be better.

If want you want is to save unsaved buffers and stop the daemon when you 
close the last frame, you can simply use:

(defun save-buffers-kill-terminal (&optional arg)
   (interactive "P")
   (if (and (frame-parameter nil 'client)
 	   (cddr (frame-list)))
       (server-save-buffers-kill-terminal arg)
     (save-buffers-kill-emacs arg)))





^ permalink raw reply	[flat|nested] 16+ messages in thread

* bug#51377: Automatically exit server when it has no remaining clients
  2021-10-24 20:42           ` Gregory Heytings
@ 2021-10-24 21:19             ` Jim Porter
  2021-10-24 21:37               ` Gregory Heytings
  0 siblings, 1 reply; 16+ messages in thread
From: Jim Porter @ 2021-10-24 21:19 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: 51377

On 10/24/2021 1:42 PM, Gregory Heytings wrote:
> 
>>
>> Yeah, that looks like it should avoid any data loss. However, it's not 
>> the behavior I'd personally expect. As I understand it, this will just 
>> keep the Emacs daemon alive if there are any unsaved files.
>>
> 
> Indeed.  And if there are any running processes.  This is I think the 
> least surprising behavior for users who use the Emacs daemon.

I'd find it quite surprising, since an errant edit to any open file 
would prevent the server from exiting after the last client stops. While 
leaving the server running isn't the worst thing ever (indeed, that's 
how it works now), I'd want the logic for when the server exits to be as 
simple as possible, i.e. "when there are no more clients, exit" 
(provided the user has confirmed as necessary). Having the server stick 
around because I forgot to save one file would surprise me, mainly 
because it would typically happen after, well, I forgot something.

>> I'd find it easier to use if Emacs warned the user about unsaved files 
>> before killing the last client. Then, Emacs can safely kill the daemon 
>> once I've confirmed that that's what I want.
>>
> 
> I'm not sure I understand why this would be better.

Coming from a non-daemon configuration, my expectation is that 
`save-buffers-kill-terminal' typically kills Emacs entirely. The 
behavior I'm looking for is: I can have multiple clients (or 
"terminals", as the function calls them) connected to the Emacs daemon, 
but once I'm down to just one client, `save-buffers-kill-terminal' works 
like it does in a non-daemon/server configuration. Then, in terms of my 
use pattern, the extra clients work like an addition on top of the 
behavior I'm used to (i.e. running `emacs' directly).

> If want you want is to save unsaved buffers and stop the daemon when you 
> close the last frame, you can simply use:
> 
> (defun save-buffers-kill-terminal (&optional arg)
>    (interactive "P")
>    (if (and (frame-parameter nil 'client)
>         (cddr (frame-list)))
>        (server-save-buffers-kill-terminal arg)
>      (save-buffers-kill-emacs arg)))

That's pretty much what I started with, actually. I've added more to 
that as I find corner cases, and as I try to make my code work well 
under different configurations so that it could merge into Emacs if 
there's interest.





^ permalink raw reply	[flat|nested] 16+ messages in thread

* bug#51377: Automatically exit server when it has no remaining clients
  2021-10-24 21:19             ` Jim Porter
@ 2021-10-24 21:37               ` Gregory Heytings
  2021-10-25 18:21                 ` Jim Porter
  0 siblings, 1 reply; 16+ messages in thread
From: Gregory Heytings @ 2021-10-24 21:37 UTC (permalink / raw)
  To: Jim Porter; +Cc: 51377

[-- Attachment #1: Type: text/plain, Size: 1231 bytes --]


>
> I'd find it quite surprising, since an errant edit to any open file 
> would prevent the server from exiting after the last client stops. While 
> leaving the server running isn't the worst thing ever (indeed, that's 
> how it works now), I'd want the logic for when the server exits to be as 
> simple as possible, i.e. "when there are no more clients, exit" 
> (provided the user has confirmed as necessary). Having the server stick 
> around because I forgot to save one file would surprise me, mainly 
> because it would typically happen after, well, I forgot something.
>

I see.  We have different mental models, I guess.  From my viewpoint the 
Emacs server should stay there until it's not necessary, and I'd be 
surprised to be queried about what to do with buffers opened of processes 
started in a frame I already closed when I want to close another frame. 
But of course I do not object to have both behaviors.

>
> That's pretty much what I started with, actually. I've added more to 
> that as I find corner cases, and as I try to make my code work well 
> under different configurations so that it could merge into Emacs if 
> there's interest.
>

I attach a patch for that other behavior, it works fine AFAICS.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Type: text/x-diff; name=Option-to-stop-Emacs-server-when-last-client-exits.patch, Size: 2776 bytes --]

From 71fddcb7d0d6a8eb92a54c7933da1381b1c406ec Mon Sep 17 00:00:00 2001
From: Gregory Heytings <gregory@heytings.org>
Date: Sun, 24 Oct 2021 21:30:24 +0000
Subject: [PATCH] Option to stop Emacs server when last client exits.

* lisp/server.el (server-stop-when-closing-last-frame): New custom
variable.
* lisp/files.el (save-buffers-kill-terminal): Use the new custom
variable.
* doc/emacs/misc.texi (Emacs Server): Document it.
---
 doc/emacs/misc.texi | 8 ++++++++
 lisp/files.el       | 5 ++++-
 lisp/server.el      | 9 +++++++++
 3 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/doc/emacs/misc.texi b/doc/emacs/misc.texi
index 893e5f1843..d7ec1d6322 100644
--- a/doc/emacs/misc.texi
+++ b/doc/emacs/misc.texi
@@ -1768,6 +1768,14 @@ Emacs Server
   emacs --daemon=foo
 @end example
 
+@vindex server-stop-when-closing-last-frame
+  When the last client frame is closed, unsaved buffers and running
+processes are normally kept in the daemon for later user.  However, if
+you set @code{server-stop-when-closing-last-frame} is non-@code{nil} and
+the last client frame is closed, Emacs asks you whether each unsaved
+buffer must be saved and each unfinished process must be killed, and
+the daemon itself is killed.
+
 @findex server-stop-automatically
   If you want to automatically stop the Emacs server when it has no
 clients, no unsaved buffers and no running processes anymore, put the
diff --git a/lisp/files.el b/lisp/files.el
index 5a6a33721b..268c017de4 100644
--- a/lisp/files.el
+++ b/lisp/files.el
@@ -7742,7 +7742,10 @@ save-buffers-kill-terminal
 If emacsclient was started with a list of file names to edit, then
 only these files will be asked to be saved."
   (interactive "P")
-  (if (frame-parameter nil 'client)
+  (if (and (frame-parameter nil 'client)
+           (if server-stop-when-closing-last-frame
+               (cddr (frame-list))
+             t))
       (server-save-buffers-kill-terminal arg)
     (save-buffers-kill-emacs arg)))
 \f
diff --git a/lisp/server.el b/lisp/server.el
index 944f1a3dce..239ce3a0a2 100644
--- a/lisp/server.el
+++ b/lisp/server.el
@@ -272,6 +272,15 @@ server-client-instructions
   :version "28.1"
   :type 'boolean)
 
+(defcustom server-stop-when-closing-last-frame nil
+  "Whether to stop the server when closing the last frame.
+If non-nil, call `save-buffers-kill-emacs' when the last client frame is
+closed.
+If nil, the last client frame is not handled differently, and
+`server-save-buffers-kill-terminal' is called when it is closed."
+  :version "29.1"
+  :type 'boolean)
+
 ;; We do not use `temporary-file-directory' here, because emacsclient
 ;; does not read the init file.
 (defvar server-socket-dir
-- 
2.33.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* bug#51377: Automatically exit server when it has no remaining clients
  2021-10-24 19:39         ` Jim Porter
  2021-10-24 20:42           ` Gregory Heytings
@ 2021-10-24 21:40           ` Jim Porter
  1 sibling, 0 replies; 16+ messages in thread
From: Jim Porter @ 2021-10-24 21:40 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: 51377

[-- Attachment #1: Type: text/plain, Size: 611 bytes --]

On 10/24/2021 12:39 PM, Jim Porter wrote:
> Next, I updated `server-save-buffers-kill-terminal' to check if the 
> server is a lazy daemon and if the current client is the last one: if 
> so, it calls `save-buffers-kill-emacs', which then calls an improved 
> `server-kill-emacs-query-function' that knows to avoid prompting 
> unnecessarily when this is the last client.

I amended the third patch (attached) to account for this change in 
`handle-delete-frame'. This ensures that if you delete the last 
(non-daemon) frame using this method, it calls `save-buffers-kill-emacs' 
to ask the relevant questions.

[-- Attachment #2: 0003-When-killing-the-last-client-attached-to-a-lazy-daem.patch --]
[-- Type: text/plain, Size: 5379 bytes --]

From 767b43cc04f42f36db20fa1ef9b32aab2c37b497 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sun, 24 Oct 2021 12:11:37 -0700
Subject: [PATCH 3/3] When killing the last client attached to a lazy daemon,
 kill the daemon too

lisp/server.el (server-save-buffers-kill-terminal): Kill Emacs when
connected to a lazy daemon and there are no other clients.
(server-kill-emacs-query-function): Don't warn about killing Emacs
when not necessary.
lisp/frame.el (handle-delete-frame): Exclude the initial frame of a
lazily-created daemon as a "valid" other frame.
---
 lisp/frame.el  |  7 ++++--
 lisp/server.el | 61 +++++++++++++++++++++++++++++---------------------
 2 files changed, 40 insertions(+), 28 deletions(-)

diff --git a/lisp/frame.el b/lisp/frame.el
index dfbd751201..fd38c4b623 100644
--- a/lisp/frame.el
+++ b/lisp/frame.el
@@ -119,11 +119,14 @@ handle-delete-frame
     (if (catch 'other-frame
           (dolist (frame-1 (frame-list))
             ;; A valid "other" frame is visible, has its `delete-before'
-            ;; parameter unset and is not a child frame.
+            ;; parameter unset and is not a child frame or the initial frame
+            ;; of a lazily-created daemon.
             (when (and (not (eq frame-1 frame))
                        (frame-visible-p frame-1)
                        (not (frame-parent frame-1))
-                       (not (frame-parameter frame-1 'delete-before)))
+                       (not (frame-parameter frame-1 'delete-before))
+                       (or (not (eq (daemon-type) 'lazy))
+                           (frame-parameter frame-1 'client)))
               (throw 'other-frame t))))
 	(delete-frame frame t)
       ;; Gildea@x.org says it is ok to ask questions before terminating.
diff --git a/lisp/server.el b/lisp/server.el
index 5988560c83..0cbabba621 100644
--- a/lisp/server.el
+++ b/lisp/server.el
@@ -1580,12 +1580,17 @@ server-done
     (server-buffer-done (current-buffer))))
 
 (defun server-kill-emacs-query-function ()
-  "Ask before exiting Emacs if it has live clients."
-  (or (not (seq-some (lambda (proc)
-                       (seq-some #'buffer-live-p
-                                 (process-get proc 'buffers)))
-                     server-clients))
-      (yes-or-no-p "This Emacs session has clients; exit anyway? ")))
+  "Ask before exiting Emacs if it has live clients.
+If Emacs was started as a lazy daemon and the only live client is the
+current frame's client, don't bother asking."
+  (let ((ignored-proc (and (eq (daemon-type) 'lazy)
+                           (frame-parameter nil 'client))))
+    (or (not (seq-some (lambda (proc)
+                         (unless (eq ignored-proc proc)
+                           (seq-some #'buffer-live-p
+                                     (process-get proc 'buffers))))
+                       server-clients))
+        (yes-or-no-p "This Emacs session has clients; exit anyway? "))))
 
 (defun server-kill-buffer ()
   "Remove the current buffer from its clients' buffer list.
@@ -1721,28 +1726,32 @@ server-save-buffers-kill-terminal
 With ARG non-nil, silently save all file-visiting buffers, then kill.
 
 If emacsclient was started with a list of filenames to edit, then
-only these files will be asked to be saved."
+only these files will be asked to be saved.
+
+If Emacs was started as a lazy daemon and this is the last client
+connected to it, this will call `save-buffers-kill-emacs'."
   (let ((proc (frame-parameter nil 'client)))
-    (cond ((eq proc 'nowait)
+    (unless (or (eq proc 'nowait) (processp proc))
+      (error "Invalid client frame"))
+    (if (and (eq (daemon-type) 'lazy)
+             (equal server-clients (unless (eq proc 'nowait) (list proc))))
+        ;; If we're the last client connected to a lazy daemon, kill Emacs.
+        (save-buffers-kill-emacs arg)
+      (if (eq proc 'nowait)
 	   ;; Nowait frames have no client buffer list.
-	   (if (cdr (frame-list))
-	       (progn (save-some-buffers arg)
-		      (delete-frame))
-	     ;; If we're the last frame standing, kill Emacs.
-	     (save-buffers-kill-emacs arg)))
-	  ((processp proc)
-	   (let ((buffers (process-get proc 'buffers)))
-	     (save-some-buffers
-	      arg (if buffers
-                      ;; Only files from emacsclient file list.
-		      (lambda () (memq (current-buffer) buffers))
-                    ;; No emacsclient file list: don't override
-                    ;; `save-some-buffers-default-predicate' (unless
-                    ;; ARG is non-nil), since we're not killing
-                    ;; Emacs (unlike `save-buffers-kill-emacs').
-		    (and arg t)))
-	     (server-delete-client proc)))
-	  (t (error "Invalid client frame")))))
+	  (progn (save-some-buffers arg)
+		 (delete-frame))
+        (let ((buffers (process-get proc 'buffers)))
+	  (save-some-buffers
+	   arg (if buffers
+                   ;; Only files from emacsclient file list.
+		   (lambda () (memq (current-buffer) buffers))
+                 ;; No emacsclient file list: don't override
+                 ;; `save-some-buffers-default-predicate' (unless ARG
+                 ;; is non-nil), since we're not killing Emacs (unlike
+                 ;; `save-buffers-kill-emacs').
+		 (and arg t)))
+	  (server-delete-client proc))))))
 
 (define-key ctl-x-map "#" 'server-edit)
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* bug#51377: Automatically exit server when it has no remaining clients
  2021-10-24 21:37               ` Gregory Heytings
@ 2021-10-25 18:21                 ` Jim Porter
  2021-10-26 10:37                   ` Gregory Heytings
  0 siblings, 1 reply; 16+ messages in thread
From: Jim Porter @ 2021-10-25 18:21 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: 51377

On 10/24/2021 2:37 PM, Gregory Heytings wrote:
> I see.  We have different mental models, I guess.  From my viewpoint the 
> Emacs server should stay there until it's not necessary, and I'd be 
> surprised to be queried about what to do with buffers opened of 
> processes started in a frame I already closed when I want to close 
> another frame. But of course I do not object to have both behaviors.
[snip]
> 
> I attach a patch for that other behavior, it works fine AFAICS.

There are a few issues that I found when doing this previously:

1) I think it should check whether there are other *clients*, not other 
*frames*. One client might have multiple frames open, and `C-x C-c' 
closes all frames for the current client. If there's only one client 
left, but it has two frames open, it should still kill the server, since 
there will be no more (non-daemon) frames after `C-x C-c'. (Also, it 
should check `(daemonp)', since this logic doesn't apply to a client of 
a non-daemon Emacs.)

2) When killing the Emacs daemon from the last client, 
`server-kill-emacs-query-function' (probably) doesn't need to warn you 
that there are still clients. There's only the one client remaining, 
which the user already intends to kill (albeit indirectly, by killing 
the daemon).

3) `handle-delete-frame' (used when clicking the X button in your window 
manager) can also call `save-buffers-kill-emacs' if the user closed the 
last frame. The logic here should exclude the daemon frame from counting 
as another frame so that closing all frames via the window manager stops 
the daemon too.

I did the above in my patch[1], which could be modified pretty easily to 
use the `server-stop-when-closing-last-frame' option you added in your 
patch. There might be other cases I haven't accounted for, but it's 
working so far in my local configuration.

[1] https://lists.gnu.org/archive/html/bug-gnu-emacs/2021-10/msg02209.html





^ permalink raw reply	[flat|nested] 16+ messages in thread

* bug#51377: Automatically exit server when it has no remaining clients
  2021-10-25 18:21                 ` Jim Porter
@ 2021-10-26 10:37                   ` Gregory Heytings
  2021-10-26 11:59                     ` Gregory Heytings
  0 siblings, 1 reply; 16+ messages in thread
From: Gregory Heytings @ 2021-10-26 10:37 UTC (permalink / raw)
  To: Jim Porter; +Cc: 51377

[-- Attachment #1: Type: text/plain, Size: 1128 bytes --]


>
> 1) I think it should check whether there are other *clients*, not other 
> *frames*.
>

No, because nowait client frames are not recorded as clients.  There's no 
way around it, it's necessary to use frames.

>
> 3) `handle-delete-frame' (used when clicking the X button in your window 
> manager) can also call `save-buffers-kill-emacs' if the user closed the 
> last frame. The logic here should exclude the daemon frame from counting 
> as another frame so that closing all frames via the window manager stops 
> the daemon too.
>

If it's now also necessary to kill the daemon when you close the last 
Emacs frame with the window manager close button (I did not see this 
requirement in your original post), then it's also necessary to kill the 
daemon when you close the last Emacs frame with C-x 5 0 (delete-frame), 
because it's what Emacs tells you to do when you call emacsclient without 
a file argument.

Here's a combined patch, which implements the two desired behaviors, and 
which I believe handles all cases properly: delete-frame, 
handele-delete-frame, save-buffers-kill-terminal, wait and nowait clients.

[-- Attachment #2: Type: text/x-diff, Size: 7368 bytes --]

From 3a6bc059dcc0c29be81a83f70f867897a9890667 Mon Sep 17 00:00:00 2001
From: Gregory Heytings <gregory@heytings.org>
Date: Tue, 26 Oct 2021 10:21:58 +0000
Subject: [PATCH] Options to automatically stop the Emacs server.

* lisp/server.el (server-stop-automatically): New function.
(server-stop-automatically): New auxiliary variable.
(server-stop-automatically--handle-delete-frame): New auxiliary
function.
(server-save-buffers-kill-terminal): Call the new auxiliary
function when necessary.
* doc/emacs/misc.texi (Emacs Server): Document the new function.
Also mention that an Emacs server can be started with emacsclient.
---
 doc/emacs/misc.texi |  17 ++++++++
 lisp/server.el      | 103 +++++++++++++++++++++++++++++++++++---------
 2 files changed, 99 insertions(+), 21 deletions(-)

diff --git a/doc/emacs/misc.texi b/doc/emacs/misc.texi
index 5123a716dc..77e416403e 100644
--- a/doc/emacs/misc.texi
+++ b/doc/emacs/misc.texi
@@ -1697,6 +1697,11 @@ Emacs Server
 calls @code{server-start} after initialization and does not open an
 initial frame.  It then waits for edit requests from clients.
 
+@item
+Run the command @code{emacsclient} with the @samp{--alternate-editor=""}
+command-line option.  This starts an Emacs daemon only if no Emacs daemon
+is already running.
+
 @cindex systemd unit file
 @item
 If your operating system uses @command{systemd} to manage startup,
@@ -1763,6 +1768,18 @@ Emacs Server
   emacs --daemon=foo
 @end example
 
+@findex server-stop-automatically
+  If you want to automatically stop the Emacs server when it has no
+clients, no unsaved file-visiting buffers and no running processes
+anymore, put the expression @code{(server-stop-automatically nil)} in
+your init file (@pxref{Init File}).
+
+  If you want to be asked whether each unsaved file-visiting buffer
+must be saved and each unfinished process can be stopped when the last
+client frame is being closed, and if so, to stop the Emacs server, put
+the expression @code{(server-stop-automatically t)} in your init file
+(@pxref{Init File}).
+
 @findex server-eval-at
   If you have defined a server by a unique server name, it is possible
 to connect to the server from another Emacs instance and evaluate Lisp
diff --git a/lisp/server.el b/lisp/server.el
index 6359a76199..f6dc0cc145 100644
--- a/lisp/server.el
+++ b/lisp/server.el
@@ -1716,6 +1716,9 @@ 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.
@@ -1724,27 +1727,85 @@ 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."
-  (let ((proc (frame-parameter nil 'client)))
-    (cond ((eq proc 'nowait)
-	   ;; Nowait frames have no client buffer list.
-	   (if (cdr (frame-list))
-	       (progn (save-some-buffers arg)
-		      (delete-frame))
-	     ;; If we're the last frame standing, kill Emacs.
-	     (save-buffers-kill-emacs arg)))
-	  ((processp proc)
-	   (let ((buffers (process-get proc 'buffers)))
-	     (save-some-buffers
-	      arg (if buffers
-                      ;; Only files from emacsclient file list.
-		      (lambda () (memq (current-buffer) buffers))
-                    ;; No emacsclient file list: don't override
-                    ;; `save-some-buffers-default-predicate' (unless
-                    ;; ARG is non-nil), since we're not killing
-                    ;; Emacs (unlike `save-buffers-kill-emacs').
-		    (and arg t)))
-	     (server-delete-client proc)))
-	  (t (error "Invalid client frame")))))
+  (if server-stop-automatically
+      (server-stop-automatically--handle-delete-frame (selected-frame))
+    (let ((proc (frame-parameter nil 'client)))
+      (cond ((eq proc 'nowait)
+	     ;; Nowait frames have no client buffer list.
+	     (if (cdr (frame-list))
+	         (progn (save-some-buffers arg)
+		        (delete-frame))
+	       ;; If we're the last frame standing, kill Emacs.
+	       (save-buffers-kill-emacs arg)))
+	    ((processp proc)
+	     (let ((buffers (process-get proc 'buffers)))
+	       (save-some-buffers
+	        arg (if buffers
+                        ;; Only files from emacsclient file list.
+		        (lambda () (memq (current-buffer) buffers))
+                      ;; No emacsclient file list: don't override
+                      ;; `save-some-buffers-default-predicate' (unless
+                      ;; ARG is non-nil), since we're not killing
+                      ;; Emacs (unlike `save-buffers-kill-emacs').
+		      (and arg t)))
+	       (server-delete-client proc)))
+	    (t (error "Invalid client frame"))))))
+
+(defun server-stop-automatically--handle-delete-frame (frame)
+  "Handle deletion of a frame when `server-stop-automatically' is t."
+  (when server-stop-automatically
+    (if (if (and (processp (frame-parameter frame 'client))
+                 (eq this-command 'save-buffers-kill-terminal))
+            (progn
+              (dolist (f (frame-list))
+                (when (and (eq (frame-parameter frame 'client)
+                               (frame-parameter f 'client))
+                           (not (eq frame f)))
+                  (set-frame-parameter f 'client nil)
+                  (let ((server-stop-automatically nil))
+                    (delete-frame f))))
+              (if (cddr (frame-list))
+                  (let ((server-stop-automatically nil))
+                    (delete-frame frame)
+                    nil)
+                t))
+          (null (cddr (frame-list))))
+        (let ((server-stop-automatically nil))
+          (save-buffers-kill-emacs)
+          (delete-frame frame)))))
+
+;;;###autoload
+(defun server-stop-automatically (arg)
+  "Automatically stop server when possible.
+When ARG is nil, the server is stopped when it has no remaining
+clients, no remaining unsaved file-visiting buffers, and no
+running processes with a query-on-exit flag.
+When ARG is non-nil, when the last frame is being closed, the
+user is asked whether each unsaved file-visiting buffer must be
+saved and each running process with a query-on-exit flag must be
+killed, and if so, the server itself is stopped.
+This function is meant to be put in init files."
+  (when (daemonp)
+    (setq server-stop-automatically arg)
+    (if arg
+	(add-hook 'delete-frame-functions
+		  #'server-stop-automatically--handle-delete-frame)
+      (run-with-timer
+       10 2
+       (lambda ()
+	 (unless (cdr (frame-list))
+	   (when (and
+		  (not (memq t (mapcar (lambda (b)
+					 (and (buffer-file-name b)
+					      (buffer-modified-p b)))
+				       (buffer-list))))
+		  (not (memq t (mapcar (lambda (p)
+					 (and (memq (process-status p)
+						    '(run stop open listen))
+					      (process-query-on-exit-flag p)))
+				       (process-list)))))
+	     (print "killing server" #'external-debugging-output)
+	     (kill-emacs))))))))
 
 (define-key ctl-x-map "#" 'server-edit)
 
-- 
2.33.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* bug#51377: Automatically exit server when it has no remaining clients
  2021-10-26 10:37                   ` Gregory Heytings
@ 2021-10-26 11:59                     ` Gregory Heytings
  2021-10-26 15:07                       ` Gregory Heytings
  0 siblings, 1 reply; 16+ messages in thread
From: Gregory Heytings @ 2021-10-26 11:59 UTC (permalink / raw)
  To: Jim Porter; +Cc: 51377

[-- Attachment #1: Type: text/plain, Size: 392 bytes --]


>
> Here's a combined patch, which implements the two desired behaviors, and 
> which I believe handles all cases properly: delete-frame, 
> handele-delete-frame, save-buffers-kill-terminal, wait and nowait 
> clients.
>

It just occurred to me that it's very easy to add a third behavior, namely 
the one you expect, but only when the last frame is killed with C-x C-c. 
See attached patch.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Type: text/x-diff; name=Options-to-automatically-stop-the-Emacs-server.patch, Size: 7866 bytes --]

From 2ffedbeff97e33dc9c504b9877272fa5b73707d6 Mon Sep 17 00:00:00 2001
From: Gregory Heytings <gregory@heytings.org>
Date: Tue, 26 Oct 2021 11:54:27 +0000
Subject: [PATCH] Options to automatically stop the Emacs server.

* lisp/server.el (server-stop-automatically): New function.
(server-stop-automatically): New auxiliary variable.
(server-stop-automatically--maybe-kill-emacs,
server-stop-automatically--handle-delete-frame): New auxiliary
functions.
(server-save-buffers-kill-terminal): Call the new auxiliary
function when necessary.
* doc/emacs/misc.texi (Emacs Server): Document the new function.
Also mention that an Emacs server can be started with emacsclient.
---
 doc/emacs/misc.texi |  20 ++++++++
 lisp/server.el      | 116 ++++++++++++++++++++++++++++++++++++--------
 2 files changed, 115 insertions(+), 21 deletions(-)

diff --git a/doc/emacs/misc.texi b/doc/emacs/misc.texi
index 5123a716dc..b8891ed8c3 100644
--- a/doc/emacs/misc.texi
+++ b/doc/emacs/misc.texi
@@ -1697,6 +1697,11 @@ Emacs Server
 calls @code{server-start} after initialization and does not open an
 initial frame.  It then waits for edit requests from clients.
 
+@item
+Run the command @code{emacsclient} with the @samp{--alternate-editor=""}
+command-line option.  This starts an Emacs daemon only if no Emacs daemon
+is already running.
+
 @cindex systemd unit file
 @item
 If your operating system uses @command{systemd} to manage startup,
@@ -1763,6 +1768,21 @@ Emacs Server
   emacs --daemon=foo
 @end example
 
+@findex server-stop-automatically
+  If you want to automatically stop the Emacs server when it has no
+clients, no unsaved file-visiting buffers and no running processes
+anymore, put the expression @code{(server-stop-automatically nil)} in
+your init file (@pxref{Init File}).
+
+  If you want to be asked whether each unsaved file-visiting buffer
+must be saved and each unfinished process can be stopped when the last
+client frame is being closed, and if so, to stop the Emacs server, put
+the expression @code{(server-stop-automatically t)} in your init file
+(@pxref{Init File}).  If you want this to happen only when the last
+client frame is being closed with @kbd{C-x C-c}
+(@code{save-buffers-kill-terminal}), put the expression
+@code{(server-stop-automatically 'kill-terminal)} in your init file.
+
 @findex server-eval-at
   If you have defined a server by a unique server name, it is possible
 to connect to the server from another Emacs instance and evaluate Lisp
diff --git a/lisp/server.el b/lisp/server.el
index 6359a76199..a81913fe98 100644
--- a/lisp/server.el
+++ b/lisp/server.el
@@ -1716,6 +1716,9 @@ 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.
@@ -1724,27 +1727,98 @@ 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."
-  (let ((proc (frame-parameter nil 'client)))
-    (cond ((eq proc 'nowait)
-	   ;; Nowait frames have no client buffer list.
-	   (if (cdr (frame-list))
-	       (progn (save-some-buffers arg)
-		      (delete-frame))
-	     ;; If we're the last frame standing, kill Emacs.
-	     (save-buffers-kill-emacs arg)))
-	  ((processp proc)
-	   (let ((buffers (process-get proc 'buffers)))
-	     (save-some-buffers
-	      arg (if buffers
-                      ;; Only files from emacsclient file list.
-		      (lambda () (memq (current-buffer) buffers))
-                    ;; No emacsclient file list: don't override
-                    ;; `save-some-buffers-default-predicate' (unless
-                    ;; ARG is non-nil), since we're not killing
-                    ;; Emacs (unlike `save-buffers-kill-emacs').
-		    (and arg t)))
-	     (server-delete-client proc)))
-	  (t (error "Invalid client frame")))))
+  (if server-stop-automatically
+      (server-stop-automatically--handle-delete-frame (selected-frame))
+    (let ((proc (frame-parameter nil 'client)))
+      (cond ((eq proc 'nowait)
+	     ;; Nowait frames have no client buffer list.
+	     (if (cdr (frame-list))
+	         (progn (save-some-buffers arg)
+		        (delete-frame))
+	       ;; If we're the last frame standing, kill Emacs.
+	       (save-buffers-kill-emacs arg)))
+	    ((processp proc)
+	     (let ((buffers (process-get proc 'buffers)))
+	       (save-some-buffers
+	        arg (if buffers
+                        ;; Only files from emacsclient file list.
+		        (lambda () (memq (current-buffer) buffers))
+                      ;; No emacsclient file list: don't override
+                      ;; `save-some-buffers-default-predicate' (unless
+                      ;; ARG is non-nil), since we're not killing
+                      ;; Emacs (unlike `save-buffers-kill-emacs').
+		      (and arg t)))
+	       (server-delete-client proc)))
+	    (t (error "Invalid client frame"))))))
+
+(defun server-stop-automatically--handle-delete-frame (frame)
+  "Handle deletion of a frame when `server-stop-automatically' is t."
+  (when server-stop-automatically
+    (if (if (and (processp (frame-parameter frame 'client))
+		 (eq this-command 'save-buffers-kill-terminal))
+	    (progn
+	      (dolist (f (frame-list))
+		(when (and (eq (frame-parameter frame 'client)
+			       (frame-parameter f 'client))
+			   (not (eq frame f)))
+		  (set-frame-parameter f 'client nil)
+		  (let ((server-stop-automatically nil))
+		    (delete-frame f))))
+	      (if (cddr (frame-list))
+		  (let ((server-stop-automatically nil))
+		    (delete-frame frame)
+		    nil)
+		t))
+	  (null (cddr (frame-list))))
+	(let ((server-stop-automatically nil))
+	  (save-buffers-kill-emacs)
+	  (delete-frame frame)))))
+
+(defun server-stop-automatically--maybe-kill-emacs ()
+  "Handle closing of Emacs daemon when `server-stop-automatically' is nil."
+  (unless (cdr (frame-list))
+    (when (and
+	   (not (memq t (mapcar (lambda (b)
+				  (and (buffer-file-name b)
+				       (buffer-modified-p b)))
+				(buffer-list))))
+	   (not (memq t (mapcar (lambda (p)
+				  (and (memq (process-status p)
+					     '(run stop open listen))
+				       (process-query-on-exit-flag p)))
+				(process-list)))))
+      (kill-emacs))))
+
+;;;###autoload
+(defun server-stop-automatically (arg)
+  "Automatically stop server when possible.
+
+When ARG is nil, the server is stopped when it has no remaining
+clients, no remaining unsaved file-visiting buffers, and no
+running processes with a query-on-exit flag.
+
+When ARG is t, the user is asked when the last frame is being
+closed whether each unsaved file-visiting buffer must be saved
+and each running process with a query-on-exit flag must be
+killed, and if so, the server itself is stopped.
+
+When ARG is any other non-nil value, the user is asked when the
+last frame is being close with \\[save-buffers-kill-terminal] \
+whether each unsaved
+file-visiting buffer must be saved and each running process with
+a query-on-exit flag must be killed, and if so, the server itself
+is stopped.
+
+This function is meant to be put in init files."
+  (when (daemonp)
+    (setq server-stop-automatically arg)
+    (cond
+     ((eq arg t)
+      (add-hook 'delete-frame-functions
+		#'server-stop-automatically--handle-delete-frame))
+     ((eq arg nil)
+      (run-with-timer 10 2
+		      #'server-stop-automatically--maybe-kill-emacs)))))
 
 (define-key ctl-x-map "#" 'server-edit)
 
-- 
2.33.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* bug#51377: Automatically exit server when it has no remaining clients
  2021-10-26 11:59                     ` Gregory Heytings
@ 2021-10-26 15:07                       ` Gregory Heytings
  2021-11-11  5:43                         ` Lars Ingebrigtsen
  0 siblings, 1 reply; 16+ messages in thread
From: Gregory Heytings @ 2021-10-26 15:07 UTC (permalink / raw)
  To: Jim Porter; +Cc: 51377

[-- Attachment #1: Type: text/plain, Size: 73 bytes --]


And here is a slightly improved version of the patch, with a NEWS entry.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Type: text/x-diff; name=Options-to-automatically-stop-the-Emacs-server.patch, Size: 9023 bytes --]

From e4db05933e1f801fc4ed4015fd392e32ac801fcd Mon Sep 17 00:00:00 2001
From: Gregory Heytings <gregory@heytings.org>
Date: Tue, 26 Oct 2021 14:59:37 +0000
Subject: [PATCH] Options to automatically stop the Emacs server.

* lisp/server.el (server-stop-automatically): New function.
(server-stop-automatically): New auxiliary variable.
(server-stop-automatically--maybe-kill-emacs,
server-stop-automatically--handle-delete-frame): New auxiliary
functions.
(server-save-buffers-kill-terminal): Call the new auxiliary
function when necessary.

* doc/emacs/misc.texi (Emacs Server): Document the new function.
Also mention that an Emacs server can be started with emacsclient.

* etc/NEWS: Describe the new function.
---
 doc/emacs/misc.texi |  31 ++++++++++++
 etc/NEWS            |   9 ++++
 lisp/server.el      | 119 ++++++++++++++++++++++++++++++++++++--------
 3 files changed, 138 insertions(+), 21 deletions(-)

diff --git a/doc/emacs/misc.texi b/doc/emacs/misc.texi
index 5123a716dc..f94e48f57b 100644
--- a/doc/emacs/misc.texi
+++ b/doc/emacs/misc.texi
@@ -1697,6 +1697,11 @@ Emacs Server
 calls @code{server-start} after initialization and does not open an
 initial frame.  It then waits for edit requests from clients.
 
+@item
+Run the command @code{emacsclient} with the @samp{--alternate-editor=""}
+command-line option.  This starts an Emacs daemon only if no Emacs daemon
+is already running.
+
 @cindex systemd unit file
 @item
 If your operating system uses @command{systemd} to manage startup,
@@ -1763,6 +1768,32 @@ Emacs Server
   emacs --daemon=foo
 @end example
 
+@findex 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:
+
+@itemize
+@item
+With the argument @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
+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
+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
+stopped.
+@end itemize
+
 @findex server-eval-at
   If you have defined a server by a unique server name, it is possible
 to connect to the server from another Emacs instance and evaluate Lisp
diff --git a/etc/NEWS b/etc/NEWS
index 6d3256959e..03aa81042d 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -73,6 +73,15 @@ Image specifiers can now use ':type webp'.
 *** 'display-buffer' now can set up the body size of the chosen window.
 For example, an alist entry as '(window-width . (body-columns . 40))'
 will make the body of the chosen window 40 columns wide.
+
+** Emacs server and client changes
+
++++
+*** 'server-stop-automatically' can be used to automatically stop the server.
+The Emacs server will be automatically stopped when certain conditions
+are met.  The conditions are given by the argument, which can be
+'empty, 'delete-frame or 'kill-terminal.
+
 \f
 * Editing Changes in Emacs 29.1
 
diff --git a/lisp/server.el b/lisp/server.el
index 5306a54776..0c53c58465 100644
--- a/lisp/server.el
+++ b/lisp/server.el
@@ -1716,6 +1716,9 @@ 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.
@@ -1724,27 +1727,101 @@ 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."
-  (let ((proc (frame-parameter nil 'client)))
-    (cond ((eq proc 'nowait)
-	   ;; Nowait frames have no client buffer list.
-	   (if (cdr (frame-list))
-	       (progn (save-some-buffers arg)
-		      (delete-frame))
-	     ;; If we're the last frame standing, kill Emacs.
-	     (save-buffers-kill-emacs arg)))
-	  ((processp proc)
-	   (let ((buffers (process-get proc 'buffers)))
-	     (save-some-buffers
-	      arg (if buffers
-                      ;; Only files from emacsclient file list.
-		      (lambda () (memq (current-buffer) buffers))
-                    ;; No emacsclient file list: don't override
-                    ;; `save-some-buffers-default-predicate' (unless
-                    ;; ARG is non-nil), since we're not killing
-                    ;; Emacs (unlike `save-buffers-kill-emacs').
-		    (and arg t)))
-	     (server-delete-client proc)))
-	  (t (error "Invalid client frame")))))
+  (if server-stop-automatically
+      (server-stop-automatically--handle-delete-frame (selected-frame))
+    (let ((proc (frame-parameter nil 'client)))
+      (cond ((eq proc 'nowait)
+	     ;; Nowait frames have no client buffer list.
+	     (if (cdr (frame-list))
+	         (progn (save-some-buffers arg)
+		        (delete-frame))
+	       ;; If we're the last frame standing, kill Emacs.
+	       (save-buffers-kill-emacs arg)))
+	    ((processp proc)
+	     (let ((buffers (process-get proc 'buffers)))
+	       (save-some-buffers
+	        arg (if buffers
+                        ;; Only files from emacsclient file list.
+		        (lambda () (memq (current-buffer) buffers))
+                      ;; No emacsclient file list: don't override
+                      ;; `save-some-buffers-default-predicate' (unless
+                      ;; ARG is non-nil), since we're not killing
+                      ;; Emacs (unlike `save-buffers-kill-emacs').
+		      (and arg t)))
+	       (server-delete-client proc)))
+	    (t (error "Invalid client frame"))))))
+
+(defun server-stop-automatically--handle-delete-frame (frame)
+  "Handle deletion of FRAME when `server-stop-automatically' is used."
+  (when server-stop-automatically
+    (if (if (and (processp (frame-parameter frame 'client))
+		 (eq this-command 'save-buffers-kill-terminal))
+	    (progn
+	      (dolist (f (frame-list))
+		(when (and (eq (frame-parameter frame 'client)
+			       (frame-parameter f 'client))
+			   (not (eq frame f)))
+		  (set-frame-parameter f 'client nil)
+		  (let ((server-stop-automatically nil))
+		    (delete-frame f))))
+	      (if (cddr (frame-list))
+		  (let ((server-stop-automatically nil))
+		    (delete-frame frame)
+		    nil)
+		t))
+	  (null (cddr (frame-list))))
+	(let ((server-stop-automatically nil))
+	  (save-buffers-kill-emacs)
+	  (delete-frame frame)))))
+
+(defun server-stop-automatically--maybe-kill-emacs ()
+  "Handle closing of Emacs daemon when `server-stop-automatically' is used."
+  (unless (cdr (frame-list))
+    (when (and
+	   (not (memq t (mapcar (lambda (b)
+				  (and (buffer-file-name b)
+				       (buffer-modified-p b)))
+				(buffer-list))))
+	   (not (memq t (mapcar (lambda (p)
+				  (and (memq (process-status p)
+					     '(run stop open listen))
+				       (process-query-on-exit-flag p)))
+				(process-list)))))
+      (kill-emacs))))
+
+;;;###autoload
+(defun server-stop-automatically (arg)
+  "Automatically stop server when possible.
+
+When ARG is 'empty, the server is stopped when it has no remaining
+clients, no remaining unsaved file-visiting buffers, and no
+running processes with a query-on-exit flag.
+
+When ARG is 'delete-frame, the user is asked when the last frame is
+being closed 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, the server itself is stopped.
+
+When ARG is 'kill-terminal, the user is asked when the last frame
+is being close 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, the server itself is stopped.
+
+This function is meant to be put in init files."
+  (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")))))
 
 (define-key ctl-x-map "#" 'server-edit)
 
-- 
2.33.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* bug#51377: Automatically exit server when it has no remaining clients
  2021-10-26 15:07                       ` Gregory Heytings
@ 2021-11-11  5:43                         ` Lars Ingebrigtsen
  0 siblings, 0 replies; 16+ messages in thread
From: Lars Ingebrigtsen @ 2021-11-11  5:43 UTC (permalink / raw)
  To: Gregory Heytings; +Cc: Jim Porter, 51377

Gregory Heytings <gregory@heytings.org> writes:

> And here is a slightly improved version of the patch, with a NEWS entry.

Makes sense to me; pushed to Emacs 29 now (with some tiny changes).

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2021-11-11  5:43 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-10-24 15:15 bug#51377: Automatically exit server when it has no remaining clients Gregory Heytings
2021-10-24 16:03 ` Jim Porter
2021-10-24 16:14   ` Jim Porter
2021-10-24 16:32   ` Gregory Heytings
2021-10-24 18:08     ` Jim Porter
2021-10-24 18:43       ` Gregory Heytings
2021-10-24 19:39         ` Jim Porter
2021-10-24 20:42           ` Gregory Heytings
2021-10-24 21:19             ` Jim Porter
2021-10-24 21:37               ` Gregory Heytings
2021-10-25 18:21                 ` Jim Porter
2021-10-26 10:37                   ` Gregory Heytings
2021-10-26 11:59                     ` Gregory Heytings
2021-10-26 15:07                       ` Gregory Heytings
2021-11-11  5:43                         ` Lars Ingebrigtsen
2021-10-24 21:40           ` Jim Porter

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).