-/* 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;
Display *dpy;
char *program;
int pix_w, pix_h, char_w, char_h;
+ int max_lines;
Bool pty_p;
XtIntervalId pipe_timer;
}
+# 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
"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
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 */
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);
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),
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),
}
else
{
- sprintf (buf, "%.100s: %.100s", progname, program);
+ sprintf (buf, "%.100s: %.100s", progname, cmd);
perror (buf);
}
}
- free (program);
+ free (cmd);
}
}
+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)
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,
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 */
*/
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);
}
}
}
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");
{
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;
}
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);
}
ret = s[0];
else /* EOF */
{
- if (d->pipe_id)
- XtRemoveInput (d->pipe_id);
- d->pipe_id = 0;
-
if (d->pid)
{
# ifdef DEBUG
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)
{
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;
}