ange@otter.hpl.hp.com (Andy Norman) (01/26/89)
[Apologies in advance to gnu.emacs readers who may have already seen this posting] The following code implements server / client support for GNU Emacs that works either remotely or locally. It has many additional features over emacsclient/server, the main one being that it contains a program called gnudoit which allows remote execution of GNU Emacs lisp forms. If you are not too bothered about security at the individual *user* level, this code can be a replacement for emacsclient/server/server.el. The code does try to implement security efficiently at the *machine* level. It has been tested on a VAX-11/785 running Ultrix version 2.0, HP9000/850's running HP-UX 2.1 and HP9000/350's running HP-UX 6.2. I've also included a context diff of src/x11fns.c which allows GNU Emacs to raise / de-iconify its own X11 window under certain window managers. Please let me know of any bugs or suggestions. Thanks. -- ange -- ange%anorman@hplabs.hp.com #---------------------------------- cut here ---------------------------------- # This is a shell archive. Remove anything before this line, # then unpack it by saving it in a file and typing "sh file". # # Wrapped by Andy Norman <ange@anorman> on Tue Jan 24 18:07:36 1989 # # This archive contains: # Makefile gnuclient.c gnudoit.c gnulib.c # gnulib.h gnuserv.1 gnuserv.c gnuserv.el # x11fns.diff # # Error checking via wc(1) will be performed. LANG=""; export LANG echo x - Makefile cat >Makefile <<'@EOF' DEST=. CFLAGS=-O all: ${DEST}/gnuclient ${DEST}/gnuserv ${DEST}/gnudoit ${DEST}/gnuclient: gnulib.h gnulib.o gnuclient.c cc ${CFLAGS} gnuclient.c gnulib.o -o $@ ${DEST}/gnuserv: gnulib.h gnulib.o gnuserv.c cc ${CFLAGS} gnuserv.c gnulib.o -o $@ ${DEST}/gnudoit: gnulib.h gnulib.o gnudoit.c cc ${CFLAGS} gnudoit.c gnulib.o -o $@ gnulib.o: gnulib.h gnulib.c cc -c ${CFLAGS} gnulib.c @EOF set `wc -lwc <Makefile` if test $1$2$3 != 1643389 then echo ERROR: wc results of Makefile are $* should be 16 43 389 fi chmod 644 Makefile echo x - gnuclient.c cat >gnuclient.c <<'@EOF' /* -*-C-*- Client code to allow remote editing of files by GNU Emacs. This file is not part of GNU Emacs (yet). Copyright (c) 1989 Andrew P. Norman Copying is permitted under those conditions described by the GNU Emacs General Public License. Author: Andy Norman (ange%anorman@hplabs.hp.com) Please mail bugs and suggestions to the author at the above address. */ #include "gnulib.h" /* filename_expand -- try to convert the given filename into a fully-qualified pathname. */ void filename_expand(fullpath,remotepath,filename) char *fullpath; /* returned full pathname */ char *remotepath; /* pathname to tack on the front */ char *filename; /* filename to expand */ { char cwd[MAXPATHLEN+1]; int len; strcpy(fullpath,remotepath); if(filename[0] && filename[0] != '/') { /* relative filename */ if ((getcwd(cwd,MAXPATHLEN)) == NULL) { perror(progname); fprintf(stderr,"%s: unable to get current working directory\n",progname); exit(1); }; /* if */ strcat(fullpath,cwd); len = strlen(fullpath); if (len > 0 && fullpath[len-1] == '/') /* trailing slash already? */ ; else strcat(fullpath,"/"); /* append trailing slash */ }; /* if */ strcat(fullpath,filename); } /* filename_expand */ main(argc,argv) int argc; char *argv[]; { int starting_line = 1; /* line to start editing at */ char command[MAXPATHLEN+50]; /* emacs command buffer */ char remotehost[HOSTNAMSZ]; /* remote hostname */ char remotepath[MAXPATHLEN+1]; /* remote pathname */ char fullpath[MAXPATHLEN+1]; /* full pathname to file */ char *ptr; /* return from getenv */ int qflg = 0; /* quick edit, don't wait for user to finish */ int hflg = 0; /* hostname given on command line */ int rflg = 0; /* pathname given on command line */ int errflg = 0; /* option error */ int c; /* char from getopt */ int s; /* socket to server */ u_short port = 0; /* port to server */ progname = argv[0]; while ((c = getopt(argc, argv, "h:p:r:q")) != EOF) switch (c) { case 'q': qflg++; break; case 'h': strcpy(remotehost,optarg); hflg++; break; case 'r': strcpy(remotepath,optarg); rflg++; break; case 'p': port = atoi(optarg); break; case '?': errflg++; }; /* switch */ if (errflg) { fprintf(stderr,"usage: %s [-q] [-h hostname] [-p port] [-r pathname] [[+line] path] ...\n", progname); exit (1); }; /* if */ if (!hflg) { if((ptr=getenv("GNU_HOST")) != NULL) strcpy(remotehost,ptr); else gethostname(remotehost,HOSTNAMSZ); /* use this host by default */ }; /* if */ if(!rflg) { if((ptr=getenv("GNU_NODE")) != NULL) strcpy(remotepath,ptr); else remotepath[0] = '\0'; /* default is the empty path */ }; /* if */ if (port == 0 && (ptr=getenv("GNU_PORT")) != NULL) port = atoi(ptr); s = connect_to_server(remotehost,port); send_string(s,"(progn "); for (; optind < argc; optind++) { if (*argv[optind] == '+') starting_line = atoi(argv[optind]); else { filename_expand(fullpath,remotepath,argv[optind]); if (qflg) sprintf(command,"(server-edit-file-quickly %d \"%s\")",starting_line,fullpath); else sprintf(command,"(server-edit-file %d \"%s\")",starting_line,fullpath); send_string(s,command); starting_line = 1; }; /* else */ }; /* for */ if (qflg) send_string(s,"(server-edit-file-quickly-done))"); else send_string(s,")"); disconnect_from_server(s,FALSE); exit(0); } /* main */ @EOF set `wc -lwc <gnuclient.c` if test $1$2$3 != 1434563668 then echo ERROR: wc results of gnuclient.c are $* should be 143 456 3668 fi chmod 644 gnuclient.c echo x - gnudoit.c cat >gnudoit.c <<'@EOF' /* -*-C-*- Client code to remotely evaluate lisp forms using GNU Emacs. This file is not part of GNU Emacs (yet). Copyright (c) 1989 Andrew P. Norman Copying is permitted under those conditions described by the GNU Emacs General Public License. Author: Andy Norman (ange%anorman@hplabs.hp.com) Please mail bugs and suggestions to the author at the above address. */ #include "gnulib.h" main(argc,argv) int argc; char *argv[]; { int starting_line = 1; /* line to start editing at */ char remotehost[HOSTNAMSZ]; /* remote hostname */ char *ptr; /* return from getenv */ int hflg = 0; /* hostname given on command line */ int qflg = 0; /* don't wait around for gnu emacs to eval cmd */ int errflg = 0; /* option error */ int c; /* char from getopt */ int s; /* socket to server */ u_short port = 0; /* port number */ progname = argv[0]; while ((c = getopt(argc, argv, "qh:p:")) != EOF) switch (c) { case 'h': strcpy(remotehost,optarg); hflg++; break; case 'p': port = atoi(optarg); break; case 'q': qflg++; break; case '?': errflg++; }; /* switch */ if (errflg) { fprintf(stderr,"usage: %s [-q] [-h hostname] [-p port] [sexpr]...\n",progname); exit (1); }; /* if */ if (!hflg) { if((ptr=getenv("GNU_HOST")) != NULL) strcpy(remotehost,ptr); else gethostname(remotehost,HOSTNAMSZ); /* use this host by default */ }; /* if */ if (port == 0 && (ptr=getenv("GNU_PORT")) != NULL) port = atoi(ptr); s = connect_to_server(remotehost,port); if (qflg) send_string(s,"(server-eval-quickly (quote (progn "); else send_string(s,"(server-eval (quote (progn "); for (; optind < argc; optind++) send_string(s,argv[optind]); send_string(s,")))"); disconnect_from_server(s,!qflg); exit(0); } /* main */ @EOF set `wc -lwc <gnudoit.c` if test $1$2$3 != 802581893 then echo ERROR: wc results of gnudoit.c are $* should be 80 258 1893 fi chmod 644 gnudoit.c echo x - gnulib.c cat >gnulib.c <<'@EOF' /* -*-C-*- Common library code for the GNU Emacs server and client. This file is not part of GNU Emacs (yet). Copyright (c) 1989 Andrew P. Norman Copying is permitted under those conditions described by the GNU Emacs General Public License. Author: Andy Norman (ange%anorman@hplabs.hp.com) Please mail bugs and suggestions to the author at the above address. */ #include "gnulib.h" char *progname = NULL; /* send_string -- send string to socket. */ void send_string(s,msg) int s; char *msg; { if (send(s,msg,strlen(msg),0) < 0) { perror(progname); fprintf(stderr,"%s: unable to send\n",progname); exit(1); }; /* if */ } /* send_string */ /* internet_addr -- return the internet addr of the hostname or internet address passed. Return -1 on error. */ u_long internet_addr(host) char *host; { struct hostent *hp; /* pointer to host info for remote host */ u_long host_addr; /* host address */ if ((host_addr = inet_addr(host)) != -1) return host_addr; else if ((hp = gethostbyname(host)) != NULL) return ((struct in_addr *)(hp->h_addr))->s_addr; else return -1; } /* internet_addr */ /* connect_to_server -- establish connection with server process. returns socket descriptor for server if successful. */ int connect_to_server(serverhost,port) char *serverhost; u_short port; { int s; /* connected socket descriptor */ struct servent *sp; /* pointer to service information */ struct sockaddr_in peeraddr_in; /* for peer socket address */ /* clear out address structures */ memset ((char *)&peeraddr_in, 0, sizeof(struct sockaddr_in)); /* Set up the peer address to which we will connect. */ peeraddr_in.sin_family = AF_INET; /* look up the server host's internet address */ if ((peeraddr_in.sin_addr.s_addr = internet_addr(serverhost)) == -1) { fprintf(stderr,"%s: unable to find %s in /etc/hosts or from YP\n", progname,serverhost); exit(1); }; /* if */ if (port == 0) { if ((sp = getservbyname ("gnuserv","tcp")) == NULL) { fprintf(stderr,"%s: unable to find \"gnuserv\" in /etc/services or from YP\n", progname); exit(1); }; /* if */ peeraddr_in.sin_port = sp->s_port; } /* if */ else peeraddr_in.sin_port = htons(port); /* Create the socket. */ if ((s = socket (AF_INET,SOCK_STREAM, 0))== -1) { perror(progname); fprintf(stderr,"%s: unable to create socket\n",progname); exit(1); }; /* if */ /* Try to connect to the remote server at the address * which was just built into peeraddr. */ if (connect(s, &peeraddr_in, sizeof(struct sockaddr_in)) == -1) { perror(progname); fprintf(stderr, "%s: unable to connect to remote\n",progname); exit(1); }; /* if */ return(s); } /* connect_to_server */ /* disconnect_from_server -- inform the server that sending has finished, and wait for its reply. */ void disconnect_from_server(s,echo) int s; int echo; { char buffer[REPLYSIZ]; int length; if (shutdown(s,1) == -1) { perror(progname); fprintf(stderr, "%s: unable to shutdown socket\n",progname); exit(1); }; /* if */ while((length = recv(s,buffer,REPLYSIZ,0)) > 0) { buffer[length] = '\0'; if (echo) printf("%s",buffer); }; /* while */ if (echo) putchar('\n'); if(length < 0) { perror(progname); fprintf(stderr,"%s: unable to read the reply from the server\n",progname); exit(1); }; /* if */ } /* disconnect_from_server */ @EOF set `wc -lwc <gnulib.c` if test $1$2$3 != 1434683594 then echo ERROR: wc results of gnulib.c are $* should be 143 468 3594 fi chmod 644 gnulib.c echo x - gnulib.h cat >gnulib.h <<'@EOF' /* -*-C-*- Header file for the GNU Emacs server and client C code. This file is not part of GNU Emacs (yet). Copyright (c) 1989 Andrew P. Norman Copying is permitted under those conditions described by the GNU Emacs General Public License. Author: Andy Norman (ange%anorman@hplabs.hp.com) Please mail bugs and suggestions to the author at the above address. */ #include <sys/types.h> #include <sys/socket.h> #include <sys/param.h> #include <netinet/in.h> #include <stdio.h> #include <netdb.h> extern char *getenv(); extern char *optarg; extern int optind; extern char *progname; extern char *getcwd(); #define REPLYSIZ 300 /* max size of reply from server to client */ #define HOSTNAMSZ 255 /* max size of a hostname */ #define TABLE_SIZE 101 /* The number of entries in the hash table */ #define HASH(host) host /* Rather simplistic hash function */ #define FALSE 0 #define TRUE 1 @EOF set `wc -lwc <gnulib.h` if test $1$2$3 != 34137901 then echo ERROR: wc results of gnulib.h are $* should be 34 137 901 fi chmod 644 gnulib.h echo x - gnuserv.1 cat >gnuserv.1 <<'@EOF' .TH GNUSERV 1 "" "Server / Client enhancements for GNU Emacs" .UC 4 .SH NAME gnuserv, gnuclient, gnudoit \- Server / Client Enhancements for GNU Emacs .SH SYNOPSIS .B gnuclient [-h hostname] [-q] [-p port] [-r pathname] [[+line] path] ... .br .B gnudoit [-h hostname] [-q] [-p port] [sexpr] ... .br .B gnuserv .SH DESCRIPTION \fBgnuclient\fP allows the user to request a running GNU Emacs process to edit the named files or directories. .PP \fBgnudoit\fP allows the user to request a running GNU Emacs process to evaluate the given arguments inside a progn lisp form. .PP \fBgnuserv\fP is the server program that is set running by GNU Emacs to handle all incoming and outgoing requests. It is not usually invoked directly, but is started from GNU Emacs by the lisp form (server-start). .PP The options are: .P .TP 10 \fB-q\fP This option means that both gnuclient and gnudoit will exit once connection has been made with the GNU Emacs process. Normally gnuclient waits until all of the files on the command line have been finished with (their buffers killed) by the GNU Emacs process. Normally gnudoit waits around for evaluation of its arguments by the GNU Emacs process, and prints the results or error conditions. .TP 10 \fB-h\fP hostname This option specifies the host machine which should be running gnuserv. If this option is not specified then the value of the environment variable ``GNU_HOST'' is used if set, otherwise the hostname of the machine running the program is used. Note that an internet address may be specified instead of a hostname which can speed up connections to the server by quite a factor, especially if the client machine is running YP. .TP 10 \fB-p\fP port The service is called ``gnuserv'' in the services database, but a different service port may be specified with this option. The server must also be using the same service port. If this option is not specified, but the value of the environment variable ``GNU_PORT'' is specified, then it is used instead. Note that gnuserv doesn't use the command line option since it is usually started from within GNU Emacs. .TP 10 \fB-p\fP pathname The pathname argument is needed such that the server knows how to specify the root directory of a remote machine. \fBgnuclient\fP prepends this string to each path argument given. For example, if you were trying to edit a file on a client machine called otter, whose root directory was accessible \fIfrom\fP the server machine via the path /net/otter, then this argument should be set to '/net/otter'. If this option is omitted, then the value is taken from the environment variable ``GNU_NODE'', if set, or the empty string otherwise. .TP 10 \fBpath\fP This is the path of the file to be edited. If the file is a directory, then the directory browser dired is usually invoked instead. .TP 10 \fBsexpr\fP This is part of a gnu emacs LISP expression to evaluate. All the arguments are bundled together and wrapped in a progn form. .PP .SH SETUP In order to use the programs, the file gnuserv.el can be copied into a directory on your GNU Emacs load-path, and loaded into GNU Emacs by the lisp form (load "gnuserv"). The server can then be initialized by the GNU Emacs lisp form (server-start). .PP Additionally, the patch to src/x11fns.c can be applied to allow GNU Emacs to de-iconify and raise its edit window upon request under certain X11 window managers. .SH EXAMPLE .PP .TP 10 gnudoit -q '(server-make-window-visible)' '(mh-smail)' .TP 10 gnuclient -h otter -p /net/otter /tmp/* .SH SECURITY \fBgnuserv\fP admits a limited form of security at the machine level. By default only connections from the host where the server is running will be allowed. All other server connections will be rejected with a cryptic message (which is displayed only by gnudoit). Alternatively, if the variable ``GNU_SECURE'' can be found in gnuserv's environment, and it names a readable filename, then this file is opened and assumed to be a list of hosts, one per line, from which the server will allow requests. Note that a host may be either a internet address, or a hostname. If this file contains a lot of hostnames then the server may take quite a time to start up. .SH KNOWN BUGS .PP If GNU Emacs attempts to send a string containing a newline character to \fBgnuserv\fP, then \fBgnuserv\fP will die. .SH FILES .PP .TP 8 .B /etc/services .TP 8 .B ~/.emacs \fIEmacs\fP customization file, see \fIemacs(1)\fP. .SH AUTHOR. Andy Norman (ange%anorman@hplabs.hp.com). @EOF set `wc -lwc <gnuserv.1` if test $1$2$3 != 1077514476 then echo ERROR: wc results of gnuserv.1 are $* should be 107 751 4476 fi chmod 644 gnuserv.1 echo x - gnuserv.c cat >gnuserv.c <<'@EOF' /* -*-C-*- Server code for handling requests from clients and forwarding them on to the GNU Emacs process. This file is not part of GNU Emacs (yet). Copyright (c) 1989 Andrew P. Norman Copying is permitted under those conditions described by the GNU Emacs General Public License. Author: Andy Norman (ange%anorman@hplabs.hp.com) Please mail bugs and suggestions to the author at the above address. */ #include "gnulib.h" struct entry { u_long host_addr; struct entry *next; }; struct entry *permitted_hosts[TABLE_SIZE]; /* permitted -- return whether a given host is allowed to connect to the server. */ int permitted(host_addr) u_long host_addr; { int key; struct entry *entry; /* First find the hash key */ key = HASH(host_addr) % TABLE_SIZE; /* Now check the chain for that hash key */ for(entry=permitted_hosts[key]; entry != NULL; entry=entry->next) if (host_addr == entry->host_addr) return(TRUE); return(FALSE); } /* permitted */ /* add_host -- add the given host to the list of permitted hosts, provided it isn't already there. */ void add_host(host_addr) u_long host_addr; { int key; struct entry *new_entry; if (!permitted(host_addr)) { if ((new_entry = (struct entry *) malloc(sizeof(struct entry))) == NULL) { fprintf(stderr,"%s: unable to malloc space for permitted host entry\n", progname); exit(1); }; /* if */ new_entry->host_addr = host_addr; key = HASH(host_addr) % TABLE_SIZE; new_entry->next = permitted_hosts[key]; permitted_hosts[key] = new_entry; }; /* if */ } /* add_host */ void setup_table() { FILE *host_file; char *file_name; char hostname[HOSTNAMSZ]; u_long host_addr; int i; /* Make sure every entry is null */ for (i=0; i<TABLE_SIZE; i++) permitted_hosts[i] = NULL; if (((file_name = getenv("GNU_SECURE")) != NULL && (host_file = fopen(file_name,"r")) != NULL)) { while ((fscanf(host_file,"%s",hostname) != EOF)) if ((host_addr = internet_addr(hostname)) != -1) add_host(host_addr); fclose(host_file); } else { /* file not available from environment so only allow this host */ gethostname(hostname,HOSTNAMSZ); if ((host_addr = internet_addr(hostname)) == -1) { fprintf(stderr,"%s: unable to find %s in /etc/hosts or from YP", progname,hostname); exit(1); }; /* if */ add_host(host_addr); }; /* else */ } /* setup_table */ /* init -- initialize server, returning a socket that can be listened on. */ int init() { int ls; /* socket descriptor */ int chan; /* unix channel number */ struct servent *sp; /* pointer to service information */ struct sockaddr_in myaddr_in; /* for local socket address */ char *ptr; /* ptr for getenv */ setup_table(); for(chan=3; chan < _NFILE; close(chan++)) /* close unwanted channels */ ; /* clear out address structure */ memset ((char *)&myaddr_in, 0, sizeof(struct sockaddr_in)); /* Set up address structure for the listen socket. */ myaddr_in.sin_family = AF_INET; myaddr_in.sin_addr.s_addr = INADDR_ANY; /* Find the information for the gnu server * in order to get the needed port number. */ if ((ptr=getenv("GNU_PORT")) != NULL) myaddr_in.sin_port = htons(atoi(ptr)); else { if ((sp = getservbyname ("gnuserv", "tcp")) == NULL) { fprintf(stderr,"%s: unable to get \"gnuserv\" from /etc/services or from YP\n", progname); exit(1); }; /* if */ myaddr_in.sin_port = sp->s_port; }; /* else */ /* Create the listen socket. */ if ((ls = socket (AF_INET, SOCK_STREAM, 0)) == -1) { perror(progname); fprintf(stderr,"%s: unable to create socket\n",progname); exit(1); }; /* if */ /* Bind the listen address to the socket. */ if (bind(ls, &myaddr_in, sizeof(struct sockaddr_in)) == -1) { perror(progname); fprintf(stderr,"%s: unable to bind socket\n",progname); exit(1); }; /* if */ /* Initiate the listen on the socket so remote users * can connect. */ if (listen(ls,20) == -1) { perror(progname); fprintf(stderr,"%s: unable to listen\n",progname); exit(1); }; /* if */ /* setpgrp(); */ return(ls); } /* init */ /* handle_request -- accept a request from a client and send the information to stdout (the gnu process). */ void handle_request(ls) int ls; { char buf[BUFSIZ]; int s; int len; int addrlen = sizeof(struct sockaddr_in); struct sockaddr_in peeraddr_in; /* for peer socket address */ memset ((char *)&peeraddr_in,0,sizeof(struct sockaddr_in)); if ((s = accept(ls,&peeraddr_in,&addrlen)) == -1) { perror(progname); fprintf(stderr,"%s: unable to accept\n",progname); exit(1); }; /* if */ /* Check that access is allowed - if not return crud to the client */ if (!permitted(peeraddr_in.sin_addr.s_addr)) { send_string(s,"gnudoit: Connection refused\ngnudoit: unable to connect to remote"); close(s); return; }; /* if */ printf("%d ",s); while (len = recv(s,buf,BUFSIZ,0)) { if (len < 0) { perror(progname); fprintf(stderr,"%s: unable to recv\n",progname); exit(1); }; /* if */ buf[len] = '\0'; printf("%s",buf); }; /* while */ printf("\n"); } /* handle_request */ /* handle_response -- accept a response from stdin (the gnu process) and pass the information on to the relevant client. */ void handle_response() { char buf[BUFSIZ]; char response[BUFSIZ]; int s; int len; if ((len = read(0,buf,BUFSIZ)) < 0) { perror(progname); fprintf(stderr,"%s: unable to read\n",progname); exit(1); }; /* if */ /* parse the response from gnu */ response[0] = '\0'; sscanf(buf,"%d:%[^\n]\n", &s, response); /* Send a response back to the client. */ send_string(s,response); close(s); } /* handle_response */ main(argc,argv) int argc; char *argv[]; { int ls; progname = argv[0]; ls = init(); /* init and get a socket to listen on */ while (1) { int rmask = (1 << ls) + 1; if (select(ls + 1,&rmask,0,0,0) < 0) { perror(progname); fprintf(stderr,"%s: unable to select\n",progname); exit(1); } if (rmask & (1 << ls)) /* from socket (client process) */ handle_request(ls); if (rmask & 1) /* from stdin (gnu process) */ handle_response(); }; /* while */ } /* main */ @EOF set `wc -lwc <gnuserv.c` if test $1$2$3 != 2778586509 then echo ERROR: wc results of gnuserv.c are $* should be 277 858 6509 fi chmod 644 gnuserv.c echo x - gnuserv.el cat >gnuserv.el <<'@EOF' ; -*-Emacs-Lisp-*- ;;; GNU Emacs server support code ;;; ;;; This file is not part of GNU Emacs (yet). ;;; Copyright (c) 1989 Andrew P. Norman & FSF. ;;; ;;; Copying is permitted under those conditions described by the GNU ;;; Emacs General Public License. ;;; ;;; Author: Andy Norman (ange%anorman@hplabs.hp.com) ;;; ;;; Please mail bugs and suggestions to the author at the above address. ;;; (defvar server-program "gnuserv" "*The program to use as the edit server") (defvar server-process nil "the current server process") (defvar server-string "" "the last input string from the server") (defvar current-client nil "the client we are currently talking to") (defvar server-clients nil "List of current server clients. Each element is (CLIENTID BUFFER...) where CLIENTID is an integer that can be given to the server process to identify a client. When a buffer is killed, it is removed from this list.") (defvar server-buffer-clients nil "List of clientids for clients requesting editing of current buffer.") (make-variable-buffer-local 'server-buffer-clients) (setq-default server-buffer-clients nil) (or (assq 'server-buffer-clients minor-mode-alist) (setq minor-mode-alist (cons '(server-buffer-clients " Server") minor-mode-alist))) ;; If a *server* buffer exists, ;; write STRING to it for logging purposes. (defun server-log (string) (if (get-buffer "*server*") (save-excursion (set-buffer "*server*") (goto-char (point-max)) (insert string) (or (bobp) (newline))))) (defun server-sentinel (proc msg) (cond ((eq (process-status proc) 'exit) (server-log (message "Server subprocess exited"))) ((eq (process-status proc) 'signal) (server-log (message "Server subprocess killed"))))) (defun server-process-filter (proc string) "Process incoming requests for gnu emacs to do some actions." (setq server-string (concat server-string string)) (if (string-match "\n$" server-string) ;wait till ends with a newline (progn (server-log server-string) (let ((header (read-from-string server-string))) (setq current-client (car header)) (condition-case oops (eval (car (read-from-string server-string (cdr header)))) (error (setq server-string "") (server-write-to-client current-client oops) (setq current-client nil)) (quit (setq server-string "") (server-write-to-client current-client oops) (setq current-client nil) (signal 'quit nil))) (setq server-string ""))))) (defun server-kill-outstanding-buffers () "Zap all buffers that have clients waiting for them to be finished." (interactive) (while server-clients (let ((buffer (nth 1 (car server-clients)))) ;need to do this for all buffers (server-kill-buffer buffer)))) ; destructively modifies server-clients (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 editing commands to this Emacs job. Prefix arg means just kill any existing server communications subprocess." (interactive "P") ;; kill it dead! (if server-process (progn (server-kill-outstanding-buffers) (set-process-sentinel server-process nil) (condition-case () (delete-process server-process) (error nil)))) ;; If we already had a server, clear out associated status. (if leave-dead nil (if server-process (server-log (message "Restarting server"))) (setq server-string "") (setq current-client nil) (let ((process-connection-type t)) (setq server-process (start-process "server" nil server-program))) (set-process-sentinel server-process 'server-sentinel) (set-process-filter server-process 'server-process-filter) (process-kill-without-query server-process))) (defun server-write-to-client (client form) "Write the given form to the given client via the server process." (if (and client (eq (process-status server-process) 'run)) (let ((s (format "%s:%s\n" client form))) (send-string server-process s) (server-log s)))) (defun server-eval (form) "Evaluate form and return result to client." (server-write-to-client current-client (eval form))) (defun server-eval-quickly (form) "Let client know that we've received the request, but eval the form afterwards in order to not keep the client waiting." (server-write-to-client current-client nil) (setq current-client nil) (eval form)) (defun server-make-window-visible () "Try to make this window even more visible." (if (and (boundp 'window-system) (boundp 'window-system-version) (eq window-system 'x) (eq window-system-version 11) (fboundp 'x-remap-window)) (x-remap-window))) (defun server-edit-file (lineno path) "Edit the given file for the client and save enough information such that server-kill-buffer can let the client know when the buffer has been finished with." (find-file path) (let ((old-clients (assq current-client server-clients)) (buffer (current-buffer))) (goto-line lineno) (setq server-buffer-clients (cons current-client server-buffer-clients)) (if old-clients ;client already waiting for buffers? (nconc old-clients (list buffer)) ;yes -- append this one as well (setq server-clients ;nope -- make a new record (cons (list current-client buffer) server-clients))) (server-make-window-visible))) (defun server-edit-file-quickly (lineno path) "Edit the given file for the client and goto the given line number. Note that unlike server-edit-file, no information is saved about clients waiting for this buffer to be killed." (find-file path) (goto-line lineno) (server-make-window-visible)) (defun server-edit-file-quickly-done () "Let the client know that emacs has received the preceeding requests to edit file(s) quickly via server-edit-file-quickly." (server-write-to-client current-client nil)) (defun server-kill-buffer (buffer) "One arg, a string or a buffer. Get rid of the specified buffer. Note that this function has been enhanced to allow for remote editing in the following way: If the buffer is waited upon by one or more clients, and a client is not waiting for other buffers to be killed, then the server is told to tell that client that the buffer has been killed. The buffer is then killed and another buffer is selected which is preferably one that has a client waiting on it." (interactive "bKill buffer ") (setq buffer (get-buffer buffer)) ; make sure it's a real buffer object (save-excursion (set-buffer buffer) (let ((old-clients server-clients)) (real-kill-buffer buffer) ;try to kill it (if (buffer-name buffer) ;succeeded in killing? nil ;nope (while old-clients (let ((client (car old-clients))) (delq buffer client) (if (cdr client) ;pending buffers? nil ;yep (server-write-to-client (car client) nil) ;nope, tell client (setq server-clients (delq client server-clients)))) (setq old-clients (cdr old-clients)))))) (if (not (buffer-name buffer)) ;try to select another client buffer (if server-clients (switch-to-buffer (nth 1 (car server-clients)))))) (defun server-kill-all-local-variables () "Eliminate all the buffer-local variable values of the current buffer. This buffer will then see the default values of all variables. NOTE: This function has been modified to ignore the variable server-buffer-clients." (let ((clients server-buffer-clients)) (real-kill-all-local-variables) (if clients (setq server-buffer-clients clients)))) (or (fboundp 'real-kill-buffer) (fset 'real-kill-buffer (symbol-function 'kill-buffer))) (fset 'kill-buffer 'server-kill-buffer) (or (fboundp 'real-kill-all-local-variables) (fset 'real-kill-all-local-variables (symbol-function 'kill-all-local-variables))) (fset 'kill-all-local-variables 'server-kill-all-local-variables) @EOF set `wc -lwc <gnuserv.el` if test $1$2$3 != 2309297969 then echo ERROR: wc results of gnuserv.el are $* should be 230 929 7969 fi chmod 644 gnuserv.el echo x - x11fns.diff cat >x11fns.diff <<'@EOF' *** x11fns.c~ Mon Jan 9 17:30:02 1989 --- x11fns.c Mon Jan 9 17:30:23 1989 *************** *** 827,832 #endif /* subprocesses */ } syms_of_xfns () { /* If not dumping, init_display ran before us, so don't override it. */ --- 827,848 ----- #endif /* subprocesses */ } + DEFUN ("x-remap-window", Fx_remap_window, Sx_remap_window, + 0, 0, 0, + "Maps / raises the X window such that is now visible.") + () + { + + if (WindowMapped) + XRaiseWindow(XXdisplay,XXwindow); + else + XMapRaised(XXdisplay,XXwindow); + + XFlush(XXdisplay); + return Qnil; + } + + syms_of_xfns () { /* If not dumping, init_display ran before us, so don't override it. */ *************** *** 876,881 defsubr (&Sx_rebind_keys); #endif notdef defsubr (&Sx_debug); } #endif /* HAVE_X_WINDOWS */ --- 892,898 ----- defsubr (&Sx_rebind_keys); #endif notdef defsubr (&Sx_debug); + defsubr (&Sx_remap_window); } #endif /* HAVE_X_WINDOWS */ @EOF set `wc -lwc <x11fns.diff` if test $1$2$3 != 511381005 then echo ERROR: wc results of x11fns.diff are $* should be 51 138 1005 fi chmod 644 x11fns.diff exit 0