/* xscreensaver-command, 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 #include #ifdef HAVE_SYS_SELECT_H # include #endif /* HAVE_SYS_SELECT_H */ #ifdef HAVE_UNISTD_H # include #endif #include /* for CARD32 */ #include #include #include /* for XGetClassHint() */ #include #ifdef HAVE_DPMS_EXTENSION # include #endif #include "blurb.h" #include "atoms.h" #include "remote.h" #include "clientmsg.h" #ifdef _VROOT_H_ ERROR! you must not include vroot.h in this file #endif static Bool error_handler_hit_p = False; static int ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error) { error_handler_hit_p = True; return 0; } /* See comment in xscreensaver.c for why this is here instead of there. */ static void reset_dpms_timer (Display *dpy) { # ifdef HAVE_DPMS_EXTENSION XErrorHandler old_handler; int event_number, error_number; BOOL enabled = False; CARD16 power = 0; XSync (dpy, False); error_handler_hit_p = False; old_handler = XSetErrorHandler (ignore_all_errors_ehandler); if (! DPMSQueryExtension (dpy, &event_number, &error_number)) goto DONE; if (! DPMSCapable (dpy)) goto DONE; if (! DPMSInfo (dpy, &power, &enabled)) goto DONE; if (!enabled) goto DONE; /* Do this even if power == DPMSModeOn to reset the timer */ DPMSForceLevel (dpy, DPMSModeOn); DONE: XSync (dpy, False); XSetErrorHandler (old_handler); # endif /* HAVE_DPMS_EXTENSION */ } static int send_xscreensaver_command (Display *dpy, Atom command, long arg, Window *window_ret, char **error_ret) { int status = -1; char *v = 0; Window window = find_screensaver_window (dpy, &v); XWindowAttributes xgwa; char err[2048]; if (window_ret) *window_ret = window; if (!window) { sprintf (err, "no screensaver is running on display %s", DisplayString (dpy)); if (error_ret) { *error_ret = strdup (err); status = -1; goto DONE; } if (command == XA_EXIT) { /* Don't print an error if xscreensaver is already dead. */ status = 1; goto DONE; } fprintf (stderr, "%s: %s\n", progname, err); status = -1; goto DONE; } /* Select for property change events, so that we can read the response. */ XGetWindowAttributes (dpy, window, &xgwa); XSelectInput (dpy, window, xgwa.your_event_mask | PropertyChangeMask); if (command == XA_SCREENSAVER_STATUS || command == XA_SCREENSAVER_VERSION) { XClassHint hint; memset (&hint, 0, sizeof(hint)); if (!v || !*v) { sprintf (err, "version property not set on window 0x%x?", (unsigned int) window); if (error_ret) *error_ret = strdup (err); else fprintf (stderr, "%s: %s\n", progname, err); status = -1; goto DONE; } XGetClassHint(dpy, window, &hint); if (!hint.res_class) { sprintf (err, "class hints not set on window 0x%x?", (unsigned int) window); if (error_ret) *error_ret = strdup (err); else fprintf (stderr, "%s: %s\n", progname, err); status = -1; goto DONE; } fprintf (stdout, "%s %s", hint.res_class, v); if (command != XA_SCREENSAVER_STATUS) { fprintf (stdout, "\n"); } else { Atom type; int format; unsigned long nitems, bytesafter; unsigned char *dataP = 0; if (XGetWindowProperty (dpy, RootWindow (dpy, 0), XA_SCREENSAVER_STATUS, 0, 999, False, XA_INTEGER, &type, &format, &nitems, &bytesafter, &dataP) == Success && type && dataP) { Atom blanked; time_t tt; char *s; Atom *data = (Atom *) dataP; if (type != XA_INTEGER || nitems < 3) { STATUS_LOSE: if (data) free (data); fprintf (stdout, "\n"); fflush (stdout); fprintf (stderr, "bad status format on root window\n"); status = -1; goto DONE; } blanked = (Atom) data[0]; tt = (time_t) data[1]; if (tt <= (time_t) 666000000L) /* early 1991 */ goto STATUS_LOSE; if (blanked == XA_BLANK) fputs (": screen blanked since ", stdout); else if (blanked == XA_LOCK) fputs (": screen locked since ", stdout); else if (blanked == 0) /* suggestions for a better way to phrase this are welcome. */ fputs (": screen non-blanked since ", stdout); else /* `blanked' has an unknown value - fail. */ goto STATUS_LOSE; s = ctime(&tt); if (s[strlen(s)-1] == '\n') s[strlen(s)-1] = 0; fputs (s, stdout); { int nhacks = nitems - 2; Bool any = False; int i; for (i = 0; i < nhacks; i++) if (data[i + 2] > 0) { any = True; break; } if (any && nhacks == 1) fprintf (stdout, " (hack #%d)\n", (int) data[2]); else if (any) { fprintf (stdout, " (hacks: "); for (i = 0; i < nhacks; i++) { fprintf (stdout, "#%d", (int) data[2 + i]); if (i != nhacks-1) fputs (", ", stdout); } fputs (")\n", stdout); } else fputs ("\n", stdout); } if (data) free (data); } else { if (dataP) XFree (dataP); fprintf (stdout, "\n"); fflush (stdout); fprintf (stderr, "no saver status on root window\n"); status = -1; goto DONE; } } /* No need to read a response for these commands. */ status = 1; goto DONE; } else { XEvent event; long arg1 = arg; long arg2 = 0; if (arg < 0) abort(); else if (arg == 0 && command == XA_SELECT) abort(); else if (arg != 0 && command == XA_DEMO) { arg1 = 5000; /* version number of the XA_DEMO protocol, */ arg2 = arg; /* since it didn't use to take an argument. */ } if (command == XA_DEACTIVATE) reset_dpms_timer (dpy); event.xany.type = ClientMessage; event.xclient.display = dpy; event.xclient.window = window; event.xclient.message_type = XA_SCREENSAVER; event.xclient.format = 32; memset (&event.xclient.data, 0, sizeof(event.xclient.data)); event.xclient.data.l[0] = (long) command; event.xclient.data.l[1] = arg1; event.xclient.data.l[2] = arg2; if (! XSendEvent (dpy, window, False, PropertyChangeMask, &event)) { sprintf (err, "XSendEvent(dpy, 0x%x ...) failed\n", (unsigned int) window); if (error_ret) *error_ret = strdup (err); else fprintf (stderr, "%s: %s\n", progname, err); status = -1; goto DONE; } } status = 0; DONE: if (v) free (v); XSync (dpy, 0); return status; } static Bool xscreensaver_command_event_p (Display *dpy, XEvent *event, XPointer arg) { return (event->xany.type == PropertyNotify && event->xproperty.state == PropertyNewValue && event->xproperty.atom == XA_SCREENSAVER_RESPONSE); } static int xscreensaver_command_response (Display *dpy, Window window, Bool verbose_p, Bool exiting_p, char **error_ret) { int sleep_count = 0; char err[2048]; XEvent event; Bool got_event = False; while (!(got_event = XCheckIfEvent(dpy, &event, &xscreensaver_command_event_p, 0)) && sleep_count++ < 10) { # if defined(HAVE_SELECT) /* Wait for an event, but don't wait longer than 1 sec. Note that we might do this multiple times if an event comes in, but it wasn't the event we're waiting for. */ int fd = XConnectionNumber(dpy); fd_set rset; struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; FD_ZERO (&rset); FD_SET (fd, &rset); select (fd+1, &rset, 0, 0, &tv); # else /* !HAVE_SELECT */ sleep(1); # endif /* !HAVE_SELECT */ } if (!got_event) { sprintf (err, "no response to command."); if (error_ret) *error_ret = strdup (err); else fprintf (stderr, "%s: %s\n", progname, err); return -1; } else { Status st2; Atom type; int format; unsigned long nitems, bytesafter; unsigned char *msg = 0; XErrorHandler old_handler; XSync (dpy, False); error_handler_hit_p = False; old_handler = XSetErrorHandler (ignore_all_errors_ehandler); st2 = XGetWindowProperty (dpy, window, XA_SCREENSAVER_RESPONSE, 0, 1024, True, AnyPropertyType, &type, &format, &nitems, &bytesafter, &msg); XSync (dpy, False); XSetErrorHandler (old_handler); if (error_handler_hit_p) { if (exiting_p) return 0; sprintf (err, "xscreensaver window unexpectedly deleted."); if (error_ret) *error_ret = strdup (err); else fprintf (stderr, "%s: %s\n", progname, err); return -1; } if (st2 == Success && type != None) { if (type != XA_STRING || format != 8) { sprintf (err, "unrecognized response property."); if (error_ret) *error_ret = strdup (err); else fprintf (stderr, "%s: %s\n", progname, err); if (msg) XFree (msg); return -1; } else if (!msg || (msg[0] != '+' && msg[0] != '-')) { sprintf (err, "unrecognized response message."); if (error_ret) *error_ret = strdup (err); else fprintf (stderr, "%s: %s\n", progname, err); if (msg) XFree (msg); return -1; } else { int ret = (msg[0] == '+' ? 0 : -1); sprintf (err, "%s: %s\n", progname, (char *) msg+1); if (error_ret) *error_ret = strdup (err); else if (verbose_p || ret != 0) fprintf ((ret < 0 ? stderr : stdout), "%s\n", err); XFree (msg); return ret; } } } return -1; /* warning suppression: not actually reached */ } /* Wait until xscreensaver says the screen is blanked. Catches errors, times out after a few seconds. */ static int xscreensaver_command_wait_for_blank (Display *dpy, Bool verbose_p, char **error_ret) { Window w = RootWindow (dpy, 0); /* always screen 0 */ time_t start = time((time_t*)0); int max = 10; char err[2048]; while (1) { Atom type; int format; unsigned long nitems, bytesafter; unsigned char *dataP = 0; time_t now; struct timeval tv; /* Wait until the status property on the root window changes to BLANK or LOCKED. */ if (XGetWindowProperty (dpy, w, XA_SCREENSAVER_STATUS, 0, 999, False, XA_INTEGER, &type, &format, &nitems, &bytesafter, &dataP) == Success && type == XA_INTEGER && nitems >= 3 && dataP) { Atom state = ((Atom *) dataP)[0]; if (verbose_p > 1) { PROP32 *status = (PROP32 *) dataP; int i; fprintf (stderr, "%s: read status property: 0x%lx: %s", progname, (unsigned long) w, (status[0] == XA_LOCK ? "LOCK" : status[0] == XA_BLANK ? "BLANK" : status[0] == 0 ? "0" : "???")); for (i = 1; i < nitems; i++) fprintf (stderr, ", %lu", status[i]); fprintf (stderr, "\n"); } if (state == XA_BLANK || state == XA_LOCK) { if (verbose_p > 1) fprintf (stderr, "%s: screen blanked\n", progname); break; } } now = time ((time_t *) 0); if (now >= start + max) { strcpy (err, "Timed out waiting for screen to blank"); if (error_ret) *error_ret = strdup (err); else fprintf (stderr, "%s: %s\n", progname, err); return -1; } else if (verbose_p == 1 && now > start + 3) { fprintf (stderr, "%s: waiting for status change\n", progname); verbose_p++; } tv.tv_sec = 0; tv.tv_usec = 1000000L / 10; select (0, 0, 0, 0, &tv); } return 0; } int xscreensaver_command (Display *dpy, Atom command, long arg, Bool verbose_p, char **error_ret) { Window w = 0; int status = send_xscreensaver_command (dpy, command, arg, &w, error_ret); if (status == 0) status = xscreensaver_command_response (dpy, w, verbose_p, (command == XA_EXIT), error_ret); /* If this command should result in the screen being blank, wait until the xscreensaver window is mapped before returning. */ if (status == 0 && (command == XA_ACTIVATE || command == XA_SUSPEND || command == XA_LOCK || command == XA_NEXT || command == XA_PREV || command == XA_SELECT)) status = xscreensaver_command_wait_for_blank (dpy, verbose_p, error_ret); fflush (stdout); fflush (stderr); return (status < 0 ? status : 0); } void server_xscreensaver_version (Display *dpy, char **version_ret, char **user_ret, char **host_ret) { Window window = find_screensaver_window (dpy, 0); Atom type; int format; unsigned long nitems, bytesafter; if (version_ret) *version_ret = 0; if (user_ret) *user_ret = 0; if (host_ret) *host_ret = 0; if (!window) return; if (version_ret) { unsigned char *v = 0; XGetWindowProperty (dpy, window, XA_SCREENSAVER_VERSION, 0, 100, False, XA_STRING, &type, &format, &nitems, &bytesafter, &v); if (v) { *version_ret = strdup ((char *) v); XFree (v); } } if (user_ret || host_ret) { unsigned char *id = 0; const char *user = 0; const char *host = 0; XGetWindowProperty (dpy, window, XA_SCREENSAVER_ID, 0, 512, False, XA_STRING, &type, &format, &nitems, &bytesafter, &id); if (id && *id) { const char *old_tag = " on host "; const char *s = strstr ((char *) id, old_tag); if (s) { /* found ID of the form "1234 on host xyz". */ user = 0; host = s + strlen (old_tag); } else { char *o = 0, *p = 0, *c = 0; o = strchr ((char *) id, '('); if (o) p = strrchr (o, '@'); if (p) c = strchr (p, ')'); if (c) { /* found ID of the form "1234 (user@host)" or the weirder "1234 (user@crap@host)". */ user = o+1; host = p+1; *p = 0; *c = 0; } } } if (!user_ret) ; else if (user && *user && *user != '?') *user_ret = strdup (user); else *user_ret = 0; if (!host_ret) ; else if (host && *host && *host != '?') *host_ret = strdup (host); else *host_ret = 0; if (id) XFree (id); } }