Verbose mode, for yet more selection-debugging usefulness.
[sgt/utils] / xcopy / xcopy.c
index 28df882..5847504 100644 (file)
@@ -28,7 +28,10 @@ void error (char *fmt, ...);
 
 /* set from command-line parameters */
 char *display = NULL;
-enum { STRING, CTEXT, UTF8 } mode = STRING;
+enum { STRING, CTEXT, UTF8, TARGETS, TIMESTAMP, CUSTOM } mode = STRING;
+int use_clipboard = False;
+char *custom_seltype = NULL;
+int fork_when_writing = True;
 
 /* selection data */
 char *seltext;
@@ -38,14 +41,21 @@ int sellen, selsize;
 /* functional parameters */
 int reading;                           /* read instead of writing? */
 int convert_to_ctext = True;          /* Xmb convert to compound text? */
+int verbose;
 
 const char usagemsg[] =
     "usage: xcopy [ -r ] [ -u | -c ] [ -C ]\n"
-    "where: -r     read X selection and print on stdout\n"
-    "       no -r  read stdin and store in X selection\n"
-    "       -u     work with UTF8_STRING type selections\n"
-    "       -c     work with COMPOUND_TEXT type selections\n"
-    "       -C     suppress automatic conversion to COMPOUND_TEXT\n"
+    "where: -r       read X selection and print on stdout\n"
+    "       no -r    read stdin and store in X selection\n"
+    "       -u       work with UTF8_STRING type selections\n"
+    "       -c       work with COMPOUND_TEXT type selections\n"
+    "       -C       suppress automatic conversion to COMPOUND_TEXT\n"
+    "       -b       read the CLIPBOARD rather than the PRIMARY selection\n"
+    "       -t       get the list of targets available to retrieve\n"
+    "       -T       get the time stamp of the selection contents\n"
+    "       -a atom  get an arbitrary form of the selection data\n"
+    "       -F       do not fork in write mode\n"
+    "       -v       proceed verbosely when reading selection\n"
     " also: xcopy --version              report version number\n"
     "       xcopy --help                 display this help text\n"
     "       xcopy --licence              display the (MIT) licence text\n"
@@ -56,7 +66,7 @@ void usage(void) {
 }
 
 const char licencemsg[] =
-    "xcopy is copyright 2001-2004 Simon Tatham.\n"
+    "xcopy is copyright 2001-2004,2008 Simon Tatham.\n"
     "\n"
     "Permission is hereby granted, free of charge, to any person\n"
     "obtaining a copy of this software and associated documentation files\n"
@@ -109,7 +119,7 @@ int main(int ac, char **av) {
     pname = *av;
 
     /* parse the command line arguments */
-    while (--ac) {
+    while (--ac > 0) {
        char *p = *++av;
 
        if (!strcmp(p, "-display") || !strcmp(p, "-disp")) {
@@ -124,6 +134,22 @@ int main(int ac, char **av) {
             mode = CTEXT;
         } else if (!strcmp(p, "-C")) {
             convert_to_ctext = False;
+        } else if (!strcmp(p, "-b")) {
+            use_clipboard = True;
+        } else if (!strcmp(p, "-t")) {
+            mode = TARGETS;
+        } else if (!strcmp(p, "-T")) {
+            mode = TIMESTAMP;
+        } else if (!strcmp(p, "-a")) {
+           if (--ac > 0)
+               custom_seltype = *++av;
+           else
+               error ("expected an argument to `-a'");
+            mode = CUSTOM;
+       } else if (!strcmp(p, "-F")) {
+           fork_when_writing = False;
+       } else if (!strcmp(p, "-v")) {
+           verbose = True;
         } else if (!strcmp(p, "--help")) {
            usage();
            return 0;
@@ -141,6 +167,15 @@ int main(int ac, char **av) {
     }
 
     if (!reading) {
+       if (mode == TARGETS || mode == TIMESTAMP || mode == CUSTOM) {
+           error ("%s not supported in writing mode; use -r",
+                  (mode == TARGETS ? "-t" :
+                   mode == TIMESTAMP ? "-T" :
+                   /* mode == CUSTOM ? */ "-a"));
+       }
+    }
+
+    if (!reading) {
         seltext = malloc(SELDELTA);
         if (!seltext)
             error ("out of memory");
@@ -164,7 +199,7 @@ int main(int ac, char **av) {
     }
 
     eventloop = init_X();
-    if (!reading) {
+    if (!reading && fork_when_writing) {
         /*
          * If we are writing the selection, we must go into the
          * background now.
@@ -213,10 +248,22 @@ char *ucasename = "XCopy";
 
 Display *disp = NULL;
 Window ourwin = None;
-Atom compound_text_atom, targets_atom;
+Atom compound_text_atom, targets_atom, timestamp_atom, atom_atom, integer_atom;
+Atom multiple_atom, atom_pair_atom;
 int screen, wwidth, wheight;
 
 Atom strtype = XA_STRING;
+Atom sel_atom = XA_PRIMARY;
+Atom expected_type = (Atom)None;
+int expected_format = 8;
+
+static const char *translate_atom(Display *disp, Atom atom)
+{
+    if (atom == None)
+       return "None";
+    else
+       return XGetAtomName(disp, atom);
+}
 
 /*
  * Returns TRUE if we need to enter an event loop, FALSE otherwise.
@@ -236,18 +283,33 @@ int init_X(void) {
     if (!disp)
        error ("unable to open display");
 
+    targets_atom = XInternAtom(disp, "TARGETS", False);
+    timestamp_atom = XInternAtom(disp, "TIMESTAMP", False);
+    atom_atom = XInternAtom(disp, "ATOM", False);
+    atom_pair_atom = XInternAtom(disp, "ATOM_PAIR", False);
+    multiple_atom = XInternAtom(disp, "MULTIPLE", False);
+    integer_atom = XInternAtom(disp, "INTEGER", False);
     if (mode == UTF8) {
        strtype = XInternAtom(disp, "UTF8_STRING", False);
-       if (!strtype)
-           error ("unable to get UTF8_STRING property");
     } else if (mode == CTEXT) {
        strtype = XInternAtom(disp, "COMPOUND_TEXT", False);
+    } else if (mode == TARGETS) {
+       strtype = targets_atom;
+       expected_type = atom_atom;
+       expected_format = 32;
+    } else if (mode == TIMESTAMP) {
+       strtype = timestamp_atom;
+       expected_type = integer_atom;
+       expected_format = 32;
+    } else if (mode == CUSTOM) {
+       strtype = XInternAtom(disp, custom_seltype, True);
        if (!strtype)
-           error ("unable to get COMPOUND_TEXT property");
+           error ("atom '%s' does not exist on the server", custom_seltype);
+       expected_format = 0;
+    }
+    if (use_clipboard) {
+        sel_atom = XInternAtom(disp, "CLIPBOARD", False);
     }
-    targets_atom = XInternAtom(disp, "TARGETS", False);
-    if (!targets_atom)
-        error ("unable to get TARGETS property");
 
     /* get the screen and root-window */
     screen = DefaultScreen (disp);
@@ -271,13 +333,23 @@ int init_X(void) {
         /*
          * We are reading the selection, so we must FIXME.
          */
-        if (XGetSelectionOwner(disp, XA_PRIMARY) == None) {
+        if (XGetSelectionOwner(disp, sel_atom) == None) {
             /* No primary selection, so use the cut buffer. */
-            do_paste(DefaultRootWindow(disp), XA_CUT_BUFFER0, False);
+           if (verbose)
+               fprintf(stderr, "no selection owner: trying cut buffer\n");
+            if (strtype == XA_STRING)
+               do_paste(DefaultRootWindow(disp), XA_CUT_BUFFER0, False);
             return False;
         } else {
             Atom sel_property = XInternAtom(disp, "VT_SELECTION", False);
-            XConvertSelection(disp, XA_PRIMARY, strtype,
+           if (verbose)
+               fprintf(stderr, "calling XConvertSelection: selection=%s"
+                       " target=%s property=%s requestor=%08lx\n",
+                       translate_atom(disp, sel_atom),
+                       translate_atom(disp, strtype),
+                       translate_atom(disp, sel_property),
+                       ourwin);
+            XConvertSelection(disp, sel_atom, strtype,
                               sel_property, ourwin, CurrentTime);
             return True;
         }
@@ -288,9 +360,9 @@ int init_X(void) {
          * CUT_BUFFER0, if it isn't of an exotic type (cut buffers
          * can only take ordinary string data, it turns out).
          */
-        XSetSelectionOwner (disp, XA_PRIMARY, ourwin, CurrentTime);
-        if (XGetSelectionOwner (disp, XA_PRIMARY) != ourwin)
-            error ("unable to obtain primary X selection\n");
+        XSetSelectionOwner (disp, sel_atom, ourwin, CurrentTime);
+        if (XGetSelectionOwner (disp, sel_atom) != ourwin)
+            error ("unable to obtain primary X selection");
         compound_text_atom = XInternAtom(disp, "COMPOUND_TEXT", False);
        if (strtype == XA_STRING) {
            /*
@@ -322,6 +394,102 @@ int init_X(void) {
     }
 }
 
+Atom convert_sel_inner(Window requestor, Atom target, Atom property) {
+    if (target == strtype) {
+       XChangeProperty (disp, requestor, property, strtype,
+                        8, PropModeReplace, seltext, sellen);
+       return property;
+    } else if (target == compound_text_atom && convert_to_ctext) {
+       XTextProperty tp;
+       XmbTextListToTextProperty (disp, &seltext, 1,
+                                  XCompoundTextStyle, &tp);
+       XChangeProperty (disp, requestor, property, target,
+                        tp.format, PropModeReplace,
+                        tp.value, tp.nitems);
+       return property;
+    } else if (target == targets_atom) {
+       Atom targets[16];
+       int len = 0;
+       targets[len++] = timestamp_atom;
+       targets[len++] = targets_atom;
+       targets[len++] = multiple_atom;
+       targets[len++] = strtype;
+       if (strtype != compound_text_atom && convert_to_ctext)
+           targets[len++] = compound_text_atom;
+       XChangeProperty (disp, requestor, property,
+                        atom_atom, 32, PropModeReplace,
+                        (unsigned char *)targets, len);
+       return property;
+    } else if (target == timestamp_atom) {
+       Time rettime = CurrentTime;
+       XChangeProperty (disp, requestor, property,
+                        integer_atom, 32, PropModeReplace,
+                        (unsigned char *)&rettime, 1);
+       return property;
+    } else {
+       return None;
+    }
+}
+
+Atom convert_sel_outer(Window requestor, Atom target, Atom property) {
+    if (target == multiple_atom) {
+       /*
+        * Support for the MULTIPLE selection type, since it's
+        * specified as required in the ICCCM. Completely
+        * untested, though, because I have no idea of what X
+        * application might implement it for me to test against.
+        */
+
+       int size = SELDELTA;
+       Atom actual_type;
+       int actual_format, i;
+       long nitems, bytes_after, nread;
+       unsigned char *data;
+       Atom *adata;
+
+        if (property == (Atom)None)
+            return None;               /* ICCCM says this isn't allowed */
+
+       /*
+        * Fetch the requestor's window property giving a list of
+        * selection requests.
+        */
+       while (XGetWindowProperty(disp, requestor, property, 0, size,
+                                 False, AnyPropertyType, &actual_type,
+                                 &actual_format, &nitems, &bytes_after,
+                                 (unsigned char **) &data) == Success &&
+              nitems * (actual_format / 8) == size) {
+           XFree(data);
+           size *= 3 / 2;
+       }
+
+       if (actual_type != atom_pair_atom || actual_format != 32) {
+           XFree(data);
+           return None;
+       }
+
+       adata = (Atom *)data;
+
+       for (i = 0; i+1 < nitems; i += 2) {
+            if (adata[i+1] != (Atom)None)   /* ICCCM says this isn't allowed */
+                adata[i+1] = convert_sel_inner(requestor, adata[i],
+                                               adata[i+1]);
+       }
+
+       XChangeProperty (disp, requestor, property,
+                        atom_pair_atom, 32, PropModeReplace,
+                        data, nitems);
+
+       XFree(data);
+
+       return property;
+    } else {
+        if (property == (Atom)None)
+            property = target;      /* ICCCM says this is a sensible default */
+       return convert_sel_inner(requestor, target, property);
+    }
+}
+
 void run_X(void) {
     XEvent ev, e2;
 
@@ -330,6 +498,14 @@ void run_X(void) {
         if (reading) {
             switch (ev.type) {
               case SelectionNotify:
+               if (verbose)
+                   fprintf(stderr, "got SelectionNotify: requestor=%08lx "
+                           "selection=%s target=%s property=%s\n",
+                           ev.xselection.requestor,
+                           translate_atom(disp, ev.xselection.selection),
+                           translate_atom(disp, ev.xselection.target),
+                           translate_atom(disp, ev.xselection.property));
+
                 if (ev.xselection.property != None)
                     do_paste(ev.xselection.requestor,
                              ev.xselection.property, True);
@@ -346,37 +522,12 @@ void run_X(void) {
                 e2.xselection.selection = ev.xselectionrequest.selection;
                 e2.xselection.target = ev.xselectionrequest.target;
                 e2.xselection.time = ev.xselectionrequest.time;
-                if (ev.xselectionrequest.target == strtype) {
-                    XChangeProperty (disp, ev.xselectionrequest.requestor,
-                                     ev.xselectionrequest.property, strtype,
-                                     8, PropModeReplace, seltext, sellen);
-                    e2.xselection.property = ev.xselectionrequest.property;
-                } else if (ev.xselectionrequest.target == compound_text_atom &&
-                          convert_to_ctext) {
-                    XTextProperty tp;
-                    XmbTextListToTextProperty (disp, &seltext, 1,
-                                               XCompoundTextStyle, &tp);
-                    XChangeProperty (disp, ev.xselectionrequest.requestor,
-                                     ev.xselectionrequest.property,
-                                     ev.xselectionrequest.target,
-                                     tp.format, PropModeReplace,
-                                     tp.value, tp.nitems);
-                    e2.xselection.property = ev.xselectionrequest.property;
-                } else if (ev.xselectionrequest.target == targets_atom) {
-                    Atom targets[2];
-                    int len = 0;
-                    targets[len++] = strtype;
-                    if (strtype != compound_text_atom && convert_to_ctext)
-                        targets[len++] = compound_text_atom;
-                    XChangeProperty (disp, ev.xselectionrequest.requestor,
-                                     ev.xselectionrequest.property,
-                                     ev.xselectionrequest.target,
-                                     32, PropModeReplace,
-                                     (unsigned char *)targets, len);
-                } else {
-                    e2.xselection.property = None;
-                }
-                XSendEvent (disp, ev.xselectionrequest.requestor, False, 0, &e2);
+                e2.xselection.property =
+                   convert_sel_outer(ev.xselectionrequest.requestor,
+                                     ev.xselectionrequest.target,
+                                     ev.xselectionrequest.property);
+                XSendEvent (disp, ev.xselectionrequest.requestor,
+                           False, 0, &e2);
             }
         }
     }
@@ -402,29 +553,62 @@ void do_paste(Window window, Atom property, int Delete) {
                               Delete, AnyPropertyType, &actual_type,
                               &actual_format, &nitems, &bytes_after,
                               (unsigned char **) &data) == Success) {
-       /*
-        * We expect all returned chunks of data to be multiples of
-        * 4 bytes (because we can only request the subsequent
-        * starting offset in 4-byte increments). Of course you can
-        * store an odd number of bytes in a selection, so this
-        * can't be the case every time XGetWindowProperty returns;
-        * but it should be the case every time it returns _and
-        * there is more data to come_.
-        * 
-        * Hence, whenever XGetWindowProperty returns, we verify
-        * that the size of the data returned _last_ time was
-        * divisible by 4.
-        */
-       if (nitems > 0)
-           assert((nread & 3) == 0);
+       if (verbose)
+           fprintf(stderr, "got %ld items of %d-byte data, type=%s;"
+                   " %ld to go\n", nitems, actual_format,
+                   translate_atom(disp, actual_type), bytes_after);
+
+       if (nitems > 0) {
+           /*
+            * We expect all returned chunks of data to be
+            * multiples of 4 bytes (because we can only request
+            * the subsequent starting offset in 4-byte
+            * increments). Of course you can store an odd number
+            * of bytes in a selection, so this can't be the case
+            * every time XGetWindowProperty returns; but it
+            * should be the case every time it returns _and there
+            * is more data to come_.
+            *
+            * Hence, whenever XGetWindowProperty returns, we
+            * verify that the size of the data returned _last_
+            * time was divisible by 4.
+            */
+           if ((nread & 3) != 0) {
+               error("unexpected data size: %d read (not a multiple"
+                     " of 4), but more to come", nread);
+           }
+
+           if (expected_type != (Atom)None && actual_type != expected_type) {
+               const char *expout = translate_atom(disp, expected_type);
+               const char *gotout = translate_atom(disp, actual_type);
+               error("unexpected data type: expected %s, got %s",
+                     expout, gotout);
+           }
+           if (expected_format && expected_format != actual_format) {
+               error("unexpected data format: expected %d-bit, got %d-bit",
+                     expected_format, actual_format);
+           }
+       }
 
-        if (actual_type == strtype && nitems > 0) {
-            assert(actual_format == 8);
-            fwrite(data, 1, nitems, stdout);
-            nread += nitems;
+        if (nitems > 0) {
+           if (mode == TARGETS) {
+               assert(actual_format == 32);
+               int i;
+               for (i = 0; i < nitems; i++) {
+                   Atom x = ((Atom *)data)[i];
+                   printf("%s\n", translate_atom(disp, x));
+               }
+           } else if (mode == TIMESTAMP) {
+               assert(actual_format == 32);
+               Time x = ((Time *)data)[0];
+               printf("%lu\n", (unsigned long)x);
+           } else {
+               fwrite(data, actual_format / 8, nitems, stdout);
+               nread += nitems * actual_format / 8;
+           }
         }
         XFree(data);
-        if (actual_type != strtype || nitems == 0)
+        if (nitems == 0)
             break;
     }
 }