From http://www.jwz.org/xscreensaver/xscreensaver-5.36.tar.gz
[xscreensaver] / utils / textclient.c
index 794441f9c71d33913320d4566876b300cfe691b5..fe409287db6fbc0d015087505e9150b1937fa312 100644 (file)
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 2012-2014 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"
@@ -50,6 +52,9 @@
 # ifdef HAVE_UTIL_H
 #  include <util.h>
 # endif
+# ifdef HAVE_SYS_TERMIOS_H
+#  include <sys/termios.h>
+# endif
 #endif /* HAVE_FORKPTY */
 
 #undef DEBUG
@@ -93,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
@@ -110,19 +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"))
+  len = 17; /* strlen("xscreensaver-text") */
+  if (oprogram_size >= len &&
+    !memcmp (oprogram, "xscreensaver-text", len) &&
+    (oprogram[len] == ' ' || !oprogram[len]))
     {
-      if (d->char_w)
-        sprintf (program + strlen(program), " --cols %d", d->char_w);
-      if (d->max_lines)
-        sprintf (program + strlen(program), " --lines %d", d->max_lines);
+      /* 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";
+            }
+
+          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 */
     }
 
-  strcat (program, " ) 2>&1");
+  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
@@ -137,6 +242,15 @@ launch_text_generator (text_data *d)
       ws.ws_ypixel = d->pix_h;
       
       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 */
@@ -152,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);
@@ -186,7 +309,7 @@ launch_text_generator (text_data *d)
       }
 
       if (d->pipe) abort();
-      if ((d->pipe = popen (program, "r")))
+      if ((d->pipe = popen (cmd, "r")))
        {
           if (d->pipe_id) abort();
          d->pipe_id =
@@ -199,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);
 }
 
 
@@ -297,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 */
 
@@ -307,6 +433,9 @@ textclient_reshape (text_data *d,
    */
   if (!strcmp (d->program, "xscreensaver-text"))
     {
+# ifdef DEBUG
+      fprintf (stderr, "%s: textclient: reshape relaunch\n", progname);
+# endif
       close_pipe (d);
       d->input_available_p = False;
       start_timer (d);
@@ -357,6 +486,10 @@ 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