diff options
Diffstat (limited to 'lib-src')
-rw-r--r-- | lib-src/emacsclient.c | 521 |
1 files changed, 432 insertions, 89 deletions
diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c index 75fa9ebe8e7..b795e627048 100644 --- a/lib-src/emacsclient.c +++ b/lib-src/emacsclient.c @@ -41,6 +41,10 @@ Boston, MA 02110-1301, USA. */ # include <pwd.h> #endif /* not VMS */ +#include <signal.h> +#include <errno.h> + + char *getenv (), *getwd (); char *(getcwd) (); @@ -51,15 +55,27 @@ char *(getcwd) (); /* Name used to invoke this program. */ char *progname; +/* The first argument to main. */ +int main_argc; + +/* The second argument to main. */ +char **main_argv; + /* Nonzero means don't wait for a response from Emacs. --no-wait. */ int nowait = 0; /* Nonzero means args are expressions to be evaluated. --eval. */ int eval = 0; +/* Nonzero means open a new graphical frame. */ +int window_system = 0; + /* The display on which Emacs should work. --display. */ char *display = NULL; +/* Nonzero means open a new Emacs frame on the current terminal. */ +int tty = 0; + /* If non-NULL, the name of an editor to fallback to if the server is not running. --alternate-editor. */ const char * alternate_editor = NULL; @@ -75,6 +91,8 @@ struct option longopts[] = { "eval", no_argument, NULL, 'e' }, { "help", no_argument, NULL, 'H' }, { "version", no_argument, NULL, 'V' }, + { "tty", no_argument, NULL, 't' }, + { "current-frame", no_argument, NULL, 'c' }, { "alternate-editor", required_argument, NULL, 'a' }, { "socket-name", required_argument, NULL, 's' }, { "display", required_argument, NULL, 'd' }, @@ -90,11 +108,19 @@ decode_options (argc, argv) char **argv; { alternate_editor = getenv ("ALTERNATE_EDITOR"); + display = getenv ("DISPLAY"); + if (display && strlen (display) == 0) + display = NULL; + + if (display) + window_system = 1; + else + tty = 1; while (1) { int opt = getopt_long (argc, argv, - "VHnea:s:d:", longopts, 0); + "VHnea:s:d:tc", longopts, 0); if (opt == EOF) break; @@ -131,6 +157,16 @@ decode_options (argc, argv) exit (EXIT_SUCCESS); break; + case 't': + tty = 1; + window_system = 0; + break; + + case 'c': + window_system = 0; + tty = 0; + break; + case 'H': print_help_and_exit (); break; @@ -141,6 +177,11 @@ decode_options (argc, argv) break; } } + + if (tty) { + nowait = 0; + display = 0; + } } void @@ -154,6 +195,8 @@ Every FILE can be either just a FILENAME or [+LINE[:COLUMN]] FILENAME.\n\ The following OPTIONS are accepted:\n\ -V, --version Just print a version info and return\n\ -H, --help Print this usage information message\n\ +-t, --tty Open a new Emacs frame on the current terminal\n\ +-c, --current-frame Do not create a new frame; use the current Emacs frame\n\ -n, --no-wait Don't wait for the server to return\n\ -e, --eval Evaluate the FILE arguments as ELisp expressions\n\ -d, --display=DISPLAY Visit the file in the given display\n\ @@ -166,19 +209,50 @@ Report bugs to bug-gnu-emacs@gnu.org.\n", progname); exit (EXIT_SUCCESS); } -/* In NAME, insert a & before each &, each space, each newline, and +/* Like malloc but get fatal error if memory is exhausted. */ + +long * +xmalloc (size) + unsigned int size; +{ + long *result = (long *) malloc (size); + if (result == NULL) + { + perror ("malloc"); + exit (EXIT_FAILURE); + } + return result; +} + +/* Like strdup but get a fatal error if memory is exhausted. */ + +char * +xstrdup (const char *s) +{ + char *result = strdup (s); + if (result == NULL) + { + perror ("strdup"); + exit (EXIT_FAILURE); + } + return result; +} + +/* In STR, insert a & before each &, each space, each newline, and any initial -. Change spaces to underscores, too, so that the - return value never contains a space. */ + return value never contains a space. + + Does not change the string. Outputs the result to STREAM. */ void -quote_file_name (name, stream) - char *name; +quote_argument (str, stream) + char *str; FILE *stream; { - char *copy = (char *) malloc (strlen (name) * 2 + 1); + char *copy = (char *) xmalloc (strlen (str) * 2 + 1); char *p, *q; - p = name; + p = str; q = copy; while (*p) { @@ -196,7 +270,7 @@ quote_file_name (name, stream) } else { - if (*p == '&' || (*p == '-' && p == name)) + if (*p == '&' || (*p == '-' && p == str)) *q++ = '&'; *q++ = *p++; } @@ -208,34 +282,53 @@ quote_file_name (name, stream) free (copy); } -/* Like malloc but get fatal error if memory is exhausted. */ -long * -xmalloc (size) - unsigned int size; +/* The inverse of quote_argument. Removes quoting in string STR by + modifying the string in place. Returns STR. */ + +char * +unquote_argument (str) + char *str; { - long *result = (long *) malloc (size); - if (result == NULL) - { - perror ("malloc"); - exit (EXIT_FAILURE); - } - return result; + char *p, *q; + + if (! str) + return str; + + p = str; + q = str; + while (*p) + { + if (*p == '&') + { + p++; + if (*p == '&') + *p = '&'; + else if (*p == '_') + *p = ' '; + else if (*p == 'n') + *p = '\n'; + else if (*p == '-') + *p = '-'; + } + *q++ = *p++; + } + *q = 0; + return str; } + /* Try to run a different command, or --if no alternate editor is defined-- exit with an errorcode. */ void -fail (argc, argv) - int argc; - char **argv; +fail (void) { if (alternate_editor) { int i = optind - 1; - execvp (alternate_editor, argv + i); + execvp (alternate_editor, main_argv + i); return; } else @@ -244,6 +337,101 @@ fail (argc, argv) } } +/* The process id of Emacs. */ +int emacs_pid; + +/* File handles for communicating with Emacs. */ +FILE *out, *in; + +/* A signal handler that passes the signal to the Emacs process. + Useful for SIGWINCH. */ + +SIGTYPE +pass_signal_to_emacs (int signalnum) +{ + int old_errno = errno; + + if (emacs_pid) + kill (emacs_pid, signalnum); + + signal (signalnum, pass_signal_to_emacs); + errno = old_errno; +} + +/* Signal handler for SIGCONT; notify the Emacs process that it can + now resume our tty frame. */ + +SIGTYPE +handle_sigcont (int signalnum) +{ + int old_errno = errno; + + if (tcgetpgrp (1) == getpgrp ()) + { + /* We are in the foreground. */ + fprintf (out, "-resume \n"); + fflush (out); + fsync (fileno (out)); + } + else + { + /* We are in the background; cancel the continue. */ + kill (getpid (), SIGSTOP); + } + errno = old_errno; +} + +/* Signal handler for SIGTSTP; notify the Emacs process that we are + going to sleep. Normally the suspend is initiated by Emacs via + server-handle-suspend-tty, but if the server gets out of sync with + reality, we may get a SIGTSTP on C-z. Handling this signal and + notifying Emacs about it should get things under control again. */ + +SIGTYPE +handle_sigtstp (int signalnum) +{ + int old_errno = errno; + sigset_t set; + + if (out) + { + fprintf (out, "-suspend \n"); + fflush (out); + fsync (fileno (out)); + } + + /* Unblock this signal and call the default handler by temprarily + changing the handler and resignalling. */ + sigprocmask (SIG_BLOCK, NULL, &set); + sigdelset (&set, signalnum); + signal (signalnum, SIG_DFL); + kill (getpid (), signalnum); + sigprocmask (SIG_SETMASK, &set, NULL); /* Let's the above signal through. */ + signal (signalnum, handle_sigtstp); + + errno = old_errno; +} + +/* Set up signal handlers before opening a frame on the current tty. */ + +void +init_signals (void) +{ + /* Set up signal handlers. */ + signal (SIGWINCH, pass_signal_to_emacs); + + /* Don't pass SIGINT and SIGQUIT to Emacs, because it has no way of + deciding which terminal the signal came from. C-g is now a + normal input event on secondary terminals. */ +#if 0 + signal (SIGINT, pass_signal_to_emacs); + signal (SIGQUIT, pass_signal_to_emacs); +#endif + + signal (SIGCONT, handle_sigcont); + signal (SIGTSTP, handle_sigtstp); + signal (SIGTTOU, handle_sigtstp); +} #if !defined (HAVE_SOCKETS) || defined (NO_SOCKETS_IN_FILE_SYSTEM) @@ -257,7 +445,7 @@ main (argc, argv) argv[0]); fprintf (stderr, "on systems with Berkeley sockets.\n"); - fail (argc, argv); + fail (); } #else /* HAVE_SOCKETS */ @@ -291,23 +479,41 @@ socket_status (socket_name) return 0; } +/* Returns 1 if PREFIX is a prefix of STRING. */ +static int +strprefix (char *prefix, char *string) +{ + int i; + if (! prefix) + return 1; + + if (!string) + return 0; + + for (i = 0; prefix[i]; i++) + if (!string[i] || string[i] != prefix[i]) + return 0; + return 1; +} + int main (argc, argv) int argc; char **argv; { int s, i, needlf = 0; - FILE *out, *in; struct sockaddr_un server; char *cwd, *str; char string[BUFSIZ]; + main_argc = argc; + main_argv = argv; progname = argv[0]; /* Process options. */ decode_options (argc, argv); - if ((argc - optind < 1) && !eval) + if ((argc - optind < 1) && !eval && !tty && !window_system) { fprintf (stderr, "%s: file name or argument required\n", progname); fprintf (stderr, "Try `%s --help' for more information\n", progname); @@ -322,7 +528,7 @@ main (argc, argv) { fprintf (stderr, "%s: ", argv[0]); perror ("socket"); - fail (argc, argv); + fail (); } server.sun_family = AF_UNIX; @@ -330,30 +536,31 @@ main (argc, argv) { int sock_status = 0; int default_sock = !socket_name; - int saved_errno; - char *server_name = "server"; - - if (socket_name && !index (socket_name, '/') && !index (socket_name, '\\')) - { /* socket_name is a file name component. */ - server_name = socket_name; - socket_name = NULL; - default_sock = 1; /* Try both UIDs. */ - } + int saved_errno = 0; - if (default_sock) + char *server_name = "server"; + + if (socket_name && !index (socket_name, '/') && !index (socket_name, '\\')) + { /* socket_name is a file name component. */ + server_name = socket_name; + socket_name = NULL; + default_sock = 1; /* Try both UIDs. */ + } + + if (default_sock) { - socket_name = alloca (100 + strlen (server_name)); - sprintf (socket_name, "/tmp/emacs%d/%s", - (int) geteuid (), server_name); + socket_name = alloca (100 + strlen (server_name)); + sprintf (socket_name, "/tmp/emacs%d/%s", + (int) geteuid (), server_name); } if (strlen (socket_name) < sizeof (server.sun_path)) strcpy (server.sun_path, socket_name); else { - fprintf (stderr, "%s: socket-name %s too long", - argv[0], socket_name); - exit (EXIT_FAILURE); + fprintf (stderr, "%s: socket-name %s too long", + argv[0], socket_name); + fail (); } /* See if the socket exists, and if it's owned by us. */ @@ -392,7 +599,7 @@ main (argc, argv) } sock_status = socket_status (server.sun_path); - saved_errno = errno; + saved_errno = errno; } else errno = saved_errno; @@ -407,7 +614,7 @@ main (argc, argv) if (0 != geteuid ()) { fprintf (stderr, "%s: Invalid socket owner\n", argv[0]); - fail (argc, argv); + fail (); } break; @@ -421,7 +628,7 @@ To start the server in Emacs, type \"M-x server-start\".\n", else fprintf (stderr, "%s: can't stat %s: %s\n", argv[0], server.sun_path, strerror (saved_errno)); - fail (argc, argv); + fail (); break; } } @@ -431,18 +638,18 @@ To start the server in Emacs, type \"M-x server-start\".\n", { fprintf (stderr, "%s: ", argv[0]); perror ("connect"); - fail (argc, argv); + fail (); } - /* We use the stream OUT to send our command to the server. */ + /* We use the stream OUT to send our commands to the server. */ if ((out = fdopen (s, "r+")) == NULL) { fprintf (stderr, "%s: ", argv[0]); perror ("fdopen"); - fail (argc, argv); + fail (); } - /* We use the stream IN to read the response. + /* We use the stream IN to read the responses. We used to use just one stream for both output and input on the socket, but reversing direction works nonportably: on some systems, the output appears as the first input; @@ -451,7 +658,7 @@ To start the server in Emacs, type \"M-x server-start\".\n", { fprintf (stderr, "%s: ", argv[0]); perror ("fdopen"); - fail (argc, argv); + fail (); } #ifdef HAVE_GETCWD @@ -465,87 +672,223 @@ To start the server in Emacs, type \"M-x server-start\".\n", #ifdef HAVE_GETCWD fprintf (stderr, "%s: %s (%s)\n", argv[0], - "Cannot get current working directory", strerror (errno)); + "cannot get current working directory", strerror (errno)); #else fprintf (stderr, "%s: %s (%s)\n", argv[0], string, strerror (errno)); #endif - fail (argc, argv); + fail (); } + /* First of all, send our version number for verification. */ + fprintf (out, "-version %s ", VERSION); + + /* Send over our environment. */ + { + extern char **environ; + int i; + for (i = 0; environ[i]; i++) + { + char *name = xstrdup (environ[i]); + char *value = strchr (name, '='); + if (value && strlen (value) > 1) + { + *value++ = 0; + fprintf (out, "-env "); + quote_argument (name, out); + fprintf (out, " "); + quote_argument (value, out); + fprintf (out, " "); + fflush (out); + } + free (name); + } + } + + retry: if (nowait) fprintf (out, "-nowait "); - if (eval) - fprintf (out, "-eval "); - if (display) { fprintf (out, "-display "); - quote_file_name (display, out); + quote_argument (display, out); + fprintf (out, " "); + } + + if (tty) + { + char *tty_name = ttyname (fileno (stdin)); + char *type = getenv ("TERM"); + + if (! tty_name) + { + fprintf (stderr, "%s: could not get terminal name\n", progname); + fail (); + } + + if (! type) + { + fprintf (stderr, "%s: please set the TERM variable to your terminal type\n", + progname); + fail (); + } + + if (! strcmp (type, "eterm")) + { + /* This causes nasty, MULTI_KBOARD-related input lockouts. */ + fprintf (stderr, "%s: opening a frame in an Emacs term buffer" + " is not supported\n", progname); + fail (); + } + + init_signals (); + + fprintf (out, "-tty "); + quote_argument (tty_name, out); + fprintf (out, " "); + quote_argument (type, out); fprintf (out, " "); } + if (window_system) + fprintf (out, "-window-system "); + if ((argc - optind > 0)) { for (i = optind; i < argc; i++) { + int relative = 0; + if (eval) - ; /* Don't prepend any cwd or anything like that. */ - else if (*argv[i] == '+') - { + { + /* Don't prepend any cwd or anything like that. */ + fprintf (out, "-eval "); + quote_argument (argv[i], out); + fprintf (out, " "); + continue; + } + + if (*argv[i] == '+') + { char *p = argv[i] + 1; while (isdigit ((unsigned char) *p) || *p == ':') p++; - if (*p != 0) - { - quote_file_name (cwd, out); - fprintf (out, "/"); - } - } - else if (*argv[i] != '/') - { - quote_file_name (cwd, out); - fprintf (out, "/"); - } - - quote_file_name (argv[i], out); - fprintf (out, " "); - } + if (*p == 0) + { + fprintf (out, "-position "); + quote_argument (argv[i], out); + fprintf (out, " "); + continue; + } + else + relative = 1; + } + else if (*argv[i] != '/') + relative = 1; + + fprintf (out, "-file "); + if (relative) + { + quote_argument (cwd, out); + fprintf (out, "/"); + } + quote_argument (argv[i], out); + fprintf (out, " "); + } } else { - while ((str = fgets (string, BUFSIZ, stdin))) - { - quote_file_name (str, out); - } - fprintf (out, " "); + if (!tty && !window_system) + { + while ((str = fgets (string, BUFSIZ, stdin))) + { + if (eval) + fprintf (out, "-eval "); + else + fprintf (out, "-file "); + quote_argument (str, out); + } + fprintf (out, " "); + } } fprintf (out, "\n"); fflush (out); + fsync (fileno (out)); - /* Maybe wait for an answer. */ - if (nowait) - return EXIT_SUCCESS; - - if (!eval) + /* Wait for an answer. */ + if (!eval && !tty && !nowait) { printf ("Waiting for Emacs..."); needlf = 2; } fflush (stdout); + fsync (1); /* Now, wait for an answer and print any messages. */ while ((str = fgets (string, BUFSIZ, in))) { - if (needlf == 2) - printf ("\n"); - printf ("%s", str); - needlf = str[0] == '\0' ? needlf : str[strlen (str) - 1] != '\n'; + char *p = str + strlen (str) - 1; + while (p > str && *p == '\n') + *p-- = 0; + + if (strprefix ("-good-version ", str)) + { + /* -good-version: The versions match. */ + } + else if (strprefix ("-emacs-pid ", str)) + { + /* -emacs-pid PID: The process id of the Emacs process. */ + emacs_pid = strtol (string + strlen ("-emacs-pid"), NULL, 10); + } + else if (strprefix ("-window-system-unsupported ", str)) + { + /* -window-system-unsupported: Emacs was compiled without X + support. Try again on the terminal. */ + window_system = 0; + nowait = 0; + tty = 1; + goto retry; + } + else if (strprefix ("-print ", str)) + { + /* -print STRING: Print STRING on the terminal. */ + str = unquote_argument (str + strlen ("-print ")); + if (needlf) + printf ("\n"); + printf ("%s", str); + needlf = str[0] == '\0' ? needlf : str[strlen (str) - 1] != '\n'; + } + else if (strprefix ("-error ", str)) + { + /* -error DESCRIPTION: Signal an error on the terminal. */ + str = unquote_argument (str + strlen ("-error ")); + if (needlf) + printf ("\n"); + printf ("*ERROR*: %s", str); + needlf = str[0] == '\0' ? needlf : str[strlen (str) - 1] != '\n'; + } + else if (strprefix ("-suspend ", str)) + { + /* -suspend: Suspend this terminal, i.e., stop the process. */ + if (needlf) + printf ("\n"); + needlf = 0; + kill (0, SIGSTOP); + } + else + { + /* Unknown command. */ + if (needlf) + printf ("\n"); + printf ("*ERROR*: Unknown message: %s", str); + needlf = str[0] == '\0' ? needlf : str[strlen (str) - 1] != '\n'; + } } if (needlf) printf ("\n"); fflush (stdout); + fsync (1); return EXIT_SUCCESS; } |