X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/utils/blobdiff_plain/c52f9fb9d3ac94049c3324f599517d640ee8de7c..e1c1021ff50af10aa3a2f4d50d566b3b072f023d:/xcopy/xcopy.c diff --git a/xcopy/xcopy.c b/xcopy/xcopy.c index bdd0a13..f1678f1 100644 --- a/xcopy/xcopy.c +++ b/xcopy/xcopy.c @@ -3,7 +3,6 @@ * selection */ -#include #include #include #include @@ -29,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; @@ -42,11 +44,16 @@ int convert_to_ctext = True; /* Xmb convert to compound text? */ 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" " also: xcopy --version report version number\n" " xcopy --help display this help text\n" " xcopy --licence display the (MIT) licence text\n" @@ -110,7 +117,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")) { @@ -125,6 +132,20 @@ 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, "--help")) { usage(); return 0; @@ -142,6 +163,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"); @@ -165,7 +195,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. @@ -214,10 +244,14 @@ 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; /* * Returns TRUE if we need to enter an event loop, FALSE otherwise. @@ -237,18 +271,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); @@ -272,13 +321,14 @@ 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 (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, + XConvertSelection(disp, sel_atom, strtype, sel_property, ourwin, CurrentTime); return True; } @@ -289,8 +339,8 @@ 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) + XSetSelectionOwner (disp, sel_atom, ourwin, CurrentTime); + if (XGetSelectionOwner (disp, sel_atom) != ourwin) error ("unable to obtain primary X selection\n"); compound_text_atom = XInternAtom(disp, "COMPOUND_TEXT", False); if (strtype == XA_STRING) { @@ -323,6 +373,95 @@ 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; + + /* + * 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) { + 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 { + return convert_sel_inner(requestor, target, property); + } +} + void run_X(void) { XEvent ev, e2; @@ -347,37 +486,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); } } } @@ -403,29 +517,58 @@ 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 (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\n", nread); + } + + if (expected_type != (Atom)None && actual_type != expected_type) { + char *expout = XGetAtomName(disp, expected_type); + char *gotout = (actual_type == (Atom)None ? "None" : + XGetAtomName(disp, actual_type)); + error("unexpected data type: expected %s, got %s\n", + expout, gotout); + } + if (expected_format && expected_format != actual_format) { + error("unexpected data format: expected %d-bit, got %d-bit\n", + 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", XGetAtomName(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; } }