]> code.delx.au - gnu-emacs/blobdiff - lib-src/emacsclient.c
Merged in changes from CVS trunk. Plus added lisp/term tweaks.
[gnu-emacs] / lib-src / emacsclient.c
index 034d5c9faa91913580f0afa4ac34b10b08e8f1df..4908d49441bbabae986b1468da19e111eb7b52bd 100644 (file)
@@ -41,6 +41,10 @@ Boston, MA 02110-1301, USA.  */
 # include <pwd.h>
 #endif /* not VMS */
 
+#include <signal.h>
+#include <errno.h>
+
+\f
 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;
 }
+
 \f
 /*
   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,103 @@ 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);
+    }
+
+  signal (signalnum, handle_sigcont);
+  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);
+}
 
 \f
 #if !defined (HAVE_SOCKETS) || defined (NO_SOCKETS_IN_FILE_SYSTEM)
@@ -257,7 +447,7 @@ main (argc, argv)
           argv[0]);
   fprintf (stderr, "on systems with Berkeley sockets.\n");
 
-  fail (argc, argv);
+  fail ();
 }
 
 #else /* HAVE_SOCKETS */
@@ -291,23 +481,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 +530,7 @@ main (argc, argv)
     {
       fprintf (stderr, "%s: ", argv[0]);
       perror ("socket");
-      fail (argc, argv);
+      fail ();
     }
 
   server.sun_family = AF_UNIX;
@@ -330,30 +538,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;
+
+     char *server_name = "server";
 
-    if (default_sock)
+     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 +601,7 @@ main (argc, argv)
                  }
 
                sock_status = socket_status (server.sun_path);
-               saved_errno = errno;
+                saved_errno = errno;
              }
            else
              errno = saved_errno;
@@ -407,7 +616,7 @@ main (argc, argv)
         if (0 != geteuid ())
           {
             fprintf (stderr, "%s: Invalid socket owner\n", argv[0]);
-            fail (argc, argv);
+            fail ();
           }
         break;
 
@@ -421,7 +630,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 +640,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 +660,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 +674,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;
 }