From xemacs-m  Sun May 18 18:11:31 1997
Received: from jagor.srce.hr (hniksic@jagor.srce.hr [161.53.2.130])
	by xemacs.org (8.8.5/8.8.5) with ESMTP id SAA12567
	for <xemacs-beta@xemacs.org>; Sun, 18 May 1997 18:11:29 -0500 (CDT)
Received: (from hniksic@localhost)
          by jagor.srce.hr (8.8.5/8.8.4)
	  id BAA08078; Mon, 19 May 1997 01:11:28 +0200 (MET DST)
To: XEmacs Developers <xemacs-beta@xemacs.org>
Cc: jumper@lens.sri.com (Greg Jumper)
Subject: gnuclient/gnuserv update
X-Save-Project-Gutenberg: <URL:http://www.promo.net/pg/nl/pgny_nov96.html>
X-Attribution: Hrv
X-Face: Mie8:rOV<\c/~z{s.X4A{!?vY7{drJ([U]0O=W/<W*SMo/Mv:58:*_y~ki>xDi&N7XG
        KV^$k0m3Oe/)'e%3=$PCR&3ITUXH,cK>]bci&<qQ>Ff%x_>1`T(+M2Gg/fgndU%k*ft
        [(7._6e0n-V%|%'[c|q:;}td$#INd+;?!-V=c8Pqf}3J
X-Kibo-Says: The Void is unreal.
From: Hrvoje Niksic <hniksic@srce.hr>
Date: 19 May 1997 01:11:27 +0200
Message-ID: <kigvi4g8jfk.fsf@jagor.srce.hr>
Lines: 1123
X-Mailer: Gnus v5.4.52/XEmacs 20.2

Here is a new update to gnuclient.c.  The news since last time is that 
I got rid of gnudoit.c, too.  The options are now:

gnuclient [options] [files...]

default: X, if DISPLAY is present, otherwise TTY

-nw           -> force TTY
-display DISP -> force X (and specify DISP as display)
-q            -> "quick" load (exits immediately if on X)
-v            -> view instead of find

-l LIBRARY    -> load LIBRARY prior to loading files
-eval FORM    -> evaluate FORM prior to loading files
-f FUNCTION   -> call FUNCTION prior to loading files
-batch        -> just evaluate all, and print result to stdout

files:   [[+line] file] .....

gnudoit and gnuattach are now both obsolete.  The old gnuattach
behaviour is obtained through `gnuclient -nw' and the old gnudoit
behaviour through:

gnuclient -eval FORM -batch

since that's quite a mouthful, I put up a simple script that does it.

gnuserv.el itself now has a load of hooks, should be very intelligent
about what to do with buffers, frames, devices, consoles, and the
World As We Know It.  Oh, really.

This patch is against 20.3-b1.  Please try it out -- I don't think
you'll regret it.

The patch against 20.3 follows:

--- lib-src/Makefile.in.in.orig	Sun May 18 21:23:05 1997
+++ lib-src/Makefile.in.in	Sun May 18 22:14:14 1997
@@ -105,7 +105,7 @@
 
 /* Things that a user might actually run,
    which should be installed in bindir. */
-INSTALLABLES = etags ctags b2m gnuclient gnuattach gnudoit
+INSTALLABLES = etags ctags b2m gnuclient gnudoit
 INSTALLABLE_SCRIPTS = rcs-checkin pstogif install-sid send-pr
 
 /* Things that Emacs runs internally, or during the build process,
@@ -489,10 +489,6 @@
 	$(CC) -c ${cflags_gnuserv} ${srcdir}/gnuslib.c
 gnuclient: ${srcdir}/gnuclient.c gnuslib.o ${srcdir}/gnuserv.h 
 	$(CC) ${cflags_gnuserv} -o gnuclient ${srcdir}/gnuclient.c gnuslib.o $(ldflags_gnuserv)
-gnuattach: ${srcdir}/gnuclient.c gnuslib.o ${srcdir}/gnuserv.h gnuclient
-	$(CC) ${cflags_gnuserv} -o gnuattach -DGNUATTACH ${srcdir}/gnuclient.c gnuslib.o $(ldflags_gnuserv)
-gnudoit: ${srcdir}/gnudoit.c gnuslib.o ${srcdir}/gnuserv.h 
-	$(CC) ${cflags_gnuserv} -o gnudoit ${srcdir}/gnudoit.c gnuslib.o $(ldflags_gnuserv)
 gnuserv: ${srcdir}/gnuserv.c gnuslib.o ${srcdir}/gnuserv.h
 	$(CC) ${cflags_gnuserv} -o gnuserv ${srcdir}/gnuserv.c gnuslib.o $(ldflags_gnuserv)
 
--- /dev/null	Mon May 19 01:10:05 1997
+++ lib-src/gnudoit	Mon May 19 01:10:38 1997
@@ -0,0 +1,11 @@
+#! /bin/sh
+
+if [ $# -eq 0 ]
+then
+   echo "$0: arguments expected" >&2
+   exit 1
+fi
+
+# I use "$*" instead of "$@" intentionally -- I don't want to have the
+# arguments split.
+gnuclient -batch -eval "$*"
--- lisp/packages/gnuserv.el.orig	Mon May 19 01:03:58 1997
+++ lisp/packages/gnuserv.el	Mon May 19 01:04:04 1997
@@ -1,7 +1,7 @@
 ;;; gnuserv.el --- Lisp interface code between Emacs and gnuserv
 ;; Copyright (C) 1989-1997 Free Software Foundation, Inc.
 
-;; Version: 3.1
+;; Version: 3.2
 ;; Author: Andy Norman (ange@hplb.hpl.hp.com), originally based on server.el
 ;;         Hrvoje Niksic <hniksic@srce.hr>
 ;; Keywords: environment, processes, terminals
@@ -154,6 +154,16 @@
   :type 'hook
   :group 'gnuserv)
 
+(defcustom gnuserv-init-hook nil
+  "*Hook run after the server is started."
+  :type 'hook
+  :group 'gnuserv)
+
+(defcustom gnuserv-shutdown-hook nil
+  "*Hook run before the server exits."
+  :type 'hook
+  :group 'gnuserv)
+
 (defcustom gnuserv-kill-quietly nil
   "*Non-nil means to kill buffers with clients attached without requiring confirmation."
   :type 'boolean
@@ -230,7 +240,8 @@
 ;; identification, so we'll make a "minor mode".
 (defvar gnuserv-minor-mode nil)
 (make-variable-buffer-local 'gnuserv-mode)
-(pushnew '(gnuserv-minor-mode " Server") minor-mode-alist)
+(pushnew '(gnuserv-minor-mode " Server") minor-mode-alist
+	  :test 'equal)
 
 
 ;; Sample gnuserv-frame functions
@@ -263,14 +274,45 @@
 
 ;;; Communication functions
 
+;; We used to restart the server here, but it's too risky -- if
+;; something goes awry, it's too easy to wind up in a loop.
 (defun gnuserv-sentinel (proc msg)
   (case (process-status proc)
-    (exit (message "Gnuserv subprocess exited; restarting")
-	  ;; This will also kill all the existing clients.
-	  (gnuserv-start-1))
-    (closed (message "Gnuserv subprocess closed"))
-    (signal (message "Gnuserv subprocess killed"))))
-
+    (exit
+     (message
+      (substitute-command-keys
+       "Gnuserv subprocess exited; restart with `\\[gnuserv-start]'"))
+     (gnuserv-prepare-shutdown))
+    (signal
+     (message
+      (substitute-command-keys
+       "Gnuserv subprocess killed; restart with `\\[gnuserv-start]'"))
+     (gnuserv-prepare-shutdown))
+    (closed
+     (message
+      (substitute-command-keys
+       "Gnuserv subprocess closed; restart with `\\[gnuserv-start]'"))
+     (gnuserv-prepare-shutdown))))
+
+;; This function reads client requests from our current server.  Every
+;; client is identified by a unique ID within the server
+;; (incidentally, the same ID is the file descriptor the server uses
+;; to communicate to client).
+;;
+;; The request string can arrive in several chunks.  As the request
+;; ends with \C-d, we check for that character at the end of string.
+;; If not found, keep reading, and concatenating to former strings.
+;; So, if at first read we receive "5 (gn", that text will be stored
+;; to gnuserv-string.  If we then receive "us)\C-d", the two will be
+;; concatenated, `current-client' will be set to 5, and `(gnus)' form
+;; will be evaluated.
+;;
+;; Server will send the following:
+;;
+;; "ID <text>\C-d"  (no quotes)
+;;
+;;  ID    - file descriptor of the given client;
+;; <text> - the actual contents of the request.
 (defun gnuserv-process-filter (proc string)
   "Process gnuserv client requests to execute Emacs commands."
   (setq gnuserv-string (concat gnuserv-string string))
@@ -298,6 +340,16 @@
 	   (error "%s: invalid response from gnuserv" gnuserv-string)
 	   (setq gnuserv-string "")))))
 
+;; This function is somewhat of a misnomer.  Actually, we write to the
+;; server (using `process-send-string' to gnuserv-process), which
+;; interprets what we say and forwards it to the client.  The
+;; incantation server understands is (from gnuserv.c):
+;;
+;; "FD/LEN:<text>\n"  (no quotes)
+;;    FD     - file descriptor of the given client (which we obtained from
+;;             the server earlier);
+;;    LEN    - length of the stuff we are about to send;
+;;    <text> - the actual contents of the request.
 (defun gnuserv-write-to-client (client-id form)
   "Write the given form to the given client via the gnuserv process."
   (when (eq (process-status gnuserv-process) 'run)
@@ -306,7 +358,6 @@
 			   (length result) result)))
       (process-send-string gnuserv-process s))))
 
-
 ;; The following two functions are helper functions, used by
 ;; gnuclient.
 
@@ -325,82 +376,90 @@
 
 ;; "Execute" a client connection, called by gnuclient.  This is the
 ;; backbone of gnuserv.el.
-(defun gnuserv-edit-files (type list &optional flags)
+(defun gnuserv-edit-files (type list &rest flags)
   "For each (line-number . file) pair in LIST, edit the file at line-number.
 The visited buffers are memorized, so that when \\[gnuserv-edit] is invoked
 in such a buffer, or when it is killed, or the client's device deleted, the
 client will be invoked that the edit is finished.
 
 TYPE should either be a (tty TTY TERM PID) list, or (x DISPLAY) list.
-If FLAGS is `quick', just edit the files in Emacs.
-If FLAGS is `view', view the files read-only."
-  (or (not flags)
-      (memq flags '(quick view))
-      (error "Invalid flag %s" flags))
-  (let* ((old-device-num (length (device-list)))
-	 (new-frame nil)
-	 (dest-frame (if (functionp gnuserv-frame)
-			 (funcall gnuserv-frame (car type))
-		       gnuserv-frame))
-	 ;; The gnuserv-frame dependencies are ugly.
-	 (device (cond ((frame-live-p dest-frame)
-			(frame-device dest-frame))
-		       ((null dest-frame)
-			(case (car type)
-			  (tty (apply 'make-tty-device (cdr type)))
-			  (x   (make-x-device (cadr type)))
-			  (t   (error "Invalid device type"))))
-		       (t
-			(selected-device))))
-	 (frame (cond ((frame-live-p dest-frame)
-		       dest-frame)
-		      ((null dest-frame)
-		       (setq new-frame (make-frame nil device))
-		       new-frame)
-		      (t (selected-frame))))
-	 (client (make-gnuclient :id gnuserv-current-client
-				 :device device
-				 :frame new-frame)))
-    (setq gnuserv-current-client nil)
-    ;; If the device was created by this client, push it to the list.
-    (and (/= old-device-num (length (device-list)))
-	 (push device gnuserv-devices))
-    ;; Visit all the listed files.
-    (while list
-      (let ((line (caar list)) (path (cdar list)))
-	(select-frame frame)
-	;; Visit the file.
-	(funcall (if (eq flags 'view)
-		     gnuserv-view-file-function
-		   gnuserv-find-file-function)
-		 path)
-	(goto-line line)
-	(run-hooks 'gnuserv-visit-hook)
-	;; Don't memorize the quick and view buffers.
-	(when (null flags)
-	  (pushnew (current-buffer) (gnuclient-buffers client))
-	  (setq gnuserv-minor-mode t))
-	(pop list)))
-    (cond ((and flags (device-on-window-system-p device))
-	   ;; Exit if on X device, and quick or view.	   
-	   ;; NOTE: if the client is to finish now, it must absolutely
-	   ;; /not/ be included to the list of clients.  This way the
-	   ;; client-ids should be unique.
-	   (gnuserv-write-to-client (gnuclient-id client) nil))
-	  (t
-	   ;; Else, the client gets a vote.
-	   (push client gnuserv-clients)
-	   ;; Explain buffer exit options.  If dest-frame is nil, the
-	   ;; user can exit via `delete-frame'.  OTOH, if FLAGS are
-	   ;; nil and there are some buffers, the user can exit via
-	   ;; `gnuserv-edit'.
-	   (if (and (null flags)
-		    (gnuclient-buffers client))
-	       (message (substitute-command-keys
-			 "Type `\\[gnuserv-edit]' to finish editing"))
-	     (or dest-frame
-		 (message (substitute-command-keys
-			   "Type `\\[delete-frame]' to finish editing"))))))))
+If a flag is `quick', just edit the files in Emacs.
+If a flag is `view', view the files read-only."
+  (let (quick view)
+    (mapc (lambda (flag)
+	    (case flag
+	      (quick (setq quick t))
+	      (view  (setq view t))
+	      (t     (error "Invalid flag %s" flag))))
+	  flags)
+    (let* ((old-device-num (length (device-list)))
+	   (new-frame nil)
+	   (dest-frame (if (functionp gnuserv-frame)
+			   (funcall gnuserv-frame (car type))
+			 gnuserv-frame))
+	   ;; The gnuserv-frame dependencies are ugly.
+	   (device (cond ((frame-live-p dest-frame)
+			  (frame-device dest-frame))
+			 ((null dest-frame)
+			  (case (car type)
+			    (tty (apply 'make-tty-device (cdr type)))
+			    (x   (make-x-device (cadr type)))
+			    (t   (error "Invalid device type"))))
+			 (t
+			  (selected-device))))
+	   (frame (cond ((frame-live-p dest-frame)
+			 dest-frame)
+			((null dest-frame)
+			 (setq new-frame (make-frame nil device))
+			 new-frame)
+			(t (selected-frame))))
+	   (client (make-gnuclient :id gnuserv-current-client
+				   :device device
+				   :frame new-frame)))
+      (setq gnuserv-current-client nil)
+      ;; If the device was created by this client, push it to the list.
+      (and (/= old-device-num (length (device-list)))
+	   (push device gnuserv-devices))
+      (and (frame-iconified-p frame)
+	   (deiconify-frame frame))
+      ;; Visit all the listed files.
+      (while list
+	(let ((line (caar list)) (path (cdar list)))
+	  (select-frame frame)
+	  ;; Visit the file.
+	  (funcall (if view
+		       gnuserv-view-file-function
+		     gnuserv-find-file-function)
+		   path)
+	  (goto-line line)
+	  (run-hooks 'gnuserv-visit-hook)
+	  ;; Don't memorize the quick and view buffers.
+	  (unless (or quick view)
+	    (pushnew (current-buffer) (gnuclient-buffers client))
+	    (setq gnuserv-minor-mode t))
+	  (pop list)))
+      (cond
+       ((and (or quick view)
+	     (device-on-window-system-p device))
+	;; Exit if on X device, and quick or view.  NOTE: if the
+	;; client is to finish now, it must absolutely /not/ be
+	;; included to the list of clients.  This way the client-ids
+	;; should be unique.
+	(gnuserv-write-to-client (gnuclient-id client) nil))
+       (t
+	;; Else, the client gets a vote.
+	(push client gnuserv-clients)
+	;; Explain buffer exit options.  If dest-frame is nil, the
+	;; user can exit via `delete-frame'.  OTOH, if FLAGS are nil
+	;; and there are some buffers, the user can exit via
+	;; `gnuserv-edit'.
+	(if (and (not (or quick view))
+		 (gnuclient-buffers client))
+	    (message (substitute-command-keys
+		      "Type `\\[gnuserv-edit]' to finish editing"))
+	  (or dest-frame
+	      (message (substitute-command-keys
+			"Type `\\[delete-frame]' to finish editing")))))))))
 
 
 ;;; Functions that hook into Emacs in various way to enable operation
@@ -408,7 +467,9 @@
 ;; Defined later.
 (add-hook 'kill-emacs-hook 'gnuserv-kill-all-clients t)
 
-;; A helper function; used by others.
+;; A helper function; used by others.  Try avoiding it whenever
+;; possible, because it is slow, and conses a list.  Use
+;; `gnuserv-buffer-p' when appropriate, for instance.
 (defun gnuserv-buffer-clients (buffer)
   "Returns a list of clients to which BUFFER belongs."
   (let ((client gnuserv-clients)
@@ -419,6 +480,13 @@
       (pop client))
     res))
 
+;; Like `gnuserv-buffer-clients', but returns a boolean; doesn't
+;; collect a list.
+(defun gnuserv-buffer-p (buffer)
+  (member* buffer gnuserv-clients
+	   :test 'memq
+	   :key 'gnuclient-buffers))
+
 ;; This function makes sure that a killed buffer is deleted off the
 ;; list for the particular client.
 ;;
@@ -443,7 +511,7 @@
 ;; living client.
 (defun gnuserv-kill-buffer-query-function ()
   (or gnuserv-kill-quietly
-      (not (gnuserv-buffer-clients (current-buffer)))
+      (not (gnuserv-buffer-p (current-buffer)))
       (yes-or-no-p
        (format "Buffer %s belongs to gnuserv client(s); kill anyway? "
 	       (current-buffer)))))
@@ -538,7 +606,7 @@
 ;;; Higher-level functions
 
 ;; Choose a `next' server buffer, according to several criteria, and
-;; return it.  If none appropriate are found, return nil.
+;; return it.  If none are found, return nil.
 (defun gnuserv-next-buffer ()
   (let* ((frame (selected-frame))
 	 (device (selected-device))
@@ -555,18 +623,20 @@
        (setq client
 	     (car (member* device gnuserv-clients :key 'gnuclient-device))))
       (car (gnuclient-buffers client)))
-     ;; Else, try to find just any client, and return its first buffer.
-     (gnuserv-clients
-      (car (gnuclient-buffers (car gnuserv-clients))))
-      ;; Oh, give up.
+     ;; Else, try to find any client with at least one buffer, and
+     ;; return its first buffer.
+     ((setq client
+	    (car (member-if-not 'null gnuserv-clients
+				:key 'gnuserv-buffers)))
+      (car (gnuclient-buffers client)))
+     ;; Oh, give up.
      (t nil))))
 
 (defun gnuserv-buffer-done (buffer)
   "Mark BUFFER as \"done\" for its client(s).
-Calls `gnuserv-done-function' and returns another gnuserv buffer as a
-suggestion for the new current buffer."
+Does the save/backup queries first, and calls `gnuserv-done-function'."
   ;; Check whether this is the real thing.
-  (unless (gnuserv-buffer-clients buffer)
+  (unless (gnuserv-buffer-p buffer)
     (error "%s does not belong to a gnuserv client" buffer))
   ;; Backup/ask query.
   (if (gnuserv-temp-file-p buffer)
@@ -578,8 +648,7 @@
     (if (and (buffer-modified-p)
 	     (y-or-n-p (concat "Save file " buffer-file-name "? ")))
 	(save-buffer buffer)))
-  (gnuserv-buffer-done-1 buffer)
-  (gnuserv-next-buffer))
+  (gnuserv-buffer-done-1 buffer))
 
 ;; Called by `gnuserv-start-1' to clean everything.  Hooked into
 ;; `kill-emacs-hook', too.
@@ -587,29 +656,53 @@
   "Kill all the gnuserv clients.  Ruthlessly."
   (mapc 'gnuserv-kill-client gnuserv-clients))
 
-;; Actually start the process.  Kills all the clients before-hand.
-(defun gnuserv-start-1 (&optional leave-dead)
+;; This serves to run the hook and reset
+;; `allow-deletion-of-last-visible-frame'.
+(defun gnuserv-prepare-shutdown ()
+  (setq allow-deletion-of-last-visible-frame nil)
+  (run-hooks 'gnuserv-shutdown-hook))
+
+;; This is a user-callable function, too.
+(defun gnuserv-shutdown ()
+  "Shutdown the gnuserv server, if one is currently running.
+All the clients will be disposed of via the normal methods."
+  (interactive)
   (gnuserv-kill-all-clients)
   (when gnuserv-process
     (set-process-sentinel gnuserv-process nil)
+    (gnuserv-prepare-shutdown)
     (condition-case ()
 	(delete-process gnuserv-process)
-      (error nil)))
+      (error nil))
+    (setq gnuserv-process nil)
+    (message "Killed server")))
+
+;; Actually start the process.  Kills all the clients before-hand.
+(defun gnuserv-start-1 (&optional leave-dead)
+  ;; Shutdown the existing server, if any.
+  (gnuserv-shutdown)
   ;; If we already had a server, clear out associated status.
   (unless leave-dead
-    (setq gnuserv-string "")
-    (setq gnuserv-current-client nil)
+    (setq gnuserv-string ""
+	  gnuserv-current-client nil)
     (let ((process-connection-type t))
-      (setq gnuserv-process 
+      (setq gnuserv-process
 	    (start-process "gnuserv" nil gnuserv-program)))
     (set-process-sentinel gnuserv-process 'gnuserv-sentinel)
     (set-process-filter gnuserv-process 'gnuserv-process-filter)
-    (process-kill-without-query gnuserv-process)))
+    (process-kill-without-query gnuserv-process)
+    (setq allow-deletion-of-last-visible-frame t)
+    (run-hooks 'gnuserv-init-hook)))
 
 
 ;;; User-callable functions:
 
 ;;;###autoload
+(defun gnuserv-running-p ()
+  "Return non-nil if a gnuserv process is running from this XEmacs session."
+  (not (not gnuserv-process)))
+
+;;;###autoload
 (defun gnuserv-start (&optional leave-dead)
   "Allow this Emacs process to be a server for client processes.
 This starts a gnuserv communications subprocess through which
@@ -618,35 +711,44 @@
 
 Prefix arg means just kill any existing server communications subprocess."
   (interactive "P")
-  ;; kill it dead!
   (and gnuserv-process
        (not leave-dead)
        (message "Restarting gnuserv"))
   (gnuserv-start-1 leave-dead))
 
-;;;###autoload
-(defun gnuserv-edit (&optional arg)
+(defun gnuserv-edit (&optional count)
   "Mark the current gnuserv editing buffer as \"done\", and switch to next one.
 
-The `gnuserv-done-function' is used to dispose of the buffer after marking it
-as done; it is `kill-buffer' by default.
+Run with a numeric prefix argument, repeat the operation that number
+of times.  If given a universal prefix argument, close all the buffers
+of this buffer's clients.
+
+The `gnuserv-done-function' (bound to `kill-buffer' by default) is
+called to dispose of the buffer after marking it as done.
 
 Files that match `gnuserv-temp-file-regexp' are considered temporary and
 are saved unconditionally and backed up if `gnuserv-make-temp-file-backup'
-is non-nil.  They are disposed of using `gnuserv-done-temp-file-function'.
-
-When all of a client's buffers are marked as \"done\", the client is notified.
+is non-nil.  They are disposed of using `gnuserv-done-temp-file-function'
+(also bound to `kill-buffer' by default).
 
-If invoked with a prefix argument, or if there is no gnuserv process
-running, only starts server process.  Invoked with \\[gnuserv-edit]."
+When all of a client's buffers are marked as \"done\", the client is notified."
   (interactive "P")
-  (if (or arg (not gnuserv-process)
-	  (memq (process-status gnuserv-process) '(signal exit)))
-      (gnuserv-start)
-    (switch-to-buffer (or (gnuserv-buffer-done (current-buffer))
-			  (current-buffer)))))
+  (when (null count)
+    (setq count 1))
+  (cond ((numberp count)
+	 (let (next)
+	   (while (natnump (decf count))
+	     (gnuserv-buffer-done (current-buffer))
+	     (setq next (gnuserv-next-buffer))
+	     (when next
+	       (switch-to-buffer next)))))
+	(count
+	   (let* ((buf (current-buffer))
+		  (clients (gnuserv-buffer-clients buf)))
+	     (unless clients
+	       (error "%s does not belong to a gnuserv client" buf))
+	     (mapc 'gnuserv-kill-client (gnuserv-buffer-clients buf))))))
 
-;;;###autoload
 (global-set-key "\C-x#" 'gnuserv-edit)
 
 (provide 'gnuserv)
--- lib-src/gnuclient.c.orig	Sun May 18 21:00:36 1997
+++ lib-src/gnuclient.c	Mon May 19 00:57:05 1997
@@ -124,6 +124,9 @@
   signal (SIGHUP, pass_signal_to_emacs);
   signal (SIGQUIT, pass_signal_to_emacs);
   signal (SIGINT, pass_signal_to_emacs);
+#ifdef SIGWINCH
+  signal (SIGWINCH, pass_signal_to_emacs);
+#endif
 
   /* We want emacs to realize that we are resuming */
   signal (SIGCONT, tell_emacs_to_resume);
@@ -190,234 +193,395 @@
 
 } /* filename_expand */
 
+/* Encase the string in quotes, escape all the backslashes and quotes
+   in string.  */
+char *
+clean_string (CONST char *s)
+{
+  int i = 0;
+  char *p, *res;
+
+  for (p = s; *p; p++, i++)
+    {
+      if (*p == '\\' || *p == '\"')
+	++i;
+      else if (*p == '\004')
+	i += 3;
+    }
+  p = res = (char *)malloc (i + 2 + 1);
+  *p++ = '\"';
+  for (; *s; p++, s++)
+    {
+      switch (*s)
+	{
+	case '\\':
+	  *p++ = '\\';
+	  *p = '\\';
+	  break;
+	case '\"':
+	  *p++ = '\\';
+	  *p = '\"';
+	  break;
+	case '\004':
+	  *p++ = '\\';
+	  *p++ = 'C';
+	  *p++ = '-';
+	  *p = 'd';
+	  break;
+	default:
+	  *p = *s;
+	}
+    }
+  *p++ = '\"';
+  *p = '\0';
+  return res;
+}
+
+#define GET_ARGUMENT(var, desc) do {					   \
+ if (*(p + 1)) (var) = p + 1;						   \
+   else									   \
+     {									   \
+       if (!argv[++i])							   \
+         {								   \
+           fprintf (stderr, "%s: `%s' must be followed by an argument\n",  \
+		    progname, desc);					   \
+	   exit (1);							   \
+         }								   \
+      (var) = argv[i];							   \
+    }									   \
+  over = 1;								   \
+} while (0)
+
+
 int
 main (int argc, char *argv[])
 {
-  int starting_line = 1;			/* line to start editing at */
-  char command[MAXPATHLEN+50];			/* emacs command buffer */
-  char fullpath[MAXPATHLEN+1];			/* full pathname to file */
-  int qflg = 0;					/* quick edit, don't wait for 
-						 * user to finish */
-  int view = 0;					/* view only. */
-  int errflg = 0;				/* option error */
-  int c;					/* char from getopt */
-  int s;					/* socket / msqid to server */
-  int connect_type;           			/* CONN_UNIX, CONN_INTERNET, or
-						 * CONN_IPC */
+  int starting_line = 1;	/* line to start editing at */
+  char command[MAXPATHLEN+50];	/* emacs command buffer */
+  char fullpath[MAXPATHLEN+1];	/* full pathname to file */
+  char *eval_form = NULL;	/* form to evaluate with `-eval' */
+  char *eval_function = NULL;	/* function to evaluate with `-f' */
+  char *load_library = NULL;	/* library to load */
+  int quick = 0;	       	/* quick edit, don't wait for user to
+				   finish */
+  int batch = 0;		/* batch mode */
+  int view = 0;			/* view only. */
+  int nofiles = 0;
+  int errflg = 0;		/* option error */
+  int c;			/* char from getopt */
+  int s;			/* socket / msqid to server */
+  int connect_type;		/* CONN_UNIX, CONN_INTERNET, or
+				 * CONN_IPC */
   int suppress_windows_system = 0;
   char *display;
 #ifdef INTERNET_DOMAIN_SOCKETS
-  char *hostarg = NULL;				/* remote hostname */
-  char thishost[HOSTNAMSZ];			/* this hostname */
-  char remotepath[MAXPATHLEN+1];		/* remote pathname */
-  int rflg = 0;					/* pathname given on cmdline */
-  u_short portarg = 0;				/* port to server */
-  char *ptr;					/* return from getenv */
+  char *hostarg = NULL;		/* remote hostname */
+  char *remotearg;
+  char thishost[HOSTNAMSZ];	/* this hostname */
+  char remotepath[MAXPATHLEN+1]; /* remote pathname */
+  char *path;
+  int rflg = 0;			/* pathname given on cmdline */
+  char *portarg;
+  u_short port = 0;		/* port to server */
 #endif /* INTERNET_DOMAIN_SOCKETS */
 #ifdef SYSV_IPC
-  struct msgbuf *msgp;				/* message */
+  struct msgbuf *msgp;		/* message */
 #endif /* SYSV_IPC */
   char *tty;
-  char buffer[GSERV_BUFSZ+1];		/* buffer to read pid */
+  char buffer[GSERV_BUFSZ + 1];	/* buffer to read pid */
+  char result[GSERV_BUFSZ + 1];
+  int i;
 
 #ifdef INTERNET_DOMAIN_SOCKETS
   memset (remotepath, 0, sizeof (remotepath));
 #endif /* INTERNET_DOMAIN_SOCKETS */
 
-  progname = argv[0];
+  progname = strrchr (argv[0], '/');
+  if (progname)
+    ++progname;
+  else
+    progname = argv[0];
 
   display = getenv ("DISPLAY");
   if (!display)
     suppress_windows_system = 1;
 
-  while ((c = getopt (argc, argv,
-
-#ifdef INTERNET_DOMAIN_SOCKETS
-		      "n:h:p:r:qv"
-#else /* !INTERNET_DOMAIN_SOCKETS */
-		      "n:qv"
-#endif /* !INTERNET_DOMAIN_SOCKETS */
-
-		      )) != EOF)
-    switch (c)
-      {
-      case 'n':
-	if (*optarg == 'w')
-	  suppress_windows_system++;
-	else
-	  errflg++;
-	break;
-      case 'q':					/* quick mode specified */
-	qflg++;
-	break;
-      case 'v':
-	view++;
+  for (i = 1; argv[i] && !errflg; i++)
+    {
+      if (*argv[i] != '-')
 	break;
-
+      if (!strcmp (argv[i], "-batch"))
+	batch = 1;
+      else if (!strcmp (argv[i], "-eval"))
+	{
+	  if (!argv[++i])
+	    {
+	      fprintf (stderr, "%s: `-eval' must be followed by an argument\n",
+		       progname);
+	      exit (1);
+	    }
+	  eval_form = argv[i];
+	}
+      else if (!strcmp (argv[i], "-display"))
+	{
+	  suppress_windows_system = 0;
+	  if (!argv[++i])
+	    {
+	      fprintf (stderr, "%s: `-display' must be followed by an argument\n",
+		       progname);
+	      exit (1);
+	    }
+	  display = argv[i];
+	}
+      else if (!strcmp (argv[i], "-nw"))
+	suppress_windows_system = 1;
+      else
+	{
+	  /* Iterate over one-letter options. */
+	  char *p;
+	  int over = 0;
+	  for (p = argv[i] + 1; *p && !over; p++)
+	    {
+	      switch (*p)
+		{
+		case 'q':
+		  quick = 1;
+		  break;
+		case 'v':
+		  view = 1;
+		  break;
+		case 'f':
+		  GET_ARGUMENT (eval_function, "-f");
+		  break;
+		case 'l':
+		  GET_ARGUMENT (load_library, "-l");
+		  break;
 #ifdef INTERNET_DOMAIN_SOCKETS
-      case 'h':				/* server host name specified */
-	hostarg = optarg;
-	break;
-      case 'r':				/* remote path from server specifed */
-	strcpy (remotepath,optarg);
-	rflg++;
-	break;
-      case 'p':				/* port number specified */
-	portarg = atoi (optarg);
-	break;
+		case 'h':
+		  GET_ARGUMENT (hostarg, "-h");
+		  break;
+		case 'p':
+		  GET_ARGUMENT (portarg, "-p");
+		  port = atoi (portarg);
+		  break;
+		case 'r':
+		  GET_ARGUMENT (remotearg, "-r");
+		  strcpy (remotepath, remotearg);
+		  rflg = 1;
+		  break;
 #endif /* INTERNET_DOMAIN_SOCKETS */
-
-      case '?':
-	errflg++;
-      } /* switch */
+		default:
+		  errflg = 1;
+		}
+	    } /* for */
+	} /* else */
+    } /* for */
 
   if (errflg)
     {
       fprintf (stderr,
 #ifdef INTERNET_DOMAIN_SOCKETS
-	       "usage: %s [-q] [-h hostname] [-p port] [-r pathname] "
-	       "[[+line] path] ...\n",
+	       "usage: %s [-nw] [-q] [-v] [-l library] [-f function] [-eval expr]\n"
+	       "       [-h host] [-p port] [-r file-name] [[+line] file] ...\n",
 #else /* !INTERNET_DOMAIN_SOCKETS */
-	       "usage: %s [-nw] [-q] [[+line] path] ...\n",
+	       "usage: %s [-nw] [-q] [-v] [-l library] [-f function] [-eval expr] "
+	       "[[+line] path] ...\n",
 #endif /* !INTERNET_DOMAIN_SOCKETS */
 	       progname);
       exit (1);
-    } /* if */
-
-  if (suppress_windows_system)
+    }
+  if (batch && argv[i])
+    {
+      fprintf (stderr, "%s: Cannot specify `-batch' with file names\n",
+	       progname);
+      exit (1);
+    }
+  *result = '\0';
+  if (eval_function || eval_form || load_library)
     {
-      tty = ttyname (0);
-      if (!tty)
+#if defined(INTERNET_DOMAIN_SOCKETS)
+      connect_type = make_connection (hostarg, port, &s);
+#else
+      connect_type = make_connection (NULL, (u_short) 0, &s);
+#endif
+      sprintf (command, "(gnuserv-eval%s '(progn ", quick ? "-quickly" : "");
+      send_string (s, command);
+      if (load_library)
+	{
+	  sprintf (command, " (load-library %s)", clean_string (load_library));
+	  send_string (s, command);
+	}
+      if (eval_form)
+	{
+	  sprintf (command, " %s", eval_form);
+	  send_string (s, command);
+	}
+      if (eval_function)
 	{
-	  fprintf (stderr, "%s: Not connected to a tty", progname);
+	  sprintf (command, " (%s)", eval_function);
+	  send_string (s, command);
+	}
+      send_string (s, "))");
+      send_string (s, EOT_STR);
+      if (read_line (s, result) == 0)
+	{
+	  fprintf (stderr, "%s: Could not read\n", progname);
 	  exit (1);
 	}
-    }
-  /* This next stuff added in an attempt to make handling of the tty
-     do the right thing when dealing with signals.  The idea is to
-     pass all the appropriate signals to the emacs process. */
-
-  connect_type = make_connection (NULL, (u_short) 0, &s);
-
-  send_string (s, "(gnuserv-eval '(emacs-pid))");
-  send_string (s, EOT_STR);
-
-  if (read_line (s, buffer) == 0)
+    } /* eval_function || eval_form || load_library */
+  else if (batch)
     {
-      fprintf (stderr, "%s: Could not establish emacs procces id\n",
+      fprintf (stderr, "%s: `-batch' requires an evaluation\n",
 	       progname);
       exit (1);
     }
-  /* Don't do disconnect_from_server becasue we have already read
-     data, and disconnect doesn't do anything else. */
-#ifdef SYSV_IPC
-  if (connect_type == (int) CONN_IPC)
-    disconnect_from_ipc_server (s, msgp, FALSE);
+
+  if (!batch)
+    {
+      if (suppress_windows_system)
+	{
+	  tty = ttyname (0);
+	  if (!tty)
+	    {
+	      fprintf (stderr, "%s: Not connected to a tty", progname);
+	      exit (1);
+	    }
+	}
+
+#if defined(INTERNET_DOMAIN_SOCKETS)
+      connect_type = make_connection (hostarg, port, &s);
+#else
+      connect_type = make_connection (NULL, (u_short) 0, &s);
+#endif
+
+      send_string (s, "(gnuserv-eval '(emacs-pid))");
+      send_string (s, EOT_STR);
+
+      if (read_line (s, buffer) == 0)
+	{
+	  fprintf (stderr, "%s: Could not establish emacs procces id\n",
+		   progname);
+	  exit (1);
+	}
+      /* Don't do disconnect_from_server becasue we have already read
+	 data, and disconnect doesn't do anything else. */
+#ifndef INTERNET_DOMAIN_SOCKETS
+      if (connect_type == (int) CONN_IPC)
+	disconnect_from_ipc_server (s, msgp, FALSE);
 #endif /* !SYSV_IPC */
 
-  emacs_pid = (pid_t)atol(buffer);
-  initialize_signals();
+      emacs_pid = (pid_t)atol(buffer);
+      initialize_signals();
 
-#if defined(INTERNET_DOMAIN_SOCKETS) && !defined(GNUATTACH)
-  connect_type = make_connection (hostarg, portarg, &s);
+#if defined(INTERNET_DOMAIN_SOCKETS)
+      connect_type = make_connection (hostarg, port, &s);
 #else
-  connect_type = make_connection (NULL, (u_short) 0, &s);
+      connect_type = make_connection (NULL, (u_short) 0, &s);
 #endif
 
 #ifdef INTERNET_DOMAIN_SOCKETS
-  if (connect_type == (int) CONN_INTERNET)
-    {
-      gethostname (thishost, HOSTNAMSZ);
-      if (!rflg)
-	{				/* attempt to generate a path 
+      if (connect_type == (int) CONN_INTERNET)
+	{
+	  char *ptr;
+	  gethostname (thishost, HOSTNAMSZ);
+	  if (!rflg)
+	    {				/* attempt to generate a path 
 					 * to this machine */
-	  if ((ptr = getenv ("GNU_NODE")) != NULL)
-	    /* user specified a path */
-	    strcpy (remotepath, ptr);
-	}
+	      if ((ptr = getenv ("GNU_NODE")) != NULL)
+		/* user specified a path */
+		strcpy (remotepath, ptr);
+	    }
 #if 0  /* This is really bogus... re-enable it if you must have it! */
 #if defined (hp9000s300) || defined (hp9000s800)
-      else if (strcmp (thishost,hostarg))
-	{	/* try /net/thishost */
-	  strcpy (remotepath, "/net/");		/* (this fails using internet 
-						   addresses) */
-	  strcat (remotepath, thishost);
-	}
+	  else if (strcmp (thishost,hostarg))
+	    {	/* try /net/thishost */
+	      strcpy (remotepath, "/net/");		/* (this fails using internet 
+							   addresses) */
+	      strcat (remotepath, thishost);
+	    }
 #endif
 #endif
-    }
-  else
-    {					/* same machines, no need for path */
-      remotepath[0] = '\0';		/* default is the empty path */
-    }
+	}
+      else
+	{			/* same machines, no need for path */
+	  remotepath[0] = '\0';	/* default is the empty path */
+	}
 #endif /* INTERNET_DOMAIN_SOCKETS */
 
 #ifdef SYSV_IPC
-  if ((msgp = (struct msgbuf *) 
-       malloc (sizeof *msgp + GSERV_BUFSZ)) == NULL)
-    {
-      fprintf (stderr, "%s: not enough memory for message buffer\n", progname);
-      exit (1);
-    } /* if */
+      if ((msgp = (struct msgbuf *) 
+	   malloc (sizeof *msgp + GSERV_BUFSZ)) == NULL)
+	{
+	  fprintf (stderr, "%s: not enough memory for message buffer\n", progname);
+	  exit (1);
+	} /* if */
 
-  msgp->mtext[0] = '\0';			/* ready for later strcats */
+      msgp->mtext[0] = '\0';			/* ready for later strcats */
 #endif /* SYSV_IPC */
 
-  if (suppress_windows_system)
-    {
-      ptr = getenv ("TERM");
-      if (!ptr)
+      if (suppress_windows_system)
 	{
-	  fprintf (stderr, "%s: unknown terminal type\n", progname);
-	  exit (1);
+	  char *term = getenv ("TERM");
+	  if (!term)
+	    {
+	      fprintf (stderr, "%s: unknown terminal type\n", progname);
+	      exit (1);
+	    }
+	  sprintf (command, "(gnuserv-edit-files '(tty %s %s %d) '(",
+		   clean_string (tty), clean_string (term), getpid ());
 	}
-      sprintf (command,
-	       "(gnuserv-edit-files '(tty \"%s\" \"%s\" %d) '(",
-	       tty, ptr, getpid ());
-    }
-  else /* !suppress_windows_system */
-    {
-      sprintf (command, "(gnuserv-edit-files '(x \"%s\") '(",
-	       display);
-    } /* !suppress_windows_system */
-  send_string (s, command);
+      else /* !suppress_windows_system */
+	{
+	  sprintf (command, "(gnuserv-edit-files '(x %s) '(",
+		   clean_string (display));
+	} /* !suppress_windows_system */
+      send_string (s, command);
 
-  if (!suppress_windows_system && (optind == argc))
-    qflg = 1;
+      if (!argv[i])
+	nofiles = 1;
 
-  for (; optind < argc; optind++)
-    {
-      if (optind < argc - 1 && *argv[optind] == '+')
-	starting_line = atoi (argv[optind++]);
-      else
-	starting_line = 1;
-      /* If the last argument is +something, treat it as a file. */
-      if (optind == argc)
+      for (; argv[i]; i++)
 	{
-	  starting_line = 1;
-	  --optind;
-	}
-      filename_expand (fullpath, argv[optind]);
-      sprintf (command, "(%d . \"%s%s\")", starting_line,
+	  if (i < argc - 1 && *argv[i] == '+')
+	    starting_line = atoi (argv[i++]);
+	  else
+	    starting_line = 1;
+	  /* If the last argument is +something, treat it as a file. */
+	  if (i == argc)
+	    {
+	      starting_line = 1;
+	      --i;
+	    }
+	  filename_expand (fullpath, argv[i]);
 #ifdef INTERNET_DOMAIN_SOCKETS
-	       remotepath,
-#else /* !INTERNET_DOMAIN_SOCKETS */
-	       "",
+	  path = malloc (strlen (remotepath) + strlen (fullpath) + 1);
+	  sprintf (path, "%s%s", remotepath, fullpath);
+#else
+	  path = malloc (strlen (fullpath));
+	  strcpy (path, fullpath);
 #endif
-	       fullpath);
+	  sprintf (command, "(%d . %s)", starting_line, clean_string (path));
+	  send_string (s, command);
+	  free (path);
+	} /* for */
+
+      sprintf (command, ")%s%s",
+	       (quick || (nofiles && !suppress_windows_system)) ? " 'quick" : "",
+	       view ? " 'view" : "");
       send_string (s, command);
-    } /* for */
-
-  sprintf (command, ") %s)", qflg ? "'quick" : (view ? "'view" : ""));
-  send_string (s, command);
+      send_string (s, ")");
 
 #ifdef SYSV_IPC
-  if (connect_type == (int) CONN_IPC)
-    disconnect_from_ipc_server (s, msgp, FALSE);
+      if (connect_type == (int) CONN_IPC)
+	disconnect_from_ipc_server (s, msgp, FALSE);
 #else /* !SYSV_IPC */
-  if (connect_type != (int) CONN_IPC)
-    disconnect_from_server (s, FALSE);
+      if (connect_type != (int) CONN_IPC)
+	disconnect_from_server (s, FALSE);
 #endif /* !SYSV_IPC */
+    } /* not batch */
+
+  if (batch && !quick)
+      printf ("%s\n", result);
 
   return 0;
 


-- 
Hrvoje Niksic <hniksic@srce.hr> | Student at FER Zagreb, Croatia
--------------------------------+--------------------------------
main(){printf(&unix["\021%six\012\0"],(unix)["have"]+"fun"-0x60);}

