xatom: Overhaul xwait/xtell into a single program with subcommands.
authorMark Wooding <mdw@distorted.org.uk>
Sun, 23 Mar 2008 16:06:36 +0000 (16:06 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Sun, 23 Mar 2008 16:06:36 +0000 (16:06 +0000)
We can also turn xshutdown into a simple shell script at the same time.

Makefile.am
debian/control
debian/xtoys.install
libxatom.c [new file with mode: 0644]
libxatom.h [new file with mode: 0644]
xatom.1 [new file with mode: 0644]
xatom.c [new file with mode: 0644]

index 01885b1..14d29ed 100644 (file)
@@ -44,6 +44,17 @@ LDADD                         = -lX11 $(X_LIBS)
 ## xscsize
 bin_PROGRAMS           += xscsize
 dist_man_MANS          += xscsize.1
+xscsize_SOURCES                 =
+
+xscsize_SOURCES                += xscsize.c
+
+## xatom
+bin_PROGRAMS           += xatom
+dist_man_MANS          += xatom.1
+xatom_SOURCES           =
+
+xatom_SOURCES          += xatom.c
+xatom_SOURCES          += libxatom.h libxatom.c
 
 ###--------------------------------------------------------------------------
 ### Debian.
index 5a1c73f..db4fd1b 100644 (file)
@@ -11,3 +11,4 @@ Depends: ${shlibs:Depends}
 Description: A collection of small X11 tools
  We have:
  xscsize -- reports the display size as shell variables
+ xatom -- inspect properties on windows, and wait for changes
index 8c49cf3..595c141 100644 (file)
@@ -1,2 +1,4 @@
+debian/tmp/usr/bin/xatom
+debian/tmp/usr/share/man/man1/xatom.1
 debian/tmp/usr/bin/xscsize
 debian/tmp/usr/share/man/man1/xscsize.1
diff --git a/libxatom.c b/libxatom.c
new file mode 100644 (file)
index 0000000..3bca18f
--- /dev/null
@@ -0,0 +1,152 @@
+/* -*-c-*-
+ *
+ * Messing with X atom properties
+ *
+ * (c) 2007 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the Edgeware X tools collection.
+ *
+ * X tools is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * X tools is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with X tools; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stddef.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include "libxatom.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @xatom_set@ --- *
+ *
+ * Arguments:  @Display *d@ = pointer to display
+ *             @Window w@ = window to set
+ *             @Atom p@ = property to set
+ *             @Atom a@ = atom property value
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets an atom property on a particular window.
+ */
+
+void xatom_set(Display *d, Window w, Atom p, Atom a)
+{
+  XChangeProperty(d, w, p, XA_ATOM, 32, PropModeReplace,
+                 (unsigned char *)&a, 1);
+  XSync(d, False);
+}
+
+/* --- @xatom_get@ --- *
+ *
+ * Arguments:  @Display *d@ = pointer to display
+ *             @Window w@ = window to set
+ *             @Atom p@ = property to read
+ *
+ * Returns:    Atom which is the value of the property.
+ *
+ * Use:                Reads an atom property from a particular window.  The value
+ *             @None@ is returned if there is no atom value.
+ */
+
+Atom xatom_get(Display *d, Window w, Atom p)
+{
+  Atom type, v;
+  unsigned long n, left;
+  int fmt;
+  unsigned char *buf;
+
+  /* --- Fetch the property value --- */
+
+  if (XGetWindowProperty(d, w, p,      /* Display, window, property */
+                        0, 64,         /* Offset, length (both in words) */
+                        False,         /* Delete after return? */
+                        XA_ATOM,       /* Data format type */
+                        &type, &fmt,   /* Actual type and format */
+                        &n, &left,     /* Amount read, and bytes left */
+                        &buf)          /* Where to put the buffer */
+        != Success ||
+      type != XA_ATOM ||
+      n < 1 || fmt < 32)
+    return (None);
+
+  /* --- OK, get the atom and return --- *
+   *
+   * This assumes that atoms are 32-bit things.  This may not be the case.
+   * That's a right pain, actually.  It looks as if Xlib is trying to do the
+   * right thing, so I'll go with that rather than trying to do anything
+   * clever.  This is actually a bit of a poor interface.
+   */
+
+  v = *(Atom *)buf;
+  XFree(buf);
+  return (v);
+}
+
+/* --- @xatom_delete@ --- *
+ *
+ * Arguments:  @Display *d@ = pointer to display
+ *             @Window w@ = window containing atom
+ *             @Atom p@ = property to delete
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes a property from a window.
+ */
+
+void xatom_delete(Display *d, Window w, Atom p)
+{
+  XDeleteProperty(d, w, p);
+  XSync(d, False);
+}
+
+/* --- @xatom_wait@ --- *
+ *
+ * Arguments:  @Display *d@ = pointer to display
+ *             @Window w@ = window to watch
+ *             @Atom p@ = property to fetch
+ *             @const Atom *aa@ = pointer to vector of atoms
+ *             @size_t n@ = numer of atoms in vector
+ *
+ * Returns:    The matching atom.
+ *
+ * Use:                Waits for the given property on the window to match one of
+ *             the @aa[i]@.
+ */
+
+Atom xatom_wait(Display *d, Window w, Atom p, const Atom *aa, size_t n)
+{
+  Atom a;
+  size_t i;
+  XEvent event;
+
+  XSelectInput(d, w, PropertyChangeMask);
+  for (;;) {
+    a = xatom_get(d, w, p);
+    if (a != None) {
+      if (!n) return (a);
+      for (i = 0; i < n; i++)
+       if (a == aa[i]) return (a);
+    }
+    do XNextEvent(d, &event); while (event.type != PropertyNotify);
+  }
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/libxatom.h b/libxatom.h
new file mode 100644 (file)
index 0000000..200276b
--- /dev/null
@@ -0,0 +1,106 @@
+/* -*-c-*-
+ *
+ * Messing with X atom properties
+ *
+ * (c) 2007 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the Edgeware X tools collection.
+ *
+ * X tools is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * X tools is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with X tools; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef LIBXATOM_H
+#define LIBXATOM_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stddef.h>
+
+#include <X11/Xlib.h>
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @xatom_set@ --- *
+ *
+ * Arguments:  @Display *d@ = pointer to display
+ *             @Window w@ = window to set
+ *             @Atom p@ = property to set
+ *             @Atom a@ = atom property value
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets an atom property on a particular window.
+ */
+
+extern void xatom_set(Display */*d*/, Window /*w*/, Atom /*p*/, Atom /*a*/);
+
+/* --- @xatom_get@ --- *
+ *
+ * Arguments:  @Display *d@ = pointer to display
+ *             @Window w@ = window to set
+ *             @Atom p@ = property to read
+ *
+ * Returns:    Atom which is the value of the property.
+ *
+ * Use:                Reads an atom property from a particular window.  The value
+ *             @None@ is returned if there is no atom value.
+ */
+
+extern Atom xatom_get(Display */*d*/, Window /*w*/, Atom /*p*/);
+
+/* --- @xatom_delete@ --- *
+ *
+ * Arguments:  @Display *d@ = pointer to display
+ *             @Window w@ = window containing atom
+ *             @Atom p@ = property to delete
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes a property from a window.
+ */
+
+extern void xatom_delete(Display */*d*/, Window /*w*/, Atom /*p*/);
+
+/* --- @xatom_wait@ --- *
+ *
+ * Arguments:  @Display *d@ = pointer to display
+ *             @Window w@ = window to watch
+ *             @Atom p@ = property to fetch
+ *             @const Atom *aa@ = pointer to vector of atoms
+ *             @size_t n@ = numer of atoms in vector
+ *
+ * Returns:    The matching atom.
+ *
+ * Use:                Waits for the given property on the window to match one of
+ *             the @aa[i]@.
+ */
+
+extern Atom xatom_wait(Display */*d*/, Window /*w*/, Atom /*p*/,
+                      const Atom */*aa*/, size_t /*n*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/xatom.1 b/xatom.1
new file mode 100644 (file)
index 0000000..59d7ae8
--- /dev/null
+++ b/xatom.1
@@ -0,0 +1,186 @@
+.\" -*-nroff-*-
+.TH xatom 1 "13 September 2007" "Straylight/Edgeware" "xtoys"
+.SH NAME
+xatom \- manipulate properties on X windows
+.SH SYNOPSIS
+.B xatom
+.RB [ \- d
+.IR display ]
+.RB [ \- w
+.IR window ]
+.I command
+.PP
+where
+.I command
+is one of:
+.PP
+.B help
+.RI [ command ...]
+.br
+.B get
+.I property
+.br
+.B set
+.I property
+.I value
+.br
+.B delete
+.I property
+.br
+.B wait
+.I property
+.RI [ value ...]
+.SH DESCRIPTION
+The
+.B xatom
+program manipulates properties of a simple kind on X windows.  It deals
+only with those properties whose value is a single X atom.  It is
+capable of setting, reading, and deleting properties, and also waiting
+until a particular property is set, maybe to one of a number of given
+values.
+.PP
+The program provides a number of subcommands, by which the various
+operations may be carried out.
+.SS "Global options"
+Before the command name,
+.I "global options"
+may be given.  The following global options are supported.
+.TP
+.BR "\-h, \-\-help " [\fIcommand\fP...]
+Writes a brief summary of
+.BR xatom 's
+various operations to standard output, and returns a successful exit
+status.  With command names, gives help on those commands.
+.TP
+.B "\-v, \-\-version"
+Writes the program's version number to standard output, and returns a
+successful exit status.
+.TP
+.B "\-u, \-\-usage"
+Writes a very terse command line summary to standard output, and returns
+a successful exit status.
+.TP
+.BI "\-d, \-\-display=" display
+Connect to the X server named by
+.I display
+rather than the default, which is to consult the
+.B DISPLAY
+environment variable.
+.TP
+.BI "\-w, \-\-window=" window
+Manipulate properties on the window whose ID is
+.I window
+rather than the default, which is to use the root window of the screen
+named by
+.IR display .
+If
+.I window
+is the string
+.B choose
+then the window can be chosen interactively: the
+.B xatom
+program will grab the pointer and wait for a button-1 click over a
+window.  A button-3 click causes
+.B xatom
+to quit (and return an unsuccessful exit status).
+.SH "COMMAND REFERENCE"
+.SS "help"
+The
+.B help
+command behaves exactly as the
+.B \-\-help
+option.  With no arguments, it shows an overview of
+.BR xatom 's
+options; with arguments, it described the named subcommands.
+.SS "get"
+The
+.B get
+command retrieves the named
+.I property
+from the specified window, and reports its value on standard output.
+.PP
+If the property wasn't found, or its value wasn't a single atom, nothing
+is written, but a successful exit status is still returned.  If a value
+was found, it is written and followed by a newline: therefore a client
+can distinguish an empty value from no value at all.
+.SS "set"
+The
+.B set
+command sets the named
+.I property
+to have the specified
+.IR value ,
+overwriting any existing value.
+.SS "delete"
+The
+.B delete
+command removes the named
+.I property
+from the window.  If no such property exists, nothing happens.
+.SS "wait"
+The
+.B wait
+command waits on a particular
+.IR property .
+If any
+.IR value s
+are specified, then the command waits until the property's value matches
+one of the specified
+.IR value s:
+if it already matches one of them then the command won't wait.
+If no
+.IR value s
+are specified, then the command waits until the property is set to any
+value; again, if it is already set, the command returns immediately.
+.PP
+If exactly one
+.I value
+is given, the command produces no output; otherwise, it writes the new
+value of the property, as for the
+.B get
+command.
+.SH EXAMPLE
+The author uses this command at the end of his
+.BR .xinitrc
+file, to control the duration of his X session.  Specifically, he uses
+the code
+.PP
+.RS
+.ft B
+.nf
+xatom set MDW_SHUTDOWN READY
+xatom wait MDW_SHUTDOWN SHUTDOWN
+xatom delete MDW_SHUTDOWN
+.fi
+.RE
+The script
+.B xshutdown
+looks like this.
+.PP
+.RS
+.ft B
+.nf
+#! /bin/sh
+set -e
+me=$(basename $0)
+case "$(xatom get MDW_SHUTDOWN)" in
+  READY) ;;
+  *) xmsg -e -t $me "Nobody's waiting for my signal."; exit 1;;
+esac
+case "$(xmsg -q -t $me \
+  "Really shut down this session?" :_Shutdown \~gtk-cancel)" in
+  _Shutdown)
+    xatom set MDW_SHUTDOWN SHUTDOWN
+    ;;
+esac
+.fi
+.RE
+.SH BUGS
+None currently known.
+.SH SEE ALSO
+.BR xmsg (1),
+.BR XChangeProperty (3x),
+.BR XGetWindowProperty (3x),
+.BR XDeleteProperty (3x).
+.SH AUTHOR
+Mark Wooding (mdw@distorted.org.uk).
diff --git a/xatom.c b/xatom.c
new file mode 100644 (file)
index 0000000..826085c
--- /dev/null
+++ b/xatom.c
@@ -0,0 +1,360 @@
+/* -*-c-*-
+ *
+ * Set and fetch X atom properties
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the Edgeware X tools collection.
+ *
+ * X tools is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * X tools is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with X tools; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/wait.h>
+
+#include <X11/cursorfont.h>
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <mLib/mdwopt.h>
+#include <mLib/quis.h>
+#include <mLib/alloc.h>
+#include <mLib/report.h>
+
+#include "libxatom.h"
+
+/*----- Static variables --------------------------------------------------*/
+
+static Display *display = 0;
+static Window window = None;
+
+/*----- Command implementations -------------------------------------------*/
+
+static void help(char **);
+static int c_help(int argc, char **argv)
+  { help(argv + 1); return (0); }
+
+static int c_get(int argc, char **argv)
+{
+  Atom p, a;
+  char *name;
+
+  if (argc != 2)
+    die(EXIT_FAILURE, "Usage: get PROPERTY");
+  if ((p = XInternAtom(display, argv[1], True)) == None ||
+      (a = xatom_get(display, window, p)) == None)
+    return (0);
+  name = XGetAtomName(display, a);
+  puts(name);
+  return (0);
+}
+
+static int c_set(int argc, char **argv)
+{
+  Atom p, a;
+
+  if (argc != 3)
+    die(EXIT_FAILURE, "Usage: set PROPERTY VALUE");
+  p = XInternAtom(display, argv[1], False);
+  a = XInternAtom(display, argv[2], False);
+  xatom_set(display, window, p, a);
+  return (0);
+}
+
+static int c_delete(int argc, char **argv)
+{
+  Atom p;
+
+  if (argc != 2)
+    die(EXIT_FAILURE, "Usage: delete PROPERTY");
+  if ((p = XInternAtom(display, argv[1], True)) == None)
+    return (0);
+  xatom_delete(display, window, p);
+  return (0);
+}
+
+static int c_wait(int argc, char **argv)
+{
+  Atom p, a, *aa = 0;
+  int n;
+  char *name;
+
+  if (argc < 2)
+    die(EXIT_FAILURE, "Usage: wait PROPERTY [VALUE...]");
+
+  p = XInternAtom(display, argv[1], False);
+  n = argc - 2;
+  if (n) {
+    aa = xmalloc(n * sizeof(Atom));
+    XInternAtoms(display, argv + 2, n, False, aa);
+  }
+
+  a = xatom_wait(display, window, p, aa, n);
+  if (n != 1) {
+    name = XGetAtomName(display, a);
+    puts(name);
+    XFree(name);
+  }
+  if (aa)
+    xfree(aa);
+  return (0);
+}
+
+/*----- Utilities ---------------------------------------------------------*/
+
+/* --- @choosewindow@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    An X window id.
+ *
+ * Use:                Allows the user to select a window using the mouse.
+ */
+
+static Window choosewindow(void)
+{
+  Cursor cross = XCreateFontCursor(display, XC_crosshair);
+  XEvent event;
+
+  XGrabPointer(display,
+              DefaultRootWindow(display),
+              False,
+              ButtonPressMask,
+              GrabModeAsync,
+              GrabModeAsync,
+              None,
+              cross,
+              CurrentTime);
+
+  for (;;) {
+    XNextEvent(display, &event);
+    switch (event.type) {
+      case ButtonPress:
+       switch (event.xbutton.button) {
+         case 3:
+           XUngrabPointer(display, event.xbutton.time);
+           die(EXIT_FAILURE, "aborted window selection");
+           break;
+         case 1:
+           window = event.xbutton.subwindow;
+           if (window == None)
+             window = event.xbutton.window;
+           XUngrabPointer(display, event.xbutton.time);
+           return (window);
+       }
+       break;
+    }
+  }
+}
+
+/* --- @autoreap@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Causes child processes to be reaped as reports of their
+ *             demises come in.  Their exit statuses are simply discarded.
+ *
+ *             This program needs to reap child processes even though it
+ *             didn't create them and doesn't know what to do with their
+ *             statuses because it's often used in shell scripts of the form
+ *
+ *                     ... start lots of stuff ...
+ *                     exec xatom wait GODOT ARRIVED
+ */
+
+static void reap(int sig)
+  { int e = errno; while (waitpid(-1, 0, WNOHANG) > 0) ; errno = e; }
+
+static void autoreap(void)
+{
+  struct sigaction sa;
+  sigset_t ss, oss;
+
+  sa.sa_handler = reap;
+  sigemptyset(&sa.sa_mask);
+  sigaddset(&sa.sa_mask, SIGCHLD);
+  sa.sa_flags = SA_NOCLDSTOP;
+#ifdef SA_RESTART
+  sa.sa_flags |= SA_RESTART;
+#endif
+  sigaction(SIGCHLD, &sa, 0);
+
+  sigemptyset(&ss);
+  sigaddset(&ss, SIGCHLD);
+  sigprocmask(SIG_BLOCK, &ss, &oss);
+  reap(SIGCHLD);
+  sigprocmask(SIG_SETMASK, &oss, 0);
+}
+
+/*----- Command dispatch --------------------------------------------------*/
+
+static const struct cmd {
+  const char *name;
+  int (*cmd)(int, char **);
+  const char *usage;
+  const char *help;
+} cmds[] = {
+  { "help", c_help, "help [COMMANDS...]" },
+  { "get", c_get, "get PROPERTY" },
+  { "set", c_set, "set PROPERTY VALUE" },
+  { "delete", c_delete, "delete PROPERTY" },
+  { "wait", c_wait, "wait PROPERTY [VALUE...]" },
+  { 0 }
+};
+
+const struct cmd *findcmd(const char *name)
+{
+  const struct cmd *c, *chosen = 0;
+  size_t sz = strlen(name);
+
+  for (c = cmds; c->name; c++) {
+    if (strncmp(name, c->name, sz) == 0) {
+      if (c->name[sz] == 0) {
+        chosen = c;
+        break;
+      } else if (chosen)
+        die(EXIT_FAILURE, "ambiguous command name `%s'", name);
+      else
+        chosen = c;
+    }
+  }
+  if (!chosen)
+    die(EXIT_FAILURE, "unknown command name `%s'", name);
+  return (chosen);
+}
+
+/*----- Help and version information --------------------------------------*/
+
+static void version(void)
+  { pquis(stdout, "$ version " VERSION "\n"); }
+
+static void usage(FILE *fp)
+  { pquis(fp, "Usage: $ [-d DISPLAY] SUBCOMMAND [ARGUMENTS...]\n"); }
+
+static void help(char **av)
+{
+  const struct cmd *c;
+
+  version(); putchar('\n');
+  if (!*av) {
+    usage(stdout);
+    fputs("\n\
+Sets, retrieves and waits for properties on an X window.\n\
+\n\
+Global command-line options:\n\
+\n\
+-h, --help [COMMAND]           Display this help, or help on COMMAND.\n\
+-v, --version                  Display program's version number.\n\
+-u, --usage                    Display short usage summary.\n\
+\n\
+-d, --display=DISPLAY          Connect to X DISPLAY.\n\
+-w, --window=WINDOW            Use properties on WINDOW instead of root.\n\
+\n\
+The following subcommands are understood:\n\n",
+         stdout);
+    for (c = cmds; c->name; c++)
+      printf("%s\n", c->usage);
+  } else {
+    while (*av) {
+      c = findcmd(*av++);
+      printf("Usage: %s [-OPTIONS] %s\n", QUIS, c->usage);
+      if (c->help) {
+       putchar('\n');
+       pquis(stdout, c->help);
+      }
+      if (*av) putchar('\n');
+    }
+  }
+}
+
+/*----- Main program ------------------------------------------------------*/
+
+int main(int argc, char *argv[])
+{
+  const char *dpy = 0;
+  const char *win = 0;
+
+  unsigned f = 0;
+#define F_BOGUS 1u
+
+  ego(argv[0]);
+
+  /* --- Parse arguments --- */
+
+  for (;;) {
+    static struct option opt[] = {
+      { "help",        0,                      0,      'h' },
+      { "usage",       0,                      0,      'u' },
+      { "version",     0,                      0,      'v' },
+      { "display",     OPTF_ARGREQ,            0,      'd' },
+      { "window",      OPTF_ARGREQ,            0,      'w' },
+      {        0,              0,                      0,      0 }
+    };
+
+    int i = mdwopt(argc, argv, "+huvd:w:", opt, 0, 0, 0);
+    if (i < 0) break;
+    switch (i) {
+      case 'h': help(argv + optind); exit(0);
+      case 'u': usage(stdout); exit(0);
+      case 'v': version(); exit(0);
+      case 'd': dpy = optarg; break;
+      case 'w': win = optarg; break;
+      default: f |= F_BOGUS; break;
+    }
+  }
+  if ((f & F_BOGUS) || optind >= argc) {
+    usage(stderr);
+    exit(EXIT_FAILURE);
+  }
+
+  /* --- Initialize --- */
+
+  autoreap();
+  if ((display = XOpenDisplay(dpy)) == 0)
+    die(EXIT_FAILURE, "couldn't open display");
+
+  /* --- Select a target window --- */
+
+  if (!win)
+    window = DefaultRootWindow(display);
+  else if (strcmp(win, "choose") == 0)
+    window = choosewindow();
+  else
+    window = (Window)strtoul(win, 0, 0);
+
+  /* --- Dispatch the command --- */
+
+  argc -= optind;
+  argv += optind;
+  optind = 0;
+  return (findcmd(argv[0])->cmd(argc, argv));
+
+#undef F_BOGUS
+}
+
+/*----- That's all, folks -------------------------------------------------*/