/* subprocs.c --- choosing, spawning, and killing screenhacks. * xscreensaver, Copyright © 1991-2021 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation. No representations are made about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include /* not used for much... */ #include #include /* For XtAppAddSignal */ #ifndef ESRCH # include #endif #include /* sys/resource.h needs this for timeval */ #include /* for PATH_MAX */ #ifdef HAVE_SYS_WAIT_H # include /* for waitpid() and associated macros */ #endif #ifdef HAVE_SETRLIMIT # include /* for setrlimit() and RLIMIT_AS */ #endif #include /* for the signal names */ #include #ifdef ENABLE_NLS # include # include # define _(S) gettext(S) #else # define _(S) (S) #endif #include "xscreensaver.h" #include "exec.h" #include "yarandom.h" #include "visual.h" /* for id_to_visual() */ #include "atoms.h" enum job_status { job_running, /* the process is still alive */ job_stopped, /* we have sent it a STOP signal */ job_killed, /* we have sent it a TERM signal */ job_dead /* we have wait()ed for it, and it's dead -- this state only occurs so that we can avoid calling free() from a signal handler. Shortly after going into this state, the list element will be removed. */ }; struct screenhack_job { char *name; pid_t pid; int screen; enum job_status status; time_t launched, killed; struct screenhack_job *next; }; static struct screenhack_job *jobs = 0; static void clean_job_list (void); static void await_dying_children (saver_info *si); static void describe_dead_child (saver_info *, pid_t, int wait_status, struct rusage); static XtSignalId xt_sigterm_id = 0; static int sigterm_received = 0; static XtSignalId xt_sigchld_id = 0; static int sigchld_received = 0; const char * signal_name (int signal) { /* sys_signame[], sys_siglist[], strsignal() and sigabbrev_np() are an unportable mess. */ switch (signal) { case SIGHUP: return "SIGHUP"; case SIGINT: return "SIGINT"; case SIGQUIT: return "SIGQUIT"; case SIGILL: return "SIGILL"; case SIGTRAP: return "SIGTRAP"; #ifdef SIGABRT case SIGABRT: return "SIGABRT"; #endif case SIGFPE: return "SIGFPE"; case SIGKILL: return "SIGKILL"; case SIGBUS: return "SIGBUS"; case SIGSEGV: return "SIGSEGV"; case SIGPIPE: return "SIGPIPE"; case SIGALRM: return "SIGALRM"; case SIGTERM: return "SIGTERM"; #ifdef SIGSTOP case SIGSTOP: return "SIGSTOP"; #endif #ifdef SIGCONT case SIGCONT: return "SIGCONT"; #endif #ifdef SIGUSR1 case SIGUSR1: return "SIGUSR1"; #endif #ifdef SIGUSR2 case SIGUSR2: return "SIGUSR2"; #endif #ifdef SIGEMT case SIGEMT: return "SIGEMT"; #endif #ifdef SIGSYS case SIGSYS: return "SIGSYS"; #endif case SIGCHLD: return "SIGCHLD"; #ifdef SIGPWR case SIGPWR: return "SIGPWR"; #endif #ifdef SIGWINCH case SIGWINCH: return "SIGWINCH"; #endif #ifdef SIGURG case SIGURG: return "SIGURG"; #endif #ifdef SIGIO case SIGIO: return "SIGIO"; #endif #ifdef SIGVTALRM case SIGVTALRM: return "SIGVTALRM"; #endif #ifdef SIGXCPU case SIGXCPU: return "SIGXCPU"; #endif #ifdef SIGXFSZ case SIGXFSZ: return "SIGXFSZ"; #endif #ifdef SIGDANGER case SIGDANGER: return "SIGDANGER"; #endif default: { static char buf[50]; sprintf(buf, "signal %d\n", signal); return buf; } } } /* Management of child processes, and de-zombification. */ static char * timestring (time_t when) { static char buf[255] = { 0 }; struct tm tm; localtime_r (&when, &tm); sprintf (buf, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec); return buf; } #ifdef DEBUG void show_job_list (void); void show_job_list (void) { struct screenhack_job *job; fprintf(stderr, "%s: job list:\n", blurb()); for (job = jobs; job; job = job->next) { fprintf (stderr, " %5ld: %2d: (%s) %s %s\n", (long) job->pid, job->screen, (job->status == job_running ? "running" : job->status == job_stopped ? "stopped" : job->status == job_killed ? " killed" : job->status == job_dead ? " dead" : " ???"), (job->killed ? timestring (job->killed) : job->launched ? timestring (job->launched) : "??:??:??"), job->name); } fprintf (stderr, "\n"); } #endif /* DEBUG */ static void make_job (pid_t pid, int screen, const char *cmd) { struct screenhack_job *job = (struct screenhack_job *) malloc (sizeof(*job)); static char name [1024]; const char *in = cmd; char *out = name; int got_eq = 0; clean_job_list(); AGAIN: while (*in && isspace(*in)) in++; /* skip whitespace */ while (*in && !isspace(*in) && *in != ':') { if (*in == '=') got_eq = 1; *out++ = *in++; /* snarf first token */ } if (got_eq) /* if the first token was FOO=bar */ { /* then get the next token instead. */ got_eq = 0; out = name; goto AGAIN; } while (*in && isspace(*in)) in++; /* skip whitespace */ *out = 0; job->name = strdup(name); job->pid = pid; job->screen = screen; job->status = job_running; job->launched = time ((time_t *) 0); job->killed = 0; job->next = jobs; jobs = job; } static void free_job (struct screenhack_job *job) { if (!job) return; else if (job == jobs) jobs = jobs->next; else { struct screenhack_job *job2, *prev; for (prev = 0, job2 = jobs; job2; prev = job2, job2 = job2->next) if (job2 == job) { prev->next = job->next; break; } } free(job->name); free(job); } /* Cleans out dead jobs from the jobs list. */ static void clean_job_list (void) { struct screenhack_job *job, *prev, *next; time_t now = time ((time_t *) 0); static time_t last_warn = 0; Bool warnedp = False; for (prev = 0, job = jobs, next = (job ? job->next : 0); job; prev = job, job = next, next = (job ? job->next : 0)) { if (job->status == job_dead) { if (prev) prev->next = next; free_job (job); job = prev; } else if (job->status == job_killed && now - job->killed > 10 && now - last_warn > 10) { fprintf (stderr, "%s: WARNING: pid %ld (%s) sent SIGTERM %ld seconds ago" " and did not die!\n", blurb(), (long) job->pid, job->name, (long) (now - job->killed)); warnedp = True; } } if (warnedp) last_warn = now; } static struct screenhack_job * find_job (pid_t pid) { struct screenhack_job *job; for (job = jobs; job; job = job->next) if (job->pid == pid) return job; return 0; } static int kill_job (saver_info *si, pid_t pid, int signal) { saver_preferences *p = &si->prefs; struct screenhack_job *job; int status = -1; clean_job_list(); job = find_job (pid); if (!job || !job->pid || job->status == job_killed) { if (p->verbose_p) fprintf (stderr, "%s: no child %ld to signal!\n", blurb(), (long) pid); goto DONE; } switch (signal) { case SIGTERM: job->status = job_killed; job->killed = time ((time_t *) 0); break; #ifdef SIGSTOP case SIGSTOP: job->status = job_stopped; break; case SIGCONT: job->status = job_running; break; #endif /* SIGSTOP */ default: abort(); } if (p->verbose_p) fprintf (stderr, "%s: %d: %s pid %lu (%s)\n", blurb(), job->screen, (job->status == job_killed ? "killing" : job->status == job_stopped ? "suspending" : "resuming"), (unsigned long) job->pid, job->name); status = kill (job->pid, signal); if (p->verbose_p && status < 0) { if (errno == ESRCH) fprintf (stderr, "%s: %d: child process %lu (%s) was already dead\n", blurb(), job->screen, (unsigned long) job->pid, job->name); else { char buf [1024]; sprintf (buf, "%s: %d: couldn't kill child process %lu (%s)", blurb(), job->screen, (unsigned long) job->pid, job->name); perror (buf); } } await_dying_children (si); DONE: clean_job_list(); return status; } /* We use Xt-style signal handling. A Unix signal fires, and we inform Xt of that. Then after we return to the top-level command loop on the main stack, Xt runs our callback function for that signal. Just like Xt timers. */ static void catch_signal (int sig, RETSIGTYPE (*handler) (int)) { # ifdef HAVE_SIGACTION struct sigaction a; a.sa_handler = handler; sigemptyset (&a.sa_mask); a.sa_flags = 0; if (sigaction (sig, &a, 0) < 0) # else /* !HAVE_SIGACTION */ if (((long) signal (sig, handler)) == -1L) # endif /* !HAVE_SIGACTION */ { char buf [255]; sprintf (buf, "%s: couldn't catch signal %s", blurb(), signal_name (sig)); perror (buf); abort(); } } /* Exiting gracefully. When xscreensaver sends a SIGTERM signal to xscreensaver-gfx, rather than exiting immediately, we want it to do two things: - Send a SIGTERM to each running screenhack. They would *probably* die of a BadWindow X error once their window was deleted, but this is cleaner and more immediate. - Fade the screens in from black. This might take several seconds. Should another signal come in while that is ongoing, we should just die immediately. */ static RETSIGTYPE saver_sigterm_handler (int sig) { /* This is the actual signal handler, running on the signal stack. After firing once, set this signal back to the default behavior. */ sigterm_received = sig; catch_signal (sig, SIG_DFL); /* The first time a signal fires, inform Xt of that so that it will run xt_signal_handler(). "XtNoticeSignal is the only Intrinsics function that can safely be called from a signal handler". */ if (xt_sigterm_id) XtNoticeSignal (xt_sigterm_id); } static void xt_sigterm_handler (XtPointer data, XtSignalId *id) { /* This runs from the Xt event loop on the main stack, some time after the signal fired. */ saver_info *si = (saver_info *) data; saver_preferences *p = &si->prefs; static Bool hit_p = False; int i; if (xt_sigterm_id) XtRemoveSignal (xt_sigterm_id); xt_sigterm_id = 0; if (hit_p) fprintf (stderr, "%s: second signal: %s: exiting\n", blurb(), signal_name (sigterm_received)); else { hit_p = True; if (p->verbose_p) fprintf (stderr, "%s: %s: unblanking\n", blurb(), signal_name (sigterm_received)); /* Kill before unblanking, to stop drawing as soon as possible. */ for (i = 0; i < si->nscreens; i++) { saver_screen_info *ssi = &si->screens[i]; if (ssi->cycle_id) { XtRemoveTimeOut (ssi->cycle_id); ssi->cycle_id = 0; ssi->cycle_at = 0; } kill_screenhack (ssi); } unblank_screen (si); if (p->verbose_p) fprintf (stderr, "%s: %s: exiting\n", blurb(), signal_name (sigterm_received)); } /* Exit with the original signal received. */ kill (getpid(), sigterm_received); abort(); } /* SIGCHLD handling. Basically the same deal as SIGTERM. */ static RETSIGTYPE sigchld_handler (int sig) { /* This is the actual signal handler, running on the signal stack. After firing once, set this signal to fire again. */ sigchld_received = sig; # ifndef HAVE_SIGACTION catch_signal (SIGCHLD, sigchld_handler); # endif if (xt_sigchld_id) XtNoticeSignal (xt_sigchld_id); } static void xt_sigchld_handler (XtPointer data, XtSignalId *id) { /* This runs from the Xt event loop on the main stack, some time after the signal fired. */ saver_info *si = (saver_info *) data; if (si->prefs.debug_p) fprintf(stderr, "%s: got SIGCHLD\n", blurb()); await_dying_children (si); /* Their first album was better */ } static void await_dying_children (saver_info *si) { while (1) { int wait_status = 0; pid_t kid; struct rusage rus; errno = 0; kid = wait4 (-1, &wait_status, WNOHANG|WUNTRACED, &rus); if (si->prefs.debug_p) { if (kid < 0 && errno) fprintf (stderr, "%s: waitpid(-1) ==> %ld (%d)\n", blurb(), (long) kid, errno); else fprintf (stderr, "%s: waitpid(-1) ==> %ld\n", blurb(), (long) kid); } /* 0 means no more children to reap. -1 means error -- except "interrupted system call" isn't a "real" error, so if we get that, we should just try again. */ if (kid == 0 || (kid < 0 && errno != EINTR)) break; describe_dead_child (si, kid, wait_status, rus); } } static void describe_dead_child (saver_info *si, pid_t kid, int wait_status, struct rusage rus) { int i; saver_preferences *p = &si->prefs; struct screenhack_job *job = find_job (kid); const char *name = job ? job->name : ""; int screen_no = job ? job->screen : 0; char msg[1024]; *msg = 0; if (WIFEXITED (wait_status)) { int exit_status = WEXITSTATUS (wait_status); /* Treat exit code as a signed 8-bit quantity. */ if (exit_status & 0x80) exit_status |= ~0xFF; sprintf (msg, _("crashed with status %d"), exit_status); if (p->verbose_p) fprintf (stderr, "%s: %d: child pid %lu (%s) exited abnormally" " with status %d\n", blurb(), screen_no, (unsigned long) kid, name, exit_status); if (job) job->status = job_dead; } else if (WIFSIGNALED (wait_status)) { const char *sig = signal_name (WTERMSIG (wait_status)); if (job && job->status == job_killed && WTERMSIG (wait_status) == SIGTERM) { /* Expected notification after we killed it. */ sprintf (msg, _("exited normally with %.100s"), sig); if (p->verbose_p) fprintf (stderr, "%s: %d: child pid %lu (%s)" " exited normally with %s\n", blurb(), screen_no, (unsigned long) kid, name, sig); } else { /* Unexpected signal. */ sprintf (msg, _("crashed with %.100s"), sig); if (p->verbose_p) fprintf (stderr, "%s: %d: child pid %lu (%s)" " unexpectedly terminated with %s\n", blurb(), screen_no, (unsigned long) kid, name, sig); } if (job) job->status = job_dead; } else if (WIFSTOPPED (wait_status)) { if (p->verbose_p) fprintf (stderr, "%s: child pid %lu (%s) stopped with %s\n", blurb(), (unsigned long) kid, name, signal_name (WSTOPSIG (wait_status))); if (job) job->status = job_stopped; } else { /* Didn't exit, signal or stop; is this even possible? */ sprintf (msg, _("crashed mysteriously")); fprintf (stderr, "%s: child pid %lu (%s) died in a mysterious way!", blurb(), (unsigned long) kid, name); if (job) job->status = job_dead; } # ifdef LOG_CPU_TIME if (p->verbose_p && job && job->status == job_dead) { long u = rus.ru_utime.tv_usec / 1000 + rus.ru_utime.tv_sec * 1000; long s = rus.ru_stime.tv_usec / 1000 + rus.ru_stime.tv_sec * 1000; fprintf (stderr, "%s: %d: CPU used: %.1fu, %.1fs\n", blurb(), screen_no, u / 1000.0, s / 1000.0); } # endif /* LOG_CPU_TIME */ /* Clear out the pid so that any_screenhacks_running_p() knows it's dead. */ if (!job || job->status == job_dead) { for (i = 0; i < si->nscreens; i++) { saver_screen_info *ssi = &si->screens[i]; if (kid == ssi->pid) { ssi->pid = 0; if (*msg) screenhack_obituary (ssi, name, msg); } } } } void init_sigchld (saver_info *si) { static Bool signals_initialized_p = 0; if (signals_initialized_p) return; signals_initialized_p = True; catch_signal (SIGTERM, saver_sigterm_handler); /* kill */ catch_signal (SIGINT, saver_sigterm_handler); /* shell ^C */ catch_signal (SIGQUIT, saver_sigterm_handler); /* shell ^| */ catch_signal (SIGCHLD, sigchld_handler); xt_sigchld_id = XtAppAddSignal (si->app, xt_sigchld_handler, si); xt_sigterm_id = XtAppAddSignal (si->app, xt_sigterm_handler, si); } static void hack_subproc_environment (Screen *screen, Window saver_window) { /* Store $DISPLAY into the environment, so that the $DISPLAY variable that the spawned processes inherit is correct. First, it must be on the same host and display as the value of -display passed in on our command line (which is not necessarily the same as what our $DISPLAY variable is.) Second, the screen number in the $DISPLAY passed to the subprocess should be the screen on which this particular hack is running -- not the display specification which the driver itself is using, since the driver ignores its screen number and manages all existing screens. Likewise, store a window ID in $XSCREENSAVER_WINDOW -- this is necessary in a Xinerama or RANDR world where a single X11 'Screen' spans multiple monitors, and we want to run a hack on each piece of glass, not spanning them. In that case, multiple hacks have the same $DISPLAY, screen and root window. */ Display *dpy = DisplayOfScreen (screen); const char *odpy = DisplayString (dpy); char *ndpy = (char *) malloc (strlen(odpy) + 20); char *nssw = (char *) malloc (40); char *s, *c; strcpy (ndpy, "DISPLAY="); s = ndpy + strlen(ndpy); strcpy (s, odpy); /* We have to find the last colon since it is the boundary between hostname & screen - IPv6 numeric format addresses may have many colons before that point, and DECnet addresses always have two colons */ c = strrchr(s,':'); /* skip to last colon */ if (c != NULL) s = c+1; while (isdigit(*s)) s++; /* skip over dpy number */ while (*s == '.') s++; /* skip over dot */ if (s[-1] != '.') *s++ = '.'; /* put on a dot */ sprintf(s, "%d", screen_number (screen)); /* put on screen number */ sprintf (nssw, "XSCREENSAVER_WINDOW=0x%lX", (unsigned long) saver_window); if (putenv (ndpy)) abort (); if (putenv (nssw)) abort (); /* don't free ndpy/nssw -- some implementations of putenv (BSD 4.4, glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2) do not. So we must leak it (and/or the previous setting). Yay. */ } #ifdef ABORT_TESTER /* Shoot down processes after a bit, for debugging */ static void abort_debug_timer (XtPointer closure, XtIntervalId *id) { saver_screen_info *ssi = (saver_screen_info *) closure; if (ssi->pid) { fprintf (stderr, "%s: %d: %ld: born to ill\n", blurb(), ssi->number, (unsigned long) ssi->pid); kill (ssi->pid, SIGILL); } } #endif /* ABORT_TESTER */ /* Executes the command in another process. Command may be any single command acceptable to /bin/sh. It may include wildcards, but no semicolons. If successful, the pid of the other process is returned. Otherwise, -1 is returned and an error may have been printed to stderr. */ static pid_t fork_and_exec (saver_screen_info *ssi, const char *command) { saver_info *si = ssi->global; saver_preferences *p = &si->prefs; pid_t forked; switch ((int) (forked = fork ())) { case -1: { char buf [255]; sprintf (buf, "%s: couldn't fork", blurb()); perror (buf); break; } case 0: close (ConnectionNumber (si->dpy)); /* close display fd */ if (ssi) hack_subproc_environment (ssi->screen, ssi->screensaver_window); exec_command (p->shell, command, p->nice_inferior); /* If that returned, we were unable to exec the subprocess. */ exit (1); /* exits child fork */ break; default: /* parent */ make_job (forked, (ssi ? ssi->number : 0), command); if (p->verbose_p) fprintf (stderr, "%s: %d: forked \"%s\" in pid %lu" " on window 0x%lx\n", blurb(), (ssi ? ssi->number : 0), command, (unsigned long) forked, (unsigned long) ssi->screensaver_window); break; } # ifdef ABORT_TESTER if (forked) XtAppAddTimeOut (si->app, 1000 * (5 + (5 * ssi->number)), abort_debug_timer, (XtPointer) ssi); # endif return forked; } static Bool select_visual_of_hack (saver_screen_info *ssi, screenhack *hack) { saver_info *si = ssi->global; saver_preferences *p = &si->prefs; Bool selected; if (hack->visual && *hack->visual) selected = select_visual(ssi, hack->visual); else selected = select_visual(ssi, 0); if (!selected && (p->verbose_p || si->demoing_p)) fprintf (stderr, (si->demoing_p ? "%s: warning, no \"%s\" visual for \"%s\"\n" : "%s: no \"%s\" visual; skipping \"%s\"\n"), blurb(), (hack->visual && *hack->visual ? hack->visual : "???"), hack->command); return selected; } void spawn_screenhack (saver_screen_info *ssi) { saver_info *si = ssi->global; saver_preferences *p = &si->prefs; XFlush (si->dpy); if (!monitor_powered_on_p (si->dpy)) { if (si->prefs.verbose_p) fprintf (stderr, "%s: %d: X says monitor has powered down; " "not launching a hack\n", blurb(), ssi->number); ssi->current_hack = -1; /* Hooray, this doesn't actually clear the window if it was OpenGL. */ XClearWindow (si->dpy, ssi->screensaver_window); /* Even though we aren't launching a hack, do launch the cycle timer, in case the monitor powers back up at some point without us having un-blanked. */ goto DONE; } if (p->screenhacks_count) { screenhack *hack; pid_t forked; char buf [255]; int new_hack = -1; int retry_count = 0; Bool force = False; AGAIN: if (p->screenhacks_count < 1) { /* No hacks at all */ new_hack = -1; } else if (p->screenhacks_count == 1) { /* Exactly one hack in the list */ new_hack = 0; } else if (si->selection_mode == -1) { /* Select the next hack, wrapping. */ new_hack = (ssi->current_hack + 1) % p->screenhacks_count; } else if (si->selection_mode == -2) { /* Select the previous hack, wrapping. */ if (ssi->current_hack < 0) new_hack = p->screenhacks_count - 1; else new_hack = ((ssi->current_hack + p->screenhacks_count - 1) % p->screenhacks_count); } else if (si->selection_mode > 0) { /* Select a specific hack, by number (via the ACTIVATE command.) */ new_hack = ((si->selection_mode - 1) % p->screenhacks_count); force = True; } else if (p->mode == ONE_HACK && p->selected_hack >= 0) { /* Select a specific hack, by number (via "One Saver" mode.) */ new_hack = p->selected_hack; force = True; } else if (p->mode == BLANK_ONLY || p->mode == DONT_BLANK) { new_hack = -1; } else if (p->mode == RANDOM_HACKS_SAME && ssi->number != 0) { /* Use the same hack that's running on screen 0. (Assumes this function was called on screen 0 first.) */ new_hack = si->screens[0].current_hack; } else /* (p->mode == RANDOM_HACKS) */ { /* Select a random hack (but not the one we just ran.) */ while ((new_hack = random () % p->screenhacks_count) == ssi->current_hack) ; } if (new_hack < 0) /* don't run a hack */ { ssi->current_hack = -1; goto DONE; } ssi->current_hack = new_hack; hack = p->screenhacks[ssi->current_hack]; /* If the hack is disabled, or there is no visual for this hack, then try again (move forward, or backward, or re-randomize.) Unless this hack was specified explicitly, in which case, use it regardless. */ if (force) select_visual_of_hack (ssi, hack); if (!force && (!hack->enabled_p || !on_path_p (hack->command) || !select_visual_of_hack (ssi, hack))) { if (++retry_count > (p->screenhacks_count*4)) { /* Uh, oops. Odds are, there are no suitable visuals, and we're looping. Give up. (This is totally lame, what we should do is make a list of suitable hacks at the beginning, then only loop over them.) */ if (p->verbose_p) fprintf(stderr, "%s: %d: no programs enabled, or no suitable visuals\n", blurb(), ssi->number); return; } else goto AGAIN; } forked = fork_and_exec (ssi, hack->command); switch ((int) forked) { case -1: /* fork failed */ case 0: /* child fork (can't happen) */ sprintf (buf, "%s: couldn't fork", blurb()); perror (buf); exit (1); break; default: ssi->pid = forked; break; } XChangeProperty (si->dpy, ssi->screensaver_window, XA_WM_COMMAND, XA_STRING, 8, PropModeReplace, (unsigned char *) hack->command, strlen (hack->command)); XChangeProperty (si->dpy, ssi->screensaver_window, XA_NET_WM_PID, XA_CARDINAL, 32, PropModeReplace, (unsigned char *) &ssi->pid, 1); } DONE: if (ssi->current_hack < 0) XDeleteProperty (si->dpy, ssi->screensaver_window, XA_WM_COMMAND); store_saver_status (si); /* store current hack numbers */ /* Now that the hack has launched, queue a timer to cycle it. */ if (!si->demoing_p && p->cycle) { time_t now = time ((time_t *) 0); Time how_long = p->cycle; /* If we're in "SELECT n" mode, the cycle timer going off will just restart this same hack again. There's not much point in doing this every 5 or 10 minutes, but on the other hand, leaving one hack running for days is probably not a great idea, since they tend to leak and/or crash. So, restart the thing once an hour. */ if (si->selection_mode > 0 && ssi->pid) how_long = 1000 * 60 * 60; /* If there are multiple screens, stagger the restart time of subsequent screens: they will all change every N minutes, but not at the same time. But don't let that offset be more than about 5 minutes. I originally did this by just adding an offset to the very first cycle only, but after a few days, the cycles would synchronize again! Are Xt timers implemented with Huygens pendulums?? So compare this screen's target time against the previous screen's, and offset it as needed. */ if (ssi->number > 0 && p->mode != RANDOM_HACKS_SAME) { saver_screen_info *prev = &si->screens[ssi->number-1]; time_t cycle_at = now + how_long / 1000; time_t prev_at = prev->cycle_at; Time max = 1000 * 60 * 60 * 10; Time off = (how_long > max ? max : how_long) / si->nscreens; if (cycle_at < prev_at + off / 1000) { time_t old = cycle_at; cycle_at = prev_at + off / 1000; how_long = 1000 * (cycle_at - now); if (p->verbose_p && cycle_at - old > 2) fprintf (stderr, "%s: %d: offsetting cycle time by %ld sec\n", blurb(), ssi->number, cycle_at - old); } } if (p->debug_p) fprintf (stderr, "%s: %d: starting cycle_timer (%ld)\n", blurb(), ssi->number, how_long); if (ssi->cycle_id) XtRemoveTimeOut (ssi->cycle_id); ssi->cycle_id = XtAppAddTimeOut (si->app, how_long, cycle_timer, (XtPointer) ssi); ssi->cycle_at = now + how_long / 1000; if (p->verbose_p) { time_t t = time((time_t *) 0) + how_long/1000; fprintf (stderr, "%s: %d: next cycle in %lu sec at %s\n", blurb(), ssi->number, how_long/1000, timestring(t)); } } } void kill_screenhack (saver_screen_info *ssi) { saver_info *si = ssi->global; if (ssi->pid) kill_job (si, ssi->pid, SIGTERM); ssi->pid = 0; /* Hooray, this doesn't actually clear the window if it was OpenGL. */ XClearWindow (si->dpy, ssi->screensaver_window); } Bool any_screenhacks_running_p (saver_info *si) { Bool any_running_p = False; int i; for (i = 0; i < si->nscreens; i++) { saver_screen_info *ssi = &si->screens[i]; if (ssi->pid) any_running_p = True; /* Consider it running if an error dialog is posted, so that we don't prematurely clear the window. */ if (ssi->error_dialog) any_running_p = True; } return any_running_p; } /* Fork "xscreensaver-gl-visual" and wait for it to print the IDs of the GL visual that should be used on this screen. */ Visual * get_best_gl_visual (saver_info *si, Screen *screen) { saver_preferences *p = &si->prefs; pid_t forked; int fds [2]; int in, out; int errfds[2]; int errin = -1, errout = -1; char buf[1024]; char *av[10]; int ac = 0; av[ac++] = "xscreensaver-gl-visual"; av[ac] = 0; if (pipe (fds)) { perror ("error creating pipe:"); return 0; } in = fds [0]; out = fds [1]; if (!si->prefs.verbose_p) { if (pipe (errfds)) { perror ("error creating pipe:"); return 0; } errin = errfds [0]; errout = errfds [1]; } switch ((int) (forked = fork ())) { case -1: { sprintf (buf, "%s: couldn't fork", blurb()); perror (buf); exit (1); } case 0: { close (in); /* don't need this one */ close (ConnectionNumber (si->dpy)); /* close display fd */ if (dup2 (out, STDOUT_FILENO) < 0) /* pipe stdout */ { perror ("could not dup() a new stdout:"); return 0; } if (! si->prefs.verbose_p) { close(errin); if (dup2 (errout, STDERR_FILENO) < 0) { perror ("could not dup() a new stderr:"); return 0; } } hack_subproc_environment (screen, 0); /* set $DISPLAY */ execvp (av[0], av); /* shouldn't return. */ if (errno != ENOENT /* || si->prefs.verbose_p */ ) { /* Ignore "no such file or directory" errors. Issue all other exec errors, though. */ sprintf (buf, "%s: running %s", blurb(), av[0]); perror (buf); } exit (1); /* exits fork */ break; } default: { int result = 0; int wait_status = 0; pid_t pid = -1; FILE *f; unsigned long v = 0; char c; make_job (forked, 0, av[0]); /* Bookkeeping for SIGCHLD */ if (p->verbose_p) fprintf (stderr, "%s: %d: forked \"%s\" in pid %lu\n", blurb(), 0, av[0], (unsigned long) forked); f = fdopen (in, "r"); close (out); /* don't need this one */ *buf = 0; if (! fgets (buf, sizeof(buf)-1, f)) *buf = 0; fclose (f); if (! si->prefs.verbose_p) { close (errout); close (errin); } /* Wait for the child to die - wait for this pid only, not others. */ pid = waitpid (forked, &wait_status, 0); if (si->prefs.debug_p) fprintf (stderr, "%s: waitpid(%ld) => %ld\n", blurb(), (long) forked, (long) pid); if (1 == sscanf (buf, "0x%lx %c", &v, &c)) result = (int) v; if (result == 0) { if (si->prefs.verbose_p) { int L = strlen(buf); fprintf (stderr, "%s: %s did not report a GL visual!\n", blurb(), av[0]); if (L && buf[L-1] == '\n') buf[--L] = 0; if (*buf) fprintf (stderr, "%s: %s said: \"%s\"\n", blurb(), av[0], buf); } return 0; } else { Visual *v = id_to_visual (screen, result); if (si->prefs.verbose_p) fprintf (stderr, "%s: %d: %s: GL visual is 0x%X%s\n", blurb(), screen_number (screen), av[0], result, (v == DefaultVisualOfScreen (screen) ? " (default)" : "")); return v; } } } abort(); }