]> code.delx.au - gnu-emacs/blobdiff - lisp/server.el
merging Emacs.app (NeXTstep port)
[gnu-emacs] / lisp / server.el
index 434d7d7ecef2099eaae3b05365506e0cb3907b91..ff2cb1241f2cd38de82b9ea276d94ebdc9c1bdca 100644 (file)
@@ -1,7 +1,7 @@
 ;;; server.el --- Lisp code for GNU Emacs running as server process
 
 ;; Copyright (C) 1986, 1987, 1992, 1994, 1995, 1996, 1997, 1998, 1999, 2000,
-;;   2001, 2002, 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
+;;   2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
 
 ;; Author: William Sommerfeld <wesommer@athena.mit.edu>
 ;; Maintainer: FSF
 
 ;; This file is part of GNU Emacs.
 
-;; GNU Emacs is free software; you can redistribute it and/or modify
+;; GNU Emacs is free software: you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 3, or (at your option)
-;; any later version.
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
 
 ;; GNU Emacs is distributed in the hope that it will be useful,
 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -23,9 +23,7 @@
 ;; GNU General Public License for more details.
 
 ;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs; see the file COPYING.  If not, write to the
-;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
-;; Boston, MA 02110-1301, USA.
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;; Commentary:
 
 ;; The global variable "server-clients" lists all the waiting clients,
 ;; and which files are yet to be edited for each.
 
+;; Todo:
+
+;; - handle command-line-args-left.
+;; - move most of the args processing and decision making from emacsclient.c
+;;   to here.
+;; - fix up handling of the client's environment (place it in the terminal?).
+
 ;;; Code:
 
 (eval-when-compile (require 'cl))
@@ -139,8 +144,7 @@ If set, the server accepts remote connections; otherwise it is local."
 
 (defvar server-clients nil
   "List of current server clients.
-Each element is (PROC PROPERTIES...) where PROC is a process object,
-and PROPERTIES is an association list of client properties.")
+Each element is a process.")
 
 (defvar server-buffer-clients nil
   "List of client processes requesting editing of current buffer.")
@@ -178,10 +182,10 @@ invoke the Emacs server."
 (defcustom server-kill-new-buffers t
   "Whether to kill buffers when done with them.
 If non-nil, kill a buffer unless it already existed before editing
-it with Emacs server.  If nil, kill only buffers as specified by
+it with the Emacs server.  If nil, kill only buffers as specified by
 `server-temp-file-regexp'.
-Please note that only buffers are killed that still have a client,
-i.e. buffers visited which \"emacsclient --no-wait\" are never killed in
+Please note that only buffers that still have a client are killed,
+i.e. buffers visited with \"emacsclient --no-wait\" are never killed in
 this way."
   :group 'server
   :type 'boolean
@@ -198,69 +202,20 @@ are done with it in the server.")
 
 (defvar server-name "server")
 
-(defvar server-socket-dir nil
-  "The directory in which to place the server socket.
-Initialized by `server-start'.")
-
-(defun server-client (proc)
-  "Return the Emacs client corresponding to PROC.
-PROC must be a process object.
-The car of the result is PROC; the cdr is an association list.
-See `server-client-get' and `server-client-set'."
-  (assq proc server-clients))
-
-(defun server-client-get (client property)
-  "Get the value of PROPERTY in CLIENT.
-CLIENT may be a process object, or a client returned by `server-client'.
-Return nil if CLIENT has no such property."
-  (or (listp client) (setq client (server-client client)))
-  (cdr (assq property (cdr client))))
-
-(defun server-client-set (client property value)
-  "Set the PROPERTY to VALUE in CLIENT, and return VALUE.
-CLIENT may be a process object, or a client returned by `server-client'."
-  (let (p proc)
-    (if (listp client)
-       (setq proc (car client))
-      (setq proc client
-           client (server-client client)))
-    (setq p (assq property client))
-    (cond
-     (p (setcdr p value))
-     (client (setcdr client (cons (cons property value) (cdr client))))
-     (t (setq server-clients
-             `((,proc (,property . ,value)) . ,server-clients))))
-    value))
+(defvar server-socket-dir (format "/tmp/emacs%d" (user-uid))
+  "The directory in which to place the server socket.")
 
 (defun server-clients-with (property value)
   "Return a list of clients with PROPERTY set to VALUE."
   (let (result)
-    (dolist (client server-clients result)
-      (when (equal value (server-client-get client property))
-       (setq result (cons (car client) result))))))
+    (dolist (proc server-clients result)
+      (when (equal value (process-get proc property))
+       (push proc result)))))
 
 (defun server-add-client (proc)
   "Create a client for process PROC, if it doesn't already have one.
 New clients have no properties."
-  (unless (server-client proc)
-    (setq server-clients (cons (cons proc nil)
-                              server-clients))))
-
-(defun server-getenv-from (env variable)
-  "Get the value of VARIABLE in ENV.
-VARIABLE should be a string.  Value is nil if VARIABLE is
-undefined in ENV.  Otherwise, value is a string.
-
-ENV should be in the same format as `process-environment'."
-  (let (entry result)
-    (while (and env (null result))
-      (setq entry (car env)
-           env (cdr env))
-      (if (and (> (length entry) (length variable))
-              (eq ?= (aref entry (length variable)))
-              (equal variable (substring entry 0 (length variable))))
-         (setq result (substring entry (+ (length variable) 1)))))
-    result))
+  (add-to-list 'server-clients proc))
 
 (defmacro server-with-environment (env vars &rest body)
   "Evaluate BODY with environment variables VARS set to those in ENV.
@@ -273,25 +228,21 @@ ENV should be in the same format as `process-environment'."
        (value (make-symbol "value")))
     `(let ((process-environment process-environment))
        (dolist (,var ,vars)
-        (let ((,value (server-getenv-from ,env ,var)))
+         (let ((,value (getenv-internal ,var ,env)))
            (push (if (null ,value)
                      ,var
                    (concat ,var "=" ,value))
                  process-environment)))
        (progn ,@body))))
 
-(defun server-delete-client (client &optional noframe)
-  "Delete CLIENT, including its buffers, terminals and frames.
+(defun server-delete-client (proc &optional noframe)
+  "Delete PROC, including its buffers, terminals and frames.
 If NOFRAME is non-nil, let the frames live.  (To be used from
 `delete-frame-functions'.)"
-  (server-log (concat "server-delete-client" (if noframe " noframe"))
-             client)
+  (server-log (concat "server-delete-client" (if noframe " noframe")) proc)
   ;; Force a new lookup of client (prevents infinite recursion).
-  (setq client (server-client
-               (if (listp client) (car client) client)))
-  (let ((proc (car client))
-       (buffers (server-client-get client 'buffers)))
-    (when client
+  (when (memq proc server-clients)
+    (let ((buffers (process-get proc 'buffers)))
 
       ;; Kill the client's buffers.
       (dolist (buf buffers)
@@ -323,31 +274,43 @@ If NOFRAME is non-nil, let the frames live.  (To be used from
            (set-frame-parameter frame 'client nil)
            (delete-frame frame))))
 
-      (setq server-clients (delq client server-clients))
+      (setq server-clients (delq proc server-clients))
 
       ;; Delete the client's tty.
-      (let ((terminal (server-client-get client 'terminal)))
-       (when (eq (terminal-live-p terminal) t)
+      (let ((terminal (process-get proc 'terminal)))
+       ;; Only delete the terminal if it is non-nil.
+       (when (and terminal (eq (terminal-live-p terminal) t))
          (delete-terminal terminal)))
 
       ;; Delete the client's process.
-      (if (eq (process-status (car client)) 'open)
-         (delete-process (car client)))
+      (if (eq (process-status proc) 'open)
+         (delete-process proc))
 
       (server-log "Deleted" proc))))
 
+(defvar server-log-time-function 'current-time-string
+  "Function to generate timestamps for `server-buffer'.")
+
+(defconst server-buffer " *server*"
+  "Buffer used internally by Emacs's server.
+One use is to log the I/O for debugging purposes (see `server-log'),
+the other is to provide a current buffer in which the process filter can
+safely let-bind buffer-local variables like `default-directory'.")
+
+(defvar server-log nil
+  "If non-nil, log the server's inputs and outputs in the `server-buffer'.")
+
 (defun server-log (string &optional client)
-  "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."
-  (when (get-buffer "*server*")
-    (with-current-buffer "*server*"
+  "If `server-log' is non-nil, log STRING to `server-buffer'.
+If CLIENT is non-nil, add a description of it to the logged message."
+  (when server-log
+    (with-current-buffer (get-buffer-create server-buffer)
       (goto-char (point-max))
-      (insert (current-time-string)
+      (insert (funcall server-log-time-function)
              (cond
-              ((null client) " ")
-              ((listp client) (format " %s: " (car client)))
-              (t (format " %s: " client)))
+               ((null client) " ")
+               ((listp client) (format " %s: " (car client)))
+               (t (format " %s: " client)))
              string)
       (or (bolp) (newline)))))
 
@@ -369,7 +332,10 @@ message."
 
 (defun server-select-display (display)
   ;; If the current frame is on `display' we're all set.
-  (unless (equal (frame-parameter (selected-frame) 'display) display)
+  ;; Similarly if we are unable to open a frames on other displays, there's
+  ;; nothing more we can do.
+  (unless (or (not (fboundp 'make-frame-on-display))
+              (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)
@@ -381,7 +347,7 @@ message."
                      display
                      ;; Make it display (and remember) some dummy buffer, so
                      ;; we can detect later if the frame is in use or not.
-                     `((server-dummmy-buffer . ,buffer)
+                     `((server-dummy-buffer . ,buffer)
                        ;; This frame may be deleted later (see
                        ;; server-unselect-display) so we want it to be as
                        ;; unobtrusive as possible.
@@ -427,7 +393,8 @@ message."
     (server-log (format "server-handle-suspend-tty, terminal %s" terminal) proc)
     (condition-case err
        (server-send-string proc "-suspend \n")
-      (file-error (condition-case nil (server-delete-client proc) (error nil))))))
+      (file-error                       ;The pipe/socket was closed.
+       (ignore-errors (server-delete-client proc))))))
 
 (defun server-unquote-arg (arg)
   "Remove &-quotation from ARG.
@@ -493,9 +460,6 @@ kill any existing server communications subprocess."
         (not server-clients)
         (yes-or-no-p
          "The current server still has clients; delete them? "))
-    ;; It is safe to get the user id now.
-    (setq server-socket-dir (or server-socket-dir
-                               (format "/tmp/emacs%d" (user-uid))))
     (when server-process
       ;; kill it dead!
       (ignore-errors (delete-process server-process)))
@@ -524,6 +488,7 @@ kill any existing server communications subprocess."
          (add-hook 'delete-frame-functions 'server-handle-delete-frame)
          (add-hook 'kill-buffer-query-functions 'server-kill-buffer-query-function)
          (add-hook 'kill-emacs-query-functions 'server-kill-emacs-query-function)
+         (add-hook 'kill-emacs-hook (lambda () (server-mode -1))) ;Cleanup upon exit.
          (setq server-process
                (apply #'make-network-process
                       :name server-name
@@ -533,9 +498,10 @@ kill any existing server communications subprocess."
                       :filter 'server-process-filter
                       ;; We must receive file names without being decoded.
                       ;; Those are decoded by server-process-filter according
-                      ;; to file-name-coding-system.
-                      :coding 'raw-text
-                      ;; The rest of the args depends on the kind of socket used.
+                      ;; to file-name-coding-system.  Also don't get
+                      ;; confused by CRs since we don't quote them.
+                      :coding 'raw-text-unix
+                      ;; The other args depend on the kind of socket used.
                       (if server-use-tcp
                           (list :family nil
                                 :service t
@@ -548,11 +514,11 @@ kill any existing server communications subprocess."
          (when server-use-tcp
            (let ((auth-key
                   (loop
-                   ;; The auth key is a 64-byte string of random chars in the
-                   ;; range `!'..`~'.
-                   for i below 64
-                   collect (+ 33 (random 94)) into auth
-                   finally return (concat auth))))
+                     ;; The auth key is a 64-byte string of random chars in the
+                     ;; range `!'..`~'.
+                     for i below 64
+                     collect (+ 33 (random 94)) into auth
+                     finally return (concat auth))))
              (process-put server-process :auth-key auth-key)
              (with-temp-file server-file
                (set-buffer-multibyte nil)
@@ -562,6 +528,21 @@ kill any existing server communications subprocess."
                        " " (int-to-string (emacs-pid))
                        "\n" auth-key)))))))))
 
+(defun server-running-p (&optional name)
+  "Test whether server NAME is running."
+  (interactive
+   (list (if current-prefix-arg
+            (read-string "Server name: " nil nil server-name))))
+  (unless name (setq name server-name))
+  (condition-case nil
+      (progn
+       (delete-process
+        (make-network-process
+         :name "server-client-test" :family 'local :server nil :noquery t
+         :service (expand-file-name name server-socket-dir)))
+       t)
+    (file-error nil)))
+
 ;;;###autoload
 (define-minor-mode server-mode
   "Toggle Server mode.
@@ -589,6 +570,7 @@ Server mode runs a process that accepts commands from the
                           (server-quote-arg text)))))))))
 
 (defun server-create-tty-frame (tty type proc)
+  (add-to-list 'frame-inherited-parameters 'client)
   (let ((frame
          (server-with-environment (process-get proc 'env)
              '("LANG" "LC_CTYPE" "LC_ALL"
@@ -596,22 +578,32 @@ Server mode runs a process that accepts commands from the
                "BAUDRATE" "COLUMNS" "ESCDELAY" "HOME" "LINES"
                "NCURSES_ASSUMED_COLORS" "NCURSES_NO_PADDING"
                "NCURSES_NO_SETBUF" "TERM" "TERMCAP" "TERMINFO"
-               "TERMINFO_DIRS" "TERMPATH" 
+               "TERMINFO_DIRS" "TERMPATH"
                ;; rxvt wants these
                "COLORFGBG" "COLORTERM")
            (make-frame-on-tty tty type
                               ;; Ignore nowait here; we always need to
                               ;; clean up opened ttys when the client dies.
                               `((client . ,proc)
-                                (environment . ,(process-get proc 'env))))))
-        (client (server-client proc)))
-  
-    (set-frame-parameter frame 'display-environment-variable
-                         (server-getenv-from (process-get proc 'env) "DISPLAY"))
+                                ;; This is a leftover from an earlier
+                                ;; attempt at making it possible for process
+                                ;; run in the server process to use the
+                                ;; environment of the client process.
+                                ;; It has no effect now and to make it work
+                                ;; we'd need to decide how to make
+                                ;; process-environment interact with client
+                                ;; envvars, and then to change the
+                                ;; C functions `child_setup' and
+                                ;; `getenv_internal' accordingly.
+                                (environment . ,(process-get proc 'env)))))))
+
+    ;; ttys don't use the `display' parameter, but callproc.c does to set
+    ;; the DISPLAY environment on subprocesses.
+    (set-frame-parameter frame 'display
+                         (getenv-internal "DISPLAY" (process-get proc 'env)))
     (select-frame frame)
-    (server-client-set client 'frame frame)
-    (server-client-set client 'tty (terminal-name frame))
-    (server-client-set client 'terminal (frame-terminal frame))
+    (process-put proc 'frame frame)
+    (process-put proc 'terminal (frame-terminal frame))
 
     ;; Display *scratch* by default.
     (switch-to-buffer (get-buffer-create "*scratch*") 'norecord)
@@ -622,7 +614,8 @@ Server mode runs a process that accepts commands from the
     frame))
 
 (defun server-create-window-system-frame (display nowait proc)
-  (if (not (fboundp 'x-create-frame))
+  (add-to-list 'frame-inherited-parameters 'client)
+  (if (not (fboundp 'make-frame-on-display))
       (progn
         ;; This emacs does not support X.
         (server-log "Window system unsupported" proc)
@@ -634,25 +627,18 @@ Server mode runs a process that accepts commands from the
     ;; `server-save-buffers-kill-terminal' from unexpectedly
     ;; killing emacs on that frame.
     (let* ((params `((client . ,(if nowait 'nowait proc))
+                     ;; This is a leftover, see above.
                      (environment . ,(process-get proc 'env))))
            (frame (make-frame-on-display
                    (or display
                        (frame-parameter nil 'display)
                        (getenv "DISPLAY")
                        (error "Please specify display"))
-                   params))
-           (client (server-client proc)))
+                   params)))
       (server-log (format "%s created" frame) proc)
-      ;; XXX We need to ensure the parameters are
-      ;; really set because Emacs forgets unhandled
-      ;; initialization parameters for X frames at
-      ;; the moment.
-      (modify-frame-parameters frame params)
-      (set-frame-parameter frame 'display-environment-variable 
-                           (server-getenv-from (process-get proc 'env) "DISPLAY"))
       (select-frame frame)
-      (server-client-set client 'frame frame)
-      (server-client-set client 'terminal (frame-terminal frame))
+      (process-put proc 'frame frame)
+      (process-put proc 'terminal (frame-terminal frame))
 
       ;; Display *scratch* by default.
       (switch-to-buffer (get-buffer-create "*scratch*") 'norecord)
@@ -753,7 +739,7 @@ The following commands are accepted by the server:
   on this tty until it gets a -resume command.
 
 `-resume'
-  Resume this tty frame. The client sends this string when it
+  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.
 
@@ -769,9 +755,8 @@ The following commands are accepted by the client:
   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.
+  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
@@ -781,12 +766,12 @@ The following commands are accepted by the client:
   Signal an error (but continue processing).
 
 `-suspend'
-  Suspend this terminal, i.e., stop the client process.  Sent
-  when the user presses C-z."
+  Suspend this terminal, i.e., stop the client process.
+  Sent when the user presses C-z."
   (server-log (concat "Received " string) proc)
   ;; First things first: let's check the authentication
   (unless (process-get proc :authenticated)
-    (if (and (string-match "-auth \\(.*?\\)\n" string)
+    (if (and (string-match "-auth \\([!-~]+\\)\n?" string)
             (equal (match-string 1 string) (process-get proc :auth-key)))
        (progn
          (setq string (substring string (match-end 0)))
@@ -809,7 +794,7 @@ The following commands are accepted by the client:
             ;; Save for later any partial line that remains.
             (when (> (length string) 0)
               (process-put proc 'previous-string string))
-            
+
           ;; In earlier versions of server.el (where we used an `emacsserver'
           ;; process), there could be multiple lines.  Nowadays this is not
           ;; supported any more.
@@ -818,7 +803,6 @@ The following commands are accepted by the client:
                (coding-system (and default-enable-multibyte-characters
                                    (or file-name-coding-system
                                        default-file-name-coding-system)))
-               (client (server-client proc))
                nowait ; t if emacsclient does not want to wait for us.
                frame ; The frame that was opened for the client (if any).
                display              ; Open the frame on this display.
@@ -828,18 +812,18 @@ The following commands are accepted by the client:
                 (tty-name nil)       ;nil, `window-system', or the tty name.
                 tty-type             ;string.
                (files nil)
-               (lineno 1)
-               (columnno 0))
+                (filepos nil)
+               command-line-args-left
+               arg)
            ;; Remove this line from STRING.
            (setq string (substring string (match-end 0)))
-           (while (string-match " *[^ ]* " request)
-             (let ((arg (substring request (match-beginning 0)
-                                    (1- (match-end 0)))))
-               (setq request (substring request (match-end 0)))
+           (setq command-line-args-left
+                 (mapcar 'server-unquote-arg (split-string request " " t)))
+           (while (setq arg (pop command-line-args-left))
                (cond
                 ;; -version CLIENT-VERSION: obsolete at birth.
-                ((and (equal "-version" arg) (string-match "[^ ]+ " request))
-                  (setq request (substring request (match-end 0))))
+                ((and (equal "-version" arg) command-line-args-left)
+                 (pop command-line-args-left))
 
                 ;; -nowait:  Emacsclient won't wait for a result.
                 ((equal "-nowait" arg) (setq nowait t))
@@ -849,10 +833,9 @@ The following commands are accepted by the client:
 
                 ;; -display DISPLAY:
                 ;; Open X frames on the given display instead of the default.
-                ((and (equal "-display" arg)
-                       (string-match "\\([^ ]*\\) " request))
-                 (setq display (match-string 1 request)
-                       request (substring request (match-end 0))))
+                ((and (equal "-display" arg) command-line-args-left)
+                 (setq display (pop command-line-args-left))
+                  (if (zerop (length display)) (setq display nil)))
 
                 ;; -window-system:  Open a new X frame.
                 ((equal "-window-system" arg)
@@ -861,7 +844,7 @@ The following commands are accepted by the client:
 
                 ;; -resume:  Resume a suspended tty frame.
                 ((equal "-resume" arg)
-                 (lexical-let ((terminal (server-client-get client 'terminal)))
+                 (lexical-let ((terminal (process-get proc 'terminal)))
                    (setq dontkill t)
                     (push (lambda ()
                             (when (eq (terminal-live-p terminal) t)
@@ -872,7 +855,7 @@ The following commands are accepted by the client:
                 ;; get out of sync, and a C-z sends a SIGTSTP to
                 ;; emacsclient.)
                 ((equal "-suspend" arg)
-                 (lexical-let ((terminal (server-client-get client 'terminal)))
+                 (lexical-let ((terminal (process-get proc 'terminal)))
                    (setq dontkill t)
                     (push (lambda ()
                             (when (eq (terminal-live-p terminal) t)
@@ -881,74 +864,67 @@ The following commands are accepted by the client:
 
                 ;; -ignore COMMENT:  Noop; useful for debugging emacsclient.
                 ;; (The given comment appears in the server log.)
-                ((and (equal "-ignore" arg) (string-match "[^ ]* " request))
-                 (setq dontkill t
-                       request (substring request (match-end 0))))
+                ((and (equal "-ignore" arg) command-line-args-left
+                 (setq dontkill t)
+                 (pop command-line-args-left)))
 
                 ;; -tty DEVICE-NAME TYPE:  Open a new tty frame at the client.
                 ((and (equal "-tty" arg)
-                       (string-match "\\([^ ]*\\) \\([^ ]*\\) " request))
-                  (setq tty-name (match-string 1 request))
-                  (setq tty-type (match-string 2 request))
-                  (setq dontkill t)
-                  (setq request (substring request (match-end 0))))
+                       (cdr command-line-args-left))
+                  (setq tty-name (pop command-line-args-left)
+                       tty-type (pop command-line-args-left)
+                       dontkill t))
 
                 ;; -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 lineno (string-to-number (match-string 1 request))
-                       columnno (if (null (match-end 2)) 0
-                                   (string-to-number (match-string 2 request)))
-                       request (substring request (match-end 0))))
+                      command-line-args-left
+                       (string-match "\\+\\([0-9]+\\)\\(?::\\([0-9]+\\)\\)?"
+                                     (car command-line-args-left)))
+                 (setq arg (pop command-line-args-left))
+                 (setq filepos
+                        (cons (string-to-number (match-string 1 arg))
+                              (string-to-number (or (match-string 2 arg) "")))))
 
                 ;; -file FILENAME:  Load the given file.
                 ((and (equal "-file" arg)
-                       (string-match "\\([^ ]+\\) " request))
-                 (let ((file (server-unquote-arg (match-string 1 request))))
-                   (setq request (substring request (match-end 0)))
+                      command-line-args-left)
+                 (let ((file (pop command-line-args-left)))
                    (if coding-system
                        (setq file (decode-coding-string file coding-system)))
                    (setq file (command-line-normalize-file-name file))
-                   (push (list file lineno columnno) files)
-                   (server-log (format "New file: %s (%d:%d)"
-                                        file lineno columnno) proc))
-                 (setq lineno 1
-                       columnno 0))
+                   (push (cons file filepos) files)
+                   (server-log (format "New file: %s %s"
+                                        file (or filepos "")) proc))
+                 (setq filepos nil))
 
                 ;; -eval EXPR:  Evaluate a Lisp expression.
                 ((and (equal "-eval" arg)
-                       (string-match "\\([^ ]+\\) " request))
-                 (lexical-let ((expr (server-unquote-arg
-                                       (match-string 1 request))))
-                   (setq request (substring request (match-end 0)))
+                       command-line-args-left)
+                 (lexical-let ((expr (pop command-line-args-left)))
                    (if coding-system
                        (setq expr (decode-coding-string expr coding-system)))
                     (push (lambda () (server-eval-and-print expr proc))
                           commands)
-                   (setq lineno 1
-                         columnno 0)))
+                   (setq filepos nil)))
 
                 ;; -env NAME=VALUE:  An environment variable.
-                ((and (equal "-env" arg) (string-match "\\([^ ]+\\) " request))
-                 (let ((var (server-unquote-arg (match-string 1 request))))
+                ((and (equal "-env" arg) command-line-args-left)
+                 (let ((var (pop command-line-args-left)))
                    ;; XXX Variables should be encoded as in getenv/setenv.
-                   (setq request (substring request (match-end 0)))
                     (process-put proc 'env
                                  (cons var (process-get proc 'env)))))
 
                 ;; -dir DIRNAME:  The cwd of the emacsclient process.
-                ((and (equal "-dir" arg) (string-match "\\([^ ]+\\) " request))
-                 (setq dir (server-unquote-arg (match-string 1 request)))
-                 (setq request (substring request (match-end 0)))
+                ((and (equal "-dir" arg) command-line-args-left)
+                 (setq dir (pop command-line-args-left))
                  (if coding-system
                      (setq dir (decode-coding-string dir coding-system)))
                  (setq dir (command-line-normalize-file-name dir)))
 
                 ;; Unknown command.
-                (t (error "Unknown command: %s" arg)))))
-            
+                (t (error "Unknown command: %s" arg))))
+
             (setq frame
                   (case tty-name
                     ((nil) (if display (server-select-display display)))
@@ -956,17 +932,25 @@ The following commands are accepted by the client:
                      (server-create-window-system-frame display nowait proc))
                     (t (server-create-tty-frame tty-name tty-type proc))))
 
-            (process-put proc 'continuation
-                         (lexical-let ((proc proc)
-                                       (files files)
-                                       (nowait nowait)
-                                       (commands commands)
-                                       (dontkill dontkill)
-                                       (frame frame)
-                                       (tty-name tty-name))
-                           (lambda ()
-                             (server-execute proc files nowait commands
-                                             dontkill frame tty-name))))
+            (process-put
+             proc 'continuation
+             (lexical-let ((proc proc)
+                           (files files)
+                           (nowait nowait)
+                           (commands commands)
+                           (dontkill dontkill)
+                           (frame frame)
+                           (dir dir)
+                           (tty-name tty-name))
+               (lambda ()
+                 (with-current-buffer (get-buffer-create server-buffer)
+                   ;; Use the same cwd as the emacsclient, if possible, so
+                   ;; relative file names work correctly, even in `eval'.
+                   (let ((default-directory
+                         (if (and dir (file-directory-p dir))
+                             dir default-directory)))
+                     (server-execute proc files nowait commands
+                                     dontkill frame tty-name))))))
 
             (when (or frame files)
               (server-goto-toplevel proc))
@@ -977,15 +961,14 @@ The following commands are accepted by the client:
 
 (defun server-execute (proc files nowait commands dontkill frame tty-name)
   (condition-case err
-      (let* ((client (server-client proc))
-             (buffers
+      (let* ((buffers
               (when files
                 (run-hooks 'pre-command-hook)
-                (prog1 (server-visit-files files client nowait)
+                (prog1 (server-visit-files files proc nowait)
                   (run-hooks 'post-command-hook)))))
 
         (mapc 'funcall (nreverse commands))
-              
+
         ;; Delete the client if necessary.
         (cond
          (nowait
@@ -1020,19 +1003,20 @@ The following commands are accepted by the client:
     (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-goto-line-column (line-col)
+  "Move point to the position indicated in LINE-COL.
+LINE-COL should be a pair (LINE . COL)."
+  (when line-col
+    (goto-line (car line-col))
+    (let ((column-number (cdr line-col)))
+      (when (> column-number 0)
+        (move-to-column (1- column-number))))))
 
-(defun server-visit-files (files client &optional nowait)
+(defun server-visit-files (files proc &optional nowait)
   "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.
+FILES is an alist whose elements are (FILENAME . FILEPOS)
+where FILEPOS can be nil or a pair (LINENUMBER . COLUMNNUMBER).
+PROC 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.
@@ -1050,31 +1034,29 @@ so don't mark these buffers specially, just visit them normally."
               (filen (car file))
               (obuf (get-file-buffer filen)))
          (add-to-history 'file-name-history filen)
-         (if (and obuf (set-buffer obuf))
-             (progn
-               (cond ((file-exists-p filen)
-                      (when (not (verify-visited-file-modtime obuf))
-                        (revert-buffer t nil)))
-                     (t
-                      (when (y-or-n-p
-                             (concat "File no longer exists: " filen
-                                     ", write buffer to file? "))
-                        (write-file filen))))
-               (unless server-buffer-clients
-                 (setq server-existing-buffer t))
-               (server-goto-line-column file))
-           (set-buffer (find-file-noselect filen))
-           (server-goto-line-column file)
-           (run-hooks 'server-visit-hook)))
+         (if (null obuf)
+              (set-buffer (find-file-noselect filen))
+            (set-buffer obuf)
+            (cond ((file-exists-p filen)
+                   (when (not (verify-visited-file-modtime obuf))
+                     (revert-buffer t nil)))
+                  (t
+                   (when (y-or-n-p
+                          (concat "File no longer exists: " filen
+                                  ", write buffer to file? "))
+                     (write-file filen))))
+            (unless server-buffer-clients
+              (setq server-existing-buffer t)))
+          (server-goto-line-column (cdr file))
+          (run-hooks 'server-visit-hook))
        (unless nowait
          ;; When the buffer is killed, inform the clients.
          (add-hook 'kill-buffer-hook 'server-kill-buffer nil t)
-         (push (car client) server-buffer-clients))
+         (push proc server-buffer-clients))
        (push (current-buffer) client-record)))
     (unless nowait
-      (server-client-set
-       client 'buffers
-       (nconc (server-client-get client 'buffers) client-record)))
+      (process-put proc 'buffers
+                   (nconc (process-get proc 'buffers) client-record)))
     client-record))
 \f
 (defun server-buffer-done (buffer &optional for-killing)
@@ -1086,23 +1068,23 @@ a temp file).
 FOR-KILLING if non-nil indicates that we are called from `kill-buffer'."
   (let ((next-buffer nil)
        (killed nil))
-    (dolist (client server-clients)
-      (let ((buffers (server-client-get client 'buffers)))
+    (dolist (proc server-clients)
+      (let ((buffers (process-get proc 'buffers)))
        (or next-buffer
            (setq next-buffer (nth 1 (memq buffer buffers))))
        (when buffers                   ; Ignore bufferless clients.
          (setq buffers (delq buffer buffers))
-         ;; Delete all dead buffers from CLIENT.
+         ;; Delete all dead buffers from PROC.
          (dolist (b buffers)
            (and (bufferp b)
                 (not (buffer-live-p b))
                 (setq buffers (delq b buffers))))
-         (server-client-set client 'buffers buffers)
+         (process-put proc 'buffers buffers)
          ;; If client now has no pending buffers,
          ;; tell it that it is done, and forget it entirely.
          (unless buffers
-           (server-log "Close" client)
-           (server-delete-client client)))))
+           (server-log "Close" proc)
+           (server-delete-client proc)))))
     (when (and (bufferp buffer) (buffer-name buffer))
       ;; We may or may not kill this buffer;
       ;; if we do, do not call server-buffer-done recursively
@@ -1171,19 +1153,19 @@ specifically for the clients and did not exist before their request for it."
   (or (not server-buffer-clients)
       (let ((res t))
        (dolist (proc server-buffer-clients res)
-         (let ((client (server-client proc)))
-           (when (and client (eq (process-status proc) 'open))
-             (setq res nil)))))
+          (when (and (memq proc server-clients)
+                     (eq (process-status proc) 'open))
+            (setq res nil))))
       (yes-or-no-p (format "Buffer `%s' still has clients; kill it? "
                           (buffer-name (current-buffer))))))
 
 (defun server-kill-emacs-query-function ()
-  "Ask before exiting Emacs it has live clients."
+  "Ask before exiting Emacs if it has live clients."
   (or (not server-clients)
       (let (live-client)
-       (dolist (client server-clients live-client)
-         (when (memq t (mapcar 'buffer-live-p (server-client-get
-                                               client 'buffers)))
+       (dolist (proc server-clients live-client)
+         (when (memq t (mapcar 'buffer-live-p (process-get
+                                               proc 'buffers)))
            (setq live-client t))))
       (yes-or-no-p "This Emacs session has clients; exit anyway? ")))
 
@@ -1236,10 +1218,12 @@ done that."
       (progn
        (let ((rest server-clients))
          (while (and rest (not next-buffer))
-           (let ((client (car rest)))
-             ;; Only look at frameless clients.
-             (when (not (server-client-get client 'frame))
-               (setq next-buffer (car (server-client-get client 'buffers))))
+           (let ((proc (car rest)))
+             ;; Only look at frameless clients, or those in the selected
+             ;; frame.
+             (when (or (not (process-get proc 'frame))
+                       (eq (process-get proc 'frame) (selected-frame)))
+               (setq next-buffer (car (process-get proc 'buffers))))
              (setq rest (cdr rest)))))
        (and next-buffer (server-switch-buffer next-buffer killed-one))
        (unless (or next-buffer killed-one (window-dedicated-p (selected-window)))
@@ -1286,35 +1270,39 @@ done that."
 
 ;;;###autoload
 (defun server-save-buffers-kill-terminal (proc &optional arg)
+  ;; Called from save-buffers-kill-terminal in files.el.
   "Offer to save each buffer, then kill PROC.
 
 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."
-  (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-terminal occasionally calls us with proc set
+  ;; to `nowait' (comes from the value of the `client' frame parameter).
+  (when (processp proc)
+    (let ((buffers (process-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))))
 
 (define-key ctl-x-map "#" 'server-edit)
 
-(defun server-unload-hook ()
+(defun server-unload-function ()
   "Unload the server library."
   (server-mode -1)
-  (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 'kill-emacs-hook (lambda () (server-mode -1))) ;Cleanup upon exit.
-(add-hook 'server-unload-hook 'server-unload-hook)
+  (substitute-key-definition 'server-edit nil ctl-x-map)
+  (save-current-buffer
+    (dolist (buffer (buffer-list))
+      (set-buffer buffer)
+      (remove-hook 'kill-buffer-hook 'server-kill-buffer t)))
+  ;; continue standard unloading
+  nil)
+
 \f
 (provide 'server)