From http://www.jwz.org/xscreensaver/xscreensaver-5.36.tar.gz
[xscreensaver] / utils / textclient.c
index ed2997e0b0a0a63dc8510c97fdf56cd012e1324f..fe409287db6fbc0d015087505e9150b1937fa312 100644 (file)
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 2012 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 2012-2016 Jamie Zawinski <jwz@jwz.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
  * usePty: bool                Whether to run the command interactively.
  * metaSendsESC: bool  Whether to send Alt-x as ESC x in pty-mode.
  * swapBSDEL: bool     Swap Backspace and Delete in pty-mode.
+ *
+ * On iOS and Android, textclient-mobile.c is used instead.
  */
 
 #include "utils.h"
 
-#ifndef USE_IPHONE /* whole file -- see OSX/iostextclient.m */
+#if !defined(USE_IPHONE) && !defined(HAVE_ANDROID)  /* whole file */
 
 #include "textclient.h"
 #include "resources.h"
 # ifdef HAVE_UTIL_H
 #  include <util.h>
 # endif
+# ifdef HAVE_SYS_TERMIOS_H
+#  include <sys/termios.h>
+# endif
 #endif /* HAVE_FORKPTY */
 
-/*#define DEBUG*/
+#undef DEBUG
 
 extern const char *progname;
 
@@ -60,6 +65,7 @@ struct text_data {
   Display *dpy;
   char *program;
   int pix_w, pix_h, char_w, char_h;
+  int max_lines;
 
   Bool pty_p;
   XtIntervalId pipe_timer;
@@ -92,16 +98,53 @@ subproc_cb (XtPointer closure, int *source, XtInputId *id)
 }
 
 
+# define BACKSLASH(c) \
+  (! ((c >= 'a' && c <= 'z') || \
+      (c >= 'A' && c <= 'Z') || \
+      (c >= '0' && c <= '9') || \
+      c == '.' || c == '_' || c == '-' || c == '+' || c == '/'))
+
+#ifdef HAVE_COCOA
+static char *
+escape_str (char *s, const char *src)
+{
+  while (*src) {
+    char c = *src++;
+    if (BACKSLASH(c)) *s++ = '\\';
+    *s++ = c;
+  }
+  return s;
+}
+#endif
+
 static void
 launch_text_generator (text_data *d)
 {
   XtAppContext app = XtDisplayToApplicationContext (d->dpy);
   char buf[255];
   const char *oprogram = d->program;
-  char *program = (char *) malloc (strlen (oprogram) + 50);
+  char *s;
+
+  size_t oprogram_size = strlen(oprogram);
+  size_t len;
+
+# ifdef HAVE_COCOA
+  /* /bin/sh on OS X 10.10 wipes out the PATH. */
+  const char *path = getenv("PATH");
+  size_t cmd_capacity = (oprogram_size + strlen(path)) * 2 + 100;
+  char *cmd = s = malloc (cmd_capacity);
+  strcpy (s, "export PATH=");
+  s += strlen (s);
+  s = escape_str (s, path);
+  strcpy (s, "; ");
+  s += strlen (s);
+# else
+  char *cmd = s = malloc ((strlen(oprogram)) * 2 + 100);
+# endif
 
-  strcpy (program, "( ");
-  strcat (program, oprogram);
+  strcpy (s, "( ");
+  strcat (s, oprogram);
+  s += strlen (s);
 
   /* Kludge!  Special-case "xscreensaver-text" to tell it how wide
      the screen is.  We used to do this by just always feeding
@@ -109,14 +152,82 @@ launch_text_generator (text_data *d)
      "xscreensaver-text --cols %d", but that makes things blow up
      if someone ever uses a --program that includes a % anywhere.
    */
-  if (!strcmp (oprogram, "xscreensaver-text"))
-    sprintf (program + strlen(program), " --cols %d", d->char_w);
+  len = 17; /* strlen("xscreensaver-text") */
+  if (oprogram_size >= len &&
+    !memcmp (oprogram, "xscreensaver-text", len) &&
+    (oprogram[len] == ' ' || !oprogram[len]))
+    {
+      /* strstr is sloppy here. Technically, we should be parsing the command
+         line to identify flags and their arguments. This will blow up if one
+         of those pesky end users could set .textLiteral to "--cols".
+       */
+      if (d->char_w && !strstr (oprogram, "--cols "))
+        sprintf (s, " --cols %d", d->char_w);
+      if (d->max_lines && !strstr (oprogram, "--lines "))
+        sprintf (s, " --lines %d", d->max_lines);
+      s += strlen(s);
+
+# ifdef HAVE_COCOA
+      /* Also special-case "xscreensaver-text" to specify the text content on
+         the command line. defaults(1) on macOS doesn't know about the default
+         screenhack resources that don't make it into the
+         ~/Library/Preferences/ByHost/org.jwz.xscreensaver.*.plist.
+       */
+
+      char *text_mode_flag = " --date";
+      char *value_res = NULL;
+      char *text_mode = get_string_resource (d->dpy, "textMode", "String");
+
+      if (text_mode)
+        {
+          if (!strcmp (text_mode, "1") || !strcmp (text_mode, "literal"))
+            {
+              text_mode_flag = " --text";
+              value_res = "textLiteral";
+            }
+          else if (!strcmp (text_mode, "2") || !strcmp (text_mode, "file"))
+            {
+              text_mode_flag = " --file";
+              value_res = "textFile";
+            }
+          else if (!strcmp (text_mode, "3") || !strcmp (text_mode, "url"))
+            {
+              text_mode_flag = " --url";
+              value_res = "textURL";
+            }
+          else if (!strcmp (text_mode, "4") || !strcmp (text_mode, "program"))
+            {
+              text_mode_flag = " --program";
+              value_res = "textProgram";
+            }
 
-  strcat (program, " ) 2>&1");
+          free (text_mode);
+        }
+
+      strcpy (s, text_mode_flag);
+      s += strlen (s);
+
+      if (value_res)
+        {
+          size_t old_s = s - cmd;
+          char *value = get_string_resource (d->dpy, value_res, "");
+          if (!value)
+            value = strdup("");
+          cmd = realloc(cmd, cmd_capacity + strlen(value) * 2);
+          s = cmd + old_s;
+          *s = ' ';
+          ++s;
+          s = escape_str(s, value);
+          free(value);
+        }
+# endif /* HAVE_COCOA */
+    }
+
+  strcpy (s, " ) 2>&1");
 
 # ifdef DEBUG
   fprintf (stderr, "%s: textclient: launch %s: %s\n", progname, 
-           (d->pty_p ? "pty" : "pipe"), program);
+           (d->pty_p ? "pty" : "pipe"), cmd);
 # endif
 
 #ifdef HAVE_FORKPTY
@@ -130,7 +241,16 @@ launch_text_generator (text_data *d)
       ws.ws_xpixel = d->pix_w;
       ws.ws_ypixel = d->pix_h;
       
-      d->pipe = NULL;
+      d->pipe = 0;
+
+# ifdef HAVE_COCOA
+      if (getenv ("MallocScribble"))
+        /* This is here to stop me from wasting my time trying to answer
+           this question the next time I forget about it. */
+        fprintf (stderr, "%s: WARNING: forkpty hates 'Enable Guard Malloc'\n",
+                 progname);
+# endif
+
       if ((d->pid = forkpty(&fd, NULL, NULL, &ws)) < 0)
        {
           /* Unable to fork */
@@ -146,8 +266,17 @@ launch_text_generator (text_data *d)
             abort();
           av[i++] = "/bin/sh";
           av[i++] = "-c";
-          av[i++] = program;
+          av[i++] = cmd;
           av[i] = 0;
+# ifdef DEBUG
+          {
+            int j;
+            fprintf (stderr, "%s: textclient: execvp:", progname);
+            for (j = 0; j < i; j++)
+              fprintf (stderr, " %s", av[j]);
+            fprintf (stderr, "\n");
+          }
+# endif
           execvp (av[0], av);
           sprintf (buf, "%.100s: %.100s", progname, oprogram);
          perror (buf);
@@ -156,7 +285,9 @@ launch_text_generator (text_data *d)
       else
        {
           /* This is the parent fork. */
+          if (d->pipe) abort();
          d->pipe = fdopen (fd, "r+");
+          if (d->pipe_id) abort();
          d->pipe_id =
            XtAppAddInput (app, fileno (d->pipe),
                           (XtPointer) (XtInputReadMask | XtInputExceptMask),
@@ -177,8 +308,10 @@ launch_text_generator (text_data *d)
         protected_stdin_p = 1;
       }
 
-      if ((d->pipe = popen (program, "r")))
+      if (d->pipe) abort();
+      if ((d->pipe = popen (cmd, "r")))
        {
+          if (d->pipe_id) abort();
          d->pipe_id =
            XtAppAddInput (app, fileno (d->pipe),
                           (XtPointer) (XtInputReadMask | XtInputExceptMask),
@@ -189,12 +322,12 @@ launch_text_generator (text_data *d)
        }
       else
        {
-          sprintf (buf, "%.100s: %.100s", progname, program);
+          sprintf (buf, "%.100s: %.100s", progname, cmd);
          perror (buf);
        }
     }
 
-  free (program);
+  free (cmd);
 }
 
 
@@ -211,10 +344,58 @@ relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
 }
 
 
+static void
+start_timer (text_data *d)
+{
+  XtAppContext app = XtDisplayToApplicationContext (d->dpy);
+
+# ifdef DEBUG
+  fprintf (stderr, "%s: textclient: relaunching in %d\n", progname, 
+           (int) d->subproc_relaunch_delay);
+# endif
+  if (d->pipe_timer)
+    XtRemoveTimeOut (d->pipe_timer);
+  d->pipe_timer =
+    XtAppAddTimeOut (app, d->subproc_relaunch_delay,
+                     relaunch_generator_timer,
+                     (XtPointer) d);
+}
+
+
+static void
+close_pipe (text_data *d)
+{
+  if (d->pid)
+    {
+# ifdef DEBUG
+      fprintf (stderr, "%s: textclient: kill %d\n", progname, d->pid);
+# endif
+      kill (d->pid, SIGTERM);
+    }
+  d->pid = 0;
+
+  if (d->pipe_id)
+    XtRemoveInput (d->pipe_id);
+  d->pipe_id = 0;
+
+  if (d->pipe)
+    {
+# ifdef DEBUG
+      fprintf (stderr, "%s: textclient: pclose\n", progname);
+# endif
+      pclose (d->pipe);
+    }
+  d->pipe = 0;
+
+
+}
+
+
 void
 textclient_reshape (text_data *d,
                     int pix_w, int pix_h,
-                    int char_w, int char_h)
+                    int char_w, int char_h,
+                    int max_lines)
 {
 # if defined(HAVE_FORKPTY) && defined(TIOCSWINSZ)
 
@@ -222,6 +403,7 @@ textclient_reshape (text_data *d,
   d->pix_h  = pix_h;
   d->char_w = char_w;
   d->char_h = char_h;
+  d->max_lines = max_lines;
 
 # ifdef DEBUG
   fprintf (stderr, "%s: textclient: reshape: %dx%d, %dx%d\n", progname,
@@ -238,6 +420,9 @@ textclient_reshape (text_data *d,
       ws.ws_ypixel = pix_h;
       ioctl (fileno (d->pipe), TIOCSWINSZ, &ws);
       kill (d->pid, SIGWINCH);
+#  ifdef DEBUG
+      fprintf (stderr, "%s: textclient: SIGWINCH\n", progname);
+#  endif
     }
 # endif /* HAVE_FORKPTY && TIOCSWINSZ */
 
@@ -248,14 +433,12 @@ textclient_reshape (text_data *d,
    */
   if (!strcmp (d->program, "xscreensaver-text"))
     {
-      if (d->pid)
-        kill (d->pid, SIGTERM);
-      if (d->pipe_id)
-        XtRemoveInput (d->pipe_id);
-      if (d->pipe)
-        pclose (d->pipe);
+# ifdef DEBUG
+      fprintf (stderr, "%s: textclient: reshape relaunch\n", progname);
+# endif
+      close_pipe (d);
       d->input_available_p = False;
-      relaunch_generator_timer (d, 0);
+      start_timer (d);
     }
 }
 
@@ -283,7 +466,11 @@ textclient_open (Display *dpy)
     }
 
   d->subproc_relaunch_delay =
-    (1000 * get_integer_resource (dpy, "relaunchDelay", "Time"));
+    get_integer_resource (dpy, "relaunchDelay", "Time");
+  if (d->subproc_relaunch_delay < 1)
+    d->subproc_relaunch_delay = 1;
+  d->subproc_relaunch_delay *= 1000;
+
 
   d->meta_sends_esc_p = get_boolean_resource (dpy, "metaSendsESC", "Boolean");
   d->swap_bs_del_p    = get_boolean_resource (dpy, "swapBSDEL",    "Boolean");
@@ -299,11 +486,15 @@ textclient_open (Display *dpy)
       {
         d->pty_p = 1;
         d->program = strdup (getenv ("SHELL"));
+#  ifdef DEBUG
+        fprintf (stderr, "%s: textclient: standalone: %s\n",
+                 progname, d->program);
+#  endif
       }
   }
 # endif
 
-  launch_text_generator (d);
+  start_timer (d);
 
   return d;
 }
@@ -316,14 +507,12 @@ textclient_close (text_data *d)
   fprintf (stderr, "%s: textclient: free\n", progname);
 # endif
 
+  close_pipe (d);
   if (d->program)
     free (d->program);
-  if (d->pipe_id)
-    XtRemoveInput (d->pipe_id);
-  if (d->pipe)
-    pclose (d->pipe);
   if (d->pipe_timer)
     XtRemoveTimeOut (d->pipe_timer);
+  d->pipe_timer = 0;
   memset (d, 0, sizeof (*d));
   free (d);
 }
@@ -350,10 +539,6 @@ textclient_getc (text_data *d)
         ret = s[0];
       else             /* EOF */
         {
-          if (d->pipe_id)
-            XtRemoveInput (d->pipe_id);
-          d->pipe_id = 0;
-
          if (d->pid)
            {
 # ifdef DEBUG
@@ -361,17 +546,10 @@ textclient_getc (text_data *d)
                        progname, d->pid);
 # endif
              waitpid (d->pid, NULL, 0);
-             fclose (d->pipe);
               d->pid = 0;
            }
-         else
-           {
-# ifdef DEBUG
-              fprintf (stderr, "%s: textclient: pclose\n", progname);
-# endif
-             pclose (d->pipe);
-           }
-          d->pipe = 0;
+
+          close_pipe (d);
 
           if (d->out_column > 0)
             {
@@ -382,14 +560,7 @@ textclient_getc (text_data *d)
               d->out_buffer = "\r\n\r\n";
             }
 
-# ifdef DEBUG
-          fprintf (stderr, "%s: textclient: relaunching in %d\n", progname, 
-                   (int) d->subproc_relaunch_delay);
-# endif
-          d->pipe_timer =
-            XtAppAddTimeOut (app, d->subproc_relaunch_delay,
-                             relaunch_generator_timer,
-                             (XtPointer) d);
+          start_timer (d);
         }
       d->input_available_p = False;
     }