/* xscreensaver-command, Copyright © 1991-2021 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
* 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 <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif /* HAVE_SYS_SELECT_H */
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <X11/Xproto.h> /* for CARD32 */
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h> /* for XGetClassHint() */
#include <X11/Xos.h>
#ifdef HAVE_DPMS_EXTENSION
# include <X11/extensions/dpms.h>
#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);
}
}