Merged from miles@gnu.org--gnu-2005 (patch 119)
[bpt/emacs.git] / lisp / server.el
index 71db27c..f0ce3c6 100644 (file)
@@ -42,7 +42,7 @@
 ;; This program transmits the file names to Emacs through
 ;; the server subprocess, and Emacs visits them and lets you edit them.
 
-;; Note that any number of clients may dispatch files to emacs to be edited.
+;; Note that any number of clients may dispatch files to Emacs to be edited.
 
 ;; When you finish editing a Server buffer, again call server-edit
 ;; to mark that buffer as done for the client and switch to the next
@@ -123,7 +123,7 @@ If it is a frame, use the frame's selected window.
 It is not meaningful to set this to a specific frame or window with Custom.
 Only programs can do so."
   :group 'server
-  :version "21.4"
+  :version "22.1"
   :type '(choice (const :tag "Use selected window"
                        :match (lambda (widget value)
                                 (not (functionp value)))
@@ -209,6 +209,46 @@ New clients have no properties."
     (setq server-clients (cons (cons proc nil)
                               server-clients))))
 
+;;;###autoload
+(defun server-getenv (variable &optional frame)
+  "Get the value of VARIABLE in the client environment of frame FRAME.
+VARIABLE should be a string.  Value is nil if VARIABLE is undefined in
+the environment.  Otherwise, value is a string.
+
+If FRAME is an emacsclient frame, then the variable is looked up
+in the environment of the emacsclient process; otherwise the
+function consults the environment of the Emacs process.
+
+If FRAME is nil or missing, then the selected frame is used."
+  (when (not frame) (setq frame (selected-frame)))
+  (let ((client (frame-parameter frame 'client)) env)
+    (if (null client)
+       (getenv variable)
+      (setq env (server-client-get client 'environment))
+      (if (null env)
+         (getenv variable)
+       (cdr (assoc variable env))))))
+
+(defmacro server-with-client-environment (client vars &rest body)
+  "Evaluate BODY with environment variables VARS set to those of CLIENT.
+The environment variables are then restored to their previous values.
+
+VARS should be a list of strings."
+  (declare (indent 2))
+  (let ((oldvalues (make-symbol "oldvalues"))
+       (var (make-symbol "var"))
+       (value (make-symbol "value"))
+       (pair (make-symbol "pair")))
+    `(let (,oldvalues)
+       (dolist (,var (quote ,vars))
+        (let ((,value (cdr (assoc ,var (server-client-get ,client 'environment)))))
+          (setq ,oldvalues (cons (cons ,var (getenv ,var)) ,oldvalues))
+          (setenv ,var ,value)))
+       (unwind-protect
+          (progn ,@body)
+        (dolist (,pair ,oldvalues)
+          (setenv (car ,pair) (cdr ,pair)))))))
+
 (defun server-delete-client (client &optional noframe)
   "Delete CLIENT, including its buffers, displays and frames.
 If NOFRAME is non-nil, let the frames live.  (To be used from
@@ -234,9 +274,9 @@ If NOFRAME is non-nil, let the frames live.  (To be used from
              (kill-buffer (current-buffer))))))
 
       ;; Delete the client's tty.
-      (let ((tty (server-client-get client 'tty)))
-       (when (and tty (server-tty-live-p tty))
-         (delete-tty tty)))
+      (let ((display-id (server-client-get client 'display)))
+       (when (eq (display-live-p display-id) t)
+         (delete-display display-id)))
 
       ;; Delete the client's frames.
       (unless noframe
@@ -252,7 +292,9 @@ If NOFRAME is non-nil, let the frames live.  (To be used from
       (server-log "Deleted" proc))))
 
 (defun server-log (string &optional client)
-  "If a *server* buffer exists, write STRING to it for logging purposes."
+  "If a *server* buffer exists, write STRING to it for logging purposes.
+If CLIENT is non-nil, add a description of it to the logged
+message."
   (if (get-buffer "*server*")
       (with-current-buffer "*server*"
        (goto-char (point-max))
@@ -264,62 +306,39 @@ If NOFRAME is non-nil, let the frames live.  (To be used from
                string)
        (or (bolp) (newline)))))
 
-(defun server-tty-live-p (tty)
-  "Return non-nil if the tty device named TTY has a live frame."
-  (let (result)
-    (dolist (frame (frame-list) result)
-      (when (and (eq (frame-live-p frame) t)
-                (equal (frame-tty-name frame) tty))
-       (setq result t)))))
-
 (defun server-sentinel (proc msg)
   "The process sentinel for Emacs server connections."
   (server-log (format "Status changed to %s: %s" (process-status proc) msg) proc)
   (server-delete-client proc))
 
-(defun server-handle-delete-tty (tty)
-  "Delete the client connection when the emacsclient terminal device is closed."
-  (dolist (proc (server-clients-with 'tty tty))
-    (server-log (format "server-handle-delete-tty, tty %s" tty) proc)
-    (server-delete-client proc)))
-
 (defun server-handle-delete-frame (frame)
   "Delete the client connection when the emacsclient frame is deleted."
   (let ((proc (frame-parameter frame 'client)))
-    (when (and proc (window-system frame))
-      ;; (Closing a terminal frame must not trigger a delete;
-      ;; we must wait for delete-tty-after-functions.)
+    (when (and proc
+              (or (window-system frame)
+                  ;; A terminal display must not yet be deleted if
+                  ;; there are other frames on it.
+                  (< 0 (let ((frame-num 0))
+                         (mapc (lambda (f)
+                                 (when (eq (frame-display f)
+                                           (frame-display frame))
+                                   (setq frame-num (1+ frame-num))))
+                               (frame-list))
+                         frame-num))))
       (server-log (format "server-handle-delete-frame, frame %s" frame) proc)
       (server-delete-client proc 'noframe)))) ; Let delete-frame delete the frame later.
 
-(defun server-handle-suspend-tty (tty)
+(defun server-handle-suspend-tty (display)
   "Notify the emacsclient process to suspend itself when its tty device is suspended."
-  (dolist (proc (server-clients-with 'tty tty))
-    (server-log (format "server-handle-suspend-tty, tty %s" tty) proc)
-    (process-send-string proc "-suspend \n")))
-
-(defun server-select-display (display)
-  ;; If the current frame is on `display' we're all set.
-  (unless (equal (frame-parameter (selected-frame) 'display) display)
-    ;; Otherwise, look for an existing frame there and select it.
-    (dolist (frame (frame-list))
-      (when (equal (frame-parameter frame 'display) display)
-       (select-frame frame)))
-    ;; If there's no frame on that display yet, create a dummy one
-    ;; and select it.
-    (unless (equal (frame-parameter (selected-frame) 'display) display)
-      (select-frame
-       (make-frame-on-display display)))))
-       ;; This frame is only there in place of an actual "current display"
-       ;; setting, so we want it to be as unobtrusive as possible.  That's
-       ;; what the invisibility is for.  The minibuffer setting is so that
-       ;; we don't end up displaying a buffer in it (which noone would
-       ;; notice).
-        ;; XXX I have found this behaviour to be surprising and annoying. -- Lorentey
-       ;; '((visibility . nil) (minibuffer . only)))))))
+  (dolist (proc (server-clients-with 'display display))
+    (server-log (format "server-handle-suspend-tty, display %s" display) proc)
+    (condition-case err
+       (server-send-string proc "-suspend \n")
+      (file-error (condition-case nil (server-delete-client proc) (error nil))))))
 
 (defun server-unquote-arg (arg)
-  "Remove &-quotation from ARG."
+  "Remove &-quotation from ARG.
+See `server-quote-arg' and `server-process-filter'."
   (replace-regexp-in-string
    "&." (lambda (s)
          (case (aref s 1)
@@ -332,7 +351,9 @@ If NOFRAME is non-nil, let the frames live.  (To be used from
 (defun server-quote-arg (arg)
   "In ARG, insert a & before each &, each space, each newline, and -.
 Change spaces to underscores, too, so that the return value never
-contains a space."
+contains a space.
+
+See `server-unquote-arg' and `server-process-filter'."
   (replace-regexp-in-string
    "[-&\n ]" (lambda (s)
               (case (aref s 0)
@@ -342,6 +363,11 @@ contains a space."
                 (?\s "&_")))
    arg t t))
 
+(defun server-send-string (proc string)
+  "A wrapper around `proc-send-string' for logging."
+  (server-log (concat "Sent " string) proc)
+  (process-send-string proc string))
+
 (defun server-ensure-safe-dir (dir)
   "Make sure DIR is a directory with no race-condition issues.
 Creates the directory if necessary and makes sure:
@@ -362,11 +388,12 @@ Creates the directory if necessary and makes sure:
 (defun server-start (&optional leave-dead)
   "Allow this Emacs process to be a server for client processes.
 This starts a server communications subprocess through which
-client \"editors\" can send your editing commands to this Emacs job.
-To use the server, set up the program `emacsclient' in the
+client \"editors\" can send your editing commands to this Emacs
+job.  To use the server, set up the program `emacsclient' in the
 Emacs distribution as your standard \"editor\".
 
-Prefix arg means just kill any existing server communications subprocess."
+Prefix arg LEAVE-DEAD means just kill any existing server
+communications subprocess."
   (interactive "P")
   (when (or
         (not server-clients)
@@ -395,7 +422,6 @@ Prefix arg means just kill any existing server communications subprocess."
          (server-log (message "Restarting server"))
        (server-log (message "Starting server")))
       (letf (((default-file-modes) ?\700))
-       (add-hook 'delete-tty-after-functions 'server-handle-delete-tty)
        (add-hook 'suspend-tty-functions 'server-handle-suspend-tty)
        (add-hook 'delete-frame-functions 'server-handle-delete-frame)
        (add-hook 'kill-buffer-query-functions 'server-kill-buffer-query-function)
@@ -418,15 +444,106 @@ Server mode runs a process that accepts commands from the
 `emacsclient' program.  See `server-start' and Info node `Emacs server'."
   :global t
   :group 'server
-  :version "21.4"
+  :version "22.1"
   ;; Fixme: Should this check for an existing server socket and do
   ;; nothing if there is one (for multiple Emacs sessions)?
   (server-start (not server-mode)))
 \f
 (defun server-process-filter (proc string)
   "Process a request from the server to edit some files.
-PROC is the server process.  Format of STRING is \"PATH PATH PATH... \\n\"."
-  (server-log string proc)
+PROC is the server process.  STRING consists of a sequence of
+commands prefixed by a dash.  Some commands have arguments; these
+are &-quoted and need to be decoded by `server-unquote-arg'.  The
+filter parses and executes these commands.
+
+To illustrate the protocol, here is an example command that
+emacsclient sends to create a new X frame (note that the whole
+sequence is sent on a single line):
+
+       -version 21.3.50 xterm
+       -env HOME /home/lorentey
+       -env DISPLAY :0.0
+       ... lots of other -env commands
+       -display :0.0
+       -window-system
+
+The server normally sends back the single command `-good-version'
+as a response.
+
+The following commands are accepted by the server:
+
+`-version CLIENT-VERSION'
+  Check version numbers between server and client, and signal an
+  error if there is a mismatch.  The server replies with
+  `-good-version' to confirm the match.
+
+`-env NAME VALUE'
+  An environment variable on the client side.
+
+`-nowait'
+  Request that the next frame created should not be
+  associated with this client.
+
+`-display DISPLAY'
+  Set the display name to open X frames on.
+
+`-position LINE[:COLUMN]'
+  Go to the given line and column number
+  in the next file opened.
+
+`-file FILENAME'
+  Load the given file in the current frame.
+
+`-eval EXPR'
+  Evaluate EXPR as a Lisp expression and return the
+  result in -print commands.
+
+`-window-system'
+  Open a new X frame.
+
+`-tty DEVICENAME TYPE'
+  Open a new tty frame at the client.
+
+`-resume'
+  Resume this tty frame. The client sends this string when it
+  gets the SIGCONT signal and it is the foreground process on its
+  controlling tty.
+
+`-suspend'
+  Suspend this tty frame.  The client sends this string in
+  response to SIGTSTP and SIGTTOU.  The server must cease all I/O
+  on this tty until it gets a -resume command.
+
+`-ignore COMMENT'
+  Do nothing, but put the comment in the server
+  log.  Useful for debugging.
+
+
+The following commands are accepted by the client:
+
+`-good-version'
+  Signals a version match between the client and the server.
+
+`-emacs-pid PID'
+  Describes the process id of the Emacs process;
+  used to forward window change signals to it.
+
+`-window-system-unsupported'
+  Signals that the server does not
+  support creating X frames; the client must try again with a tty
+  frame.
+
+`-print STRING'
+  Print STRING on stdout.  Used to send values
+  returned by -eval.
+
+`-error DESCRIPTION'
+  Signal an error (but continue processing).
+
+`-suspend'
+  Suspend this terminal, i.e., stop the client process.  Sent
+  when the user presses C-z."
+  (server-log (concat "Received " string) proc)
   (let ((prev (process-get proc 'previous-string)))
     (when prev
       (setq string (concat prev string))
@@ -465,7 +582,7 @@ PROC is the server process.  Format of STRING is \"PATH PATH PATH... \\n\"."
                    (setq request (substring request (match-end 0)))
                    (if (equal client-version truncated-emacs-version)
                        (progn
-                         (process-send-string proc "-good-version \n")
+                         (server-send-string proc "-good-version \n")
                          (server-client-set client 'version client-version))
                      (error (concat "Version mismatch: Emacs is "
                                     truncated-emacs-version
@@ -484,33 +601,42 @@ PROC is the server process.  Format of STRING is \"PATH PATH PATH... \\n\"."
                 ((equal "-window-system" arg)
                  (unless (server-client-get client 'version)
                    (error "Protocol error; make sure to use the correct version of emacsclient"))
-                 (setq frame (make-frame-on-display
-                              (or display
-                                  (frame-parameter nil 'display)
-                                  (getenv "DISPLAY")
-                                  (error "Please specify display"))
-                              (list (cons 'client proc))))
-                 ;; XXX We need to ensure the client parameter is
-                 ;; really set because Emacs forgets initialization
-                 ;; parameters for X frames at the moment.
-                 (modify-frame-parameters frame (list (cons 'client proc)))
-                 (select-frame frame)
-                 (server-client-set client 'frame frame)
-                 (setq dontkill t))
+                 (if (fboundp 'x-create-frame)
+                     (progn
+                       (setq frame (make-frame-on-display
+                                    (or display
+                                        (frame-parameter nil 'display)
+                                        (getenv "DISPLAY")
+                                        (error "Please specify display"))
+                                    (list (cons 'client proc))))
+                       ;; XXX We need to ensure the client parameter is
+                       ;; really set because Emacs forgets initialization
+                       ;; parameters for X frames at the moment.
+                       (modify-frame-parameters frame (list (cons 'client proc)))
+                       (select-frame frame)
+                       (server-client-set client 'frame frame)
+                       (server-client-set client 'display (frame-display frame))
+                       (setq dontkill t))
+                   ;; This emacs does not support X.
+                   (server-log "Window system unsupported" proc)
+                   (server-send-string proc "-window-system-unsupported \n")
+                   (setq dontkill t)))
 
                 ;; -resume:  Resume a suspended tty frame.
                 ((equal "-resume" arg)
-                 (let ((tty (server-client-get client 'tty)))
+                 (let ((display-id (server-client-get client 'display)))
                    (setq dontkill t)
-                   (when tty (resume-tty tty))))
+                   (when (eq (display-live-p display-id) t)
+                     (resume-tty display-id))))
 
                 ;; -suspend:  Suspend the client's frame.  (In case we
                 ;; get out of sync, and a C-z sends a SIGTSTP to
                 ;; emacsclient.)
                 ((equal "-suspend" arg)
-                 (let ((tty (server-client-get client 'tty)))
+                 (let ((display-id (server-client-get client 'display)))
                    (setq dontkill t)
-                   (when tty (suspend-tty tty))))
+                   (when (eq (display-live-p display-id) t)
+                     (suspend-tty display-id))))
 
                 ;; -ignore COMMENT:  Noop; useful for debugging emacsclient.
                 ;; (The given comment appears in the server log.)
@@ -525,14 +651,23 @@ PROC is the server process.  Format of STRING is \"PATH PATH PATH... \\n\"."
                    (setq request (substring request (match-end 0)))
                    (unless (server-client-get client 'version)
                      (error "Protocol error; make sure you use the correct version of emacsclient"))
-                   (setq frame (make-frame-on-tty tty type (list (cons 'client proc))))
+                   ;; Set up client's environment for tgetent(3)
+                   ;; according to ncurses(3).
+                   (server-with-client-environment proc
+                       ("BAUDRATE" "COLUMNS" "ESCDELAY" "HOME" "LINES"
+                        "NCURSES_ASSUMED_COLORS" "NCURSES_NO_PADDING"
+                        "NCURSES_NO_SETBUF" "TERM" "TERMCAP" "TERMINFO"
+                        "TERMINFO_DIRS" "TERMPATH")
+                     (setq frame (make-frame-on-tty tty type
+                                                    `((client . ,proc)))))
                    (select-frame frame)
                    (server-client-set client 'frame frame)
-                   (server-client-set client 'tty (frame-tty-name frame))
+                   (server-client-set client 'tty (display-name frame))
+                   (server-client-set client 'display (frame-display frame))
                    ;; Set up display for the remote locale.
                    (configure-display-for-locale)
                    ;; Reply with our pid.
-                   (process-send-string proc (concat "-emacs-pid " (number-to-string (emacs-pid)) "\n"))
+                   (server-send-string proc (concat "-emacs-pid " (number-to-string (emacs-pid)) "\n"))
                    (setq dontkill t)))
 
                 ;; -position LINE:  Go to the given line in the next file.
@@ -542,9 +677,9 @@ PROC is the server process.  Format of STRING is \"PATH PATH PATH... \\n\"."
 
                 ;; -position LINE:COLUMN:  Set point to the given position in the next file.
                 ((and (equal "-position" arg) (string-match "\\+\\([0-9]+\\):\\([0-9]+\\) " request))
-                 (setq request (substring request (match-end 0))
-                       lineno (string-to-int (match-string 1 request))
-                       columnno (string-to-int (match-string 2 request))))
+                 (setq lineno (string-to-int (match-string 1 request))
+                       columnno (string-to-int (match-string 2 request))
+                       request (substring request (match-end 0))))
 
                 ;; -file FILENAME:  Load the given file.
                 ((and (equal "-file" arg) (string-match "\\([^ ]+\\) " request))
@@ -568,12 +703,11 @@ PROC is the server process.  Format of STRING is \"PATH PATH PATH... \\n\"."
                        (with-temp-buffer
                          (let ((standard-output (current-buffer)))
                            (pp v)
-                           (process-send-string proc "-print ")
-                           (process-send-string
-                            proc (server-quote-arg
-                                  (buffer-substring-no-properties (point-min)
-                                                                  (point-max))))
-                           (process-send-string proc "\n")))))
+                           (server-send-string
+                            proc (format "-print %s\n"
+                                         (server-quote-arg
+                                          (buffer-substring-no-properties (point-min)
+                                                                          (point-max)))))))))
                    (setq lineno 1
                          columnno 0)))
 
@@ -593,59 +727,59 @@ PROC is the server process.  Format of STRING is \"PATH PATH PATH... \\n\"."
                 ;; Unknown command.
                 (t (error "Unknown command: %s" arg)))))
 
-           (when files
-             (run-hooks 'pre-command-hook)
-             (server-visit-files files client nowait)
-             (run-hooks 'post-command-hook))
-
-           ;; Delete the client if necessary.
-           (cond
-            (nowait
-             ;; Client requested nowait; return immediately.
-             (server-log "Close nowait client" proc)
-             (server-delete-client proc))
-            ((and (not dontkill)
-                  (null (server-client-get client 'buffers)))
-             ;; This client is empty; get rid of it immediately.
-             (server-log "Close empty client" proc)
-             (server-delete-client proc))
-            (t
-             (let ((buffers (server-client-get client 'buffers)))
-               (when buffers
-                 ;; We visited some buffer for this client.
-                 (cond
-                  ((or isearch-mode (minibufferp))
-                   nil)
-                  ((and frame (null buffers))
-                   (message (substitute-command-keys
-                             "When done with this frame, type \\[delete-frame]")))
-                  ((not (null buffers))
-                   (server-switch-buffer (car buffers))
-                   (run-hooks 'server-switch-hook)
-                   (unless nowait
-                     (message (substitute-command-keys
-                               "When done with a buffer, type \\[server-edit]")))))))))))
+           (let (buffers)
+             (when files
+               (run-hooks 'pre-command-hook)
+               (setq buffers (server-visit-files files client nowait))
+               (run-hooks 'post-command-hook))
+
+             ;; Delete the client if necessary.
+             (cond
+              (nowait
+               ;; Client requested nowait; return immediately.
+               (server-log "Close nowait client" proc)
+               (server-delete-client proc))
+              ((and (not dontkill) (null buffers))
+               ;; This client is empty; get rid of it immediately.
+               (server-log "Close empty client" proc)
+               (server-delete-client proc)))
+             (cond
+              ((or isearch-mode (minibufferp))
+               nil)
+              ((and frame (null buffers))
+               (message (substitute-command-keys
+                         "When done with this frame, type \\[delete-frame]")))
+              ((not (null buffers))
+               (server-switch-buffer (car buffers))
+               (run-hooks 'server-switch-hook)
+               (unless nowait
+                 (message (substitute-command-keys
+                           "When done with a buffer, type \\[server-edit]"))))))))
 
        ;; Save for later any partial line that remains.
        (when (> (length string) 0)
          (process-put proc 'previous-string string)))
     ;; condition-case
     (error (ignore-errors
-            (process-send-string
+            (server-send-string
              proc (concat "-error " (server-quote-arg (error-message-string err))))
             (setq string "")
             (server-log (error-message-string err) proc)
             (delete-process proc)))))
 
 (defun server-goto-line-column (file-line-col)
+  "Move point to the position indicated in FILE-LINE-COL.
+FILE-LINE-COL should be a three-element list as described in
+`server-visit-files'."
   (goto-line (nth 1 file-line-col))
   (let ((column-number (nth 2 file-line-col)))
     (if (> column-number 0)
        (move-to-column (1- column-number)))))
 
 (defun server-visit-files (files client &optional nowait)
-  "Find FILES and return the list CLIENT with the buffers nconc'd.
+  "Find FILES and return a list of buffers created.
 FILES is an alist whose elements are (FILENAME LINENUMBER COLUMNNUMBER).
+CLIENT is the client that requested this operation.
 NOWAIT non-nil means this client is not waiting for the results,
 so don't mark these buffers specially, just visit them normally."
   ;; Bind last-nonmenu-event to force use of keyboard, not mouse, for queries.
@@ -681,9 +815,11 @@ so don't mark these buffers specially, just visit them normally."
          (add-hook 'kill-buffer-hook 'server-kill-buffer nil t)
          (push (car client) server-buffer-clients))
        (push (current-buffer) client-record)))
-    (server-client-set
-     client 'buffers
-     (nconc (server-client-get client 'buffers) client-record))))
+    (unless nowait
+      (server-client-set
+       client 'buffers
+       (nconc (server-client-get client 'buffers) client-record)))
+    client-record))
 \f
 (defun server-buffer-done (buffer &optional for-killing)
   "Mark BUFFER as \"done\" for its client(s).
@@ -786,7 +922,7 @@ specifically for the clients and did not exist before their request for it."
                           (buffer-name (current-buffer))))))
 
 (defun server-kill-emacs-query-function ()
-  "Ask before exiting Emacs it has are live clients."
+  "Ask before exiting Emacs it has live clients."
   (or (not server-clients)
       (let (live-client)
        (dolist (client server-clients live-client)
@@ -799,6 +935,8 @@ specifically for the clients and did not exist before their request for it."
   "Non-nil while `server-kill-buffer' or `server-buffer-done' is running.")
 
 (defun server-kill-buffer ()
+  "Remove the current buffer from its clients' buffer list.
+Designed to be added to `kill-buffer-hook'."
   ;; Prevent infinite recursion if user has made server-done-hook
   ;; call kill-buffer.
   (or server-kill-buffer-running
@@ -830,11 +968,12 @@ starts server process and that is all.  Invoked by \\[server-edit]."
 
 (defun server-switch-buffer (&optional next-buffer killed-one)
   "Switch to another buffer, preferably one that has a client.
-Arg NEXT-BUFFER is a suggestion; if it is a live buffer, use it."
-  ;; KILLED-ONE is t in a recursive call
-  ;; if we have already killed one temp-file server buffer.
-  ;; This means we should avoid the final "switch to some other buffer"
-  ;; since we've already effectively done that.
+Arg NEXT-BUFFER is a suggestion; if it is a live buffer, use it.
+
+KILLED-ONE is t in a recursive call if we have already killed one
+temp-file server buffer.  This means we should avoid the final
+\"switch to some other buffer\" since we've already effectively
+done that."
   (if (null next-buffer)
       (progn
        (let ((rest server-clients))
@@ -888,36 +1027,40 @@ Arg NEXT-BUFFER is a suggestion; if it is a live buffer, use it."
              ;; a minibuffer/dedicated-window (if there's no other).
              (error (pop-to-buffer next-buffer)))))))))
 
-(global-set-key "\C-x#" 'server-edit)
+(defun server-save-buffers-kill-display (&optional arg)
+  "Offer to save each buffer, then kill the current connection.
+If the current frame has no client, kill Emacs itself.
 
-;;;###autoload
-(defun server-getenv (variable &optional frame)
-  "Get the value of VARIABLE in the client environment of frame FRAME.
-VARIABLE should be a string.  Value is nil if VARIABLE is undefined in
-the environment.  Otherwise, value is a string.
+With prefix arg, silently save all file-visiting buffers, then kill.
 
-If FRAME is an emacsclient frame, then the variable is looked up
-in the environment of the emacsclient process; otherwise the
-function consults the environment of the Emacs process.
+If emacsclient was started with a list of filenames to edit, then
+only these files will be asked to be saved."
+  (interactive "P")
+  (let ((proc (frame-parameter (selected-frame) 'client)))
+    (if proc
+       (let ((buffers (server-client-get proc 'buffers)))
+         ;; If client is bufferless, emulate a normal Emacs session
+         ;; exit and offer to save all buffers.  Otherwise, offer to
+         ;; save only the buffers belonging to the client.
+         (save-some-buffers arg
+                            (if buffers
+                                (lambda () (memq (current-buffer) buffers))
+                              t))
+         (server-delete-client proc))
+      (save-buffers-kill-emacs))))
 
-If FRAME is nil or missing, then the selected frame is used."
-  (when (not frame) (setq frame (selected-frame)))
-  (let ((client (frame-parameter frame 'client)) env)
-    (if (null client)
-       (getenv variable)
-      (setq env (server-client-get client 'environment))
-      (if (null env)
-         (getenv variable)
-       (cdr (assoc variable env))))))
+(global-set-key "\C-x#" 'server-edit)
 
 (defun server-unload-hook ()
+  "Unload the server library."
   (server-start t)
-  (remove-hook 'delete-tty-after-functions 'server-handle-delete-tty)
   (remove-hook 'suspend-tty-functions 'server-handle-suspend-tty)
   (remove-hook 'delete-frame-functions 'server-handle-delete-frame)
   (remove-hook 'kill-buffer-query-functions 'server-kill-buffer-query-function)
   (remove-hook 'kill-emacs-query-functions 'server-kill-emacs-query-function)
   (remove-hook 'kill-buffer-hook 'server-kill-buffer))
+
+(add-hook 'server-unload-hook 'server-unload-hook)
 \f
 (provide 'server)