X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/utils/blobdiff_plain/c52f9fb9d3ac94049c3324f599517d640ee8de7c..HEAD:/xcopy/xcopy.c diff --git a/xcopy/xcopy.c b/xcopy/xcopy.c index bdd0a13..bc5e329 100644 --- a/xcopy/xcopy.c +++ b/xcopy/xcopy.c @@ -3,7 +3,6 @@ * selection */ -#include #include #include #include @@ -11,8 +10,12 @@ #include #include #include +#include + +#include #include +#include #include #include #include @@ -29,24 +32,50 @@ 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; +int use_cutbuffers = True; +int use_outgoing_incr = True; +int sel_delta = 16384; /* selection data */ char *seltext; int sellen, selsize; -#define SELDELTA 16384 + +/* incremental transfers still pending when we return to the event loop */ +struct incr { + Window window; + Atom property; + Atom type; + int format; + unsigned char *data; + size_t size; +} *incrs = NULL; +int nincrs = 0, incrsize = 0; /* 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" + "modes: -r read X selection and print on stdout\n" + " no -r write to X selection from stdin\n" + " read: -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" + " -v verbosely report progress in reading selection\n" + "write: -F remain in foreground until selection reclaimed\n" + " -I do not attempt incremental transfers (INCR)\n" + " both: -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 use the CLIPBOARD rather than the PRIMARY selection\n" + " -d size transfer data in chunks of at most (default 16384)\n" + " -B do not use root window cut buffers\n" " also: xcopy --version report version number\n" " xcopy --help display this help text\n" " xcopy --licence display the (MIT) licence text\n" @@ -57,7 +86,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" @@ -110,7 +139,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 +154,31 @@ 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, "-I")) { + use_outgoing_incr = False; + } else if (!strcmp(p, "-B")) { + use_cutbuffers = False; + } else if (!strcmp(p, "-a")) { + if (--ac > 0) + custom_seltype = *++av; + else + error ("expected an argument to `-a'"); + mode = CUSTOM; + } else if (!strcmp(p, "-d")) { + if (--ac > 0) + sel_delta = atoi(*++av); + else + error ("expected an argument to `-d'"); + } else if (!strcmp(p, "-F")) { + fork_when_writing = False; + } else if (!strcmp(p, "-v")) { + verbose = True; } else if (!strcmp(p, "--help")) { usage(); return 0; @@ -142,22 +196,31 @@ int main(int ac, char **av) { } if (!reading) { - seltext = malloc(SELDELTA); + 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(sel_delta); if (!seltext) error ("out of memory"); - selsize = SELDELTA; + selsize = sel_delta; sellen = 0; do { n = fread(seltext+sellen, 1, selsize-sellen, stdin); sellen += n; if (sellen >= selsize) { - seltext = realloc(seltext, selsize += SELDELTA); + seltext = realloc(seltext, selsize += sel_delta); if (!seltext) error ("out of memory"); } } while (n > 0); if (sellen == selsize) { - seltext = realloc(seltext, selsize += SELDELTA); + seltext = realloc(seltext, selsize += sel_delta); if (!seltext) error ("out of memory"); } @@ -165,7 +228,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 +277,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, incr_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. @@ -237,18 +312,34 @@ 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); + incr_atom = XInternAtom(disp, "INCR", 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); @@ -270,49 +361,74 @@ int init_X(void) { /* do selection fiddling */ if (reading) { /* - * We are reading the selection, so we must FIXME. + * We are reading the selection. Call XConvertSelection to + * request transmission of the selection data in the + * appropriate format; the X event loop will then wait to + * receive the data from the selection owner. Also we need to + * make sure we receive PropertyNotify events, for INCR + * transfers. + * + * If there is no selection owner, look in the cut buffer + * property on the root window. */ - if (XGetSelectionOwner(disp, XA_PRIMARY) == None) { - /* No primary selection, so use the cut buffer. */ - do_paste(DefaultRootWindow(disp), XA_CUT_BUFFER0, False); - return False; - } else { + XSelectInput(disp, ourwin, PropertyChangeMask); + if (XGetSelectionOwner(disp, sel_atom) != None) { 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; + } else if (use_cutbuffers) { + /* No primary selection, so use the cut buffer. */ + if (verbose) + fprintf(stderr, "no selection owner: trying cut buffer\n"); + if (strtype == XA_STRING) + do_paste(DefaultRootWindow(disp), XA_CUT_BUFFER0, True); + return False; + } else { + /* Last fallback: do nothing. */ + return False; } } else { /* - * We are writing to the selection, so we establish - * ourselves as selection owner. Also place the data in - * CUT_BUFFER0, if it isn't of an exotic type (cut buffers - * can only take ordinary string data, it turns out). + * We are writing to the selection, so we establish ourselves + * as selection owner. + * + * Also place the data in CUT_BUFFER0, if it isn't of an + * exotic type (cut buffers can only take ordinary string + * data, it turns out) or bigger than our maximum chunk size. */ - 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) { + if (strtype == XA_STRING && sellen <= sel_delta && use_cutbuffers) { /* * ICCCM-required cut buffer initialisation. */ + static const unsigned char emptystring[] = {0}; XChangeProperty(disp, root, XA_CUT_BUFFER0, - XA_STRING, 8, PropModeAppend, "", 0); + XA_STRING, 8, PropModeAppend, emptystring, 0); XChangeProperty(disp, root, XA_CUT_BUFFER1, - XA_STRING, 8, PropModeAppend, "", 0); + XA_STRING, 8, PropModeAppend, emptystring, 0); XChangeProperty(disp, root, XA_CUT_BUFFER2, - XA_STRING, 8, PropModeAppend, "", 0); + XA_STRING, 8, PropModeAppend, emptystring, 0); XChangeProperty(disp, root, XA_CUT_BUFFER3, - XA_STRING, 8, PropModeAppend, "", 0); + XA_STRING, 8, PropModeAppend, emptystring, 0); XChangeProperty(disp, root, XA_CUT_BUFFER4, - XA_STRING, 8, PropModeAppend, "", 0); + XA_STRING, 8, PropModeAppend, emptystring, 0); XChangeProperty(disp, root, XA_CUT_BUFFER5, - XA_STRING, 8, PropModeAppend, "", 0); + XA_STRING, 8, PropModeAppend, emptystring, 0); XChangeProperty(disp, root, XA_CUT_BUFFER6, - XA_STRING, 8, PropModeAppend, "", 0); + XA_STRING, 8, PropModeAppend, emptystring, 0); XChangeProperty(disp, root, XA_CUT_BUFFER7, - XA_STRING, 8, PropModeAppend, "", 0); + XA_STRING, 8, PropModeAppend, emptystring, 0); /* * Rotate the cut buffers and add our text in CUT_BUFFER0. */ @@ -323,62 +439,242 @@ int init_X(void) { } } +void write_data(Window requestor, Atom property, Atom type, int format, + void *vdata, size_t size) +{ + int bformat = format / 8; /* bytes per element */ + unsigned char *data = (unsigned char *)vdata; + XEvent ev; + + if (!use_outgoing_incr || size * bformat <= sel_delta) { + XChangeProperty(disp, requestor, property, type, format, + PropModeReplace, data, size); + } else { + /* + * For large data, an incremental transfer as per ICCCM 2.7.2. + */ + Cardinal totalsize = size * bformat; + Cardinal sent, thissize; + + /* + * We're going to need PropertyNotify events on the target + * window to tell us when to send the next chunk. + */ + XSelectInput(disp, requestor, PropertyChangeMask); + + /* + * Start by sending a single 32-bit word with type INCR giving + * the total size in bytes. + */ + XChangeProperty(disp, requestor, property, incr_atom, 32, + PropModeReplace, (unsigned char *)&totalsize, 1); + + /* + * Now set up an entry in our list of ongoing incremental + * transfers, so that whenever that property is deleted, we'll + * send the next batch. + */ + if (nincrs >= incrsize) { + incrsize = nincrs * 9 / 8 + 16; + incrs = realloc(incrs, incrsize * sizeof(*incrs)); + if (!incrs) + error ("out of memory"); + } + incrs[nincrs].window = requestor; + incrs[nincrs].property = property; + incrs[nincrs].type = type; + incrs[nincrs].format = format; + incrs[nincrs].size = totalsize; + incrs[nincrs].data = malloc(totalsize); + if (!incrs[nincrs].data) + error("out of memory"); + memcpy(incrs[nincrs].data, data, size); + nincrs++; + } +} + +Atom convert_sel_inner(Window requestor, Atom target, Atom property) { + if (target == strtype) { + write_data(requestor, property, strtype, 8, seltext, sellen); + return property; + } else if (target == compound_text_atom && convert_to_ctext) { + XTextProperty tp; + XmbTextListToTextProperty(disp, &seltext, 1, XCompoundTextStyle, &tp); + write_data(requestor, property, target, tp.format, 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; + write_data(requestor, property, atom_atom, 32, targets, len); + return property; + } else if (target == timestamp_atom) { + Time rettime = CurrentTime; + write_data(requestor, property, integer_atom, 32, &rettime, 1); + return property; + } else { + return None; + } +} + +Atom convert_sel_outer(Window requestor, Atom target, Atom property) { + /* + * ICCCM 2.2 says that obsolete clients requesting the selection + * request may not specify a property name under which they want + * the data written to their window; selection owners are + * encouraged to support such clients by reusing the selection + * target name as the property. + */ + if (property == None) + property = target; + + 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 = sel_delta; + Atom actual_type; + int actual_format, i; + unsigned 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 < (long)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; + int i, j; while (1) { XNextEvent (disp, &ev); 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); + ev.xselection.property, False); return; } } else { + int have_ownership = True; + switch (ev.type) { case SelectionClear: /* Selection has been cleared by another app. */ - return; + have_ownership = False; + break; case SelectionRequest: - e2.xselection.type = SelectionNotify; - e2.xselection.requestor = ev.xselectionrequest.requestor; - 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; + if (have_ownership) { + e2.xselection.type = SelectionNotify; + e2.xselection.requestor = ev.xselectionrequest.requestor; + e2.xselection.selection = ev.xselectionrequest.selection; + e2.xselection.target = ev.xselectionrequest.target; + e2.xselection.time = ev.xselectionrequest.time; + e2.xselection.property = + convert_sel_outer(ev.xselectionrequest.requestor, + ev.xselectionrequest.target, + ev.xselectionrequest.property); + XSendEvent (disp, ev.xselectionrequest.requestor, + False, 0, &e2); + } + break; + case PropertyNotify: + for (i = j = 0; i < nincrs; i++) { + int keep = True; + if (incrs[i].window == ev.xproperty.window && + incrs[i].property == ev.xproperty.atom && + ev.xproperty.state == PropertyDelete) { + size_t thissize = incrs[i].size; + if (thissize > sel_delta) + thissize = sel_delta; + + XChangeProperty(disp, + incrs[i].window, incrs[i].property, + incrs[i].type, incrs[i].format, + PropModeReplace, incrs[i].data, + thissize / (incrs[i].format/8)); + + if (thissize == 0) { + /* + * If we've just sent a zero-length block, + * the incremental transfer is over and we + * should delete this entry. + */ + keep = False; + } + + incrs[i].data += thissize; + incrs[i].size -= thissize; + } + if (keep) { + if (j != i) + incrs[j] = incrs[i]; + j++; + } } - XSendEvent (disp, ev.xselectionrequest.requestor, False, 0, &e2); + nincrs = j; + break; } + if (nincrs == 0 && !have_ownership) + return; } } } @@ -392,40 +688,120 @@ void done_X(void) { XCloseDisplay (disp); } -void do_paste(Window window, Atom property, int Delete) { +void do_paste(Window window, Atom property, int cutbuffer) { Atom actual_type; int actual_format, i; - long nitems, bytes_after, nread; + unsigned long nitems, bytes_after, nread; unsigned char *data; + int incremental = False; + XEvent ev; nread = 0; - while (XGetWindowProperty(disp, window, property, nread / 4, SELDELTA, - Delete, AnyPropertyType, &actual_type, + while (XGetWindowProperty(disp, window, property, nread / 4, sel_delta / 4, + !cutbuffer, 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 (actual_type == strtype && nitems > 0) { - assert(actual_format == 8); - fwrite(data, 1, nitems, stdout); - nread += nitems; + /* + * ICCCM 2.7.2: if we receive data with the type atom set to + * INCR, it indicates that the actual data will arrive in + * multiple chunks, terminating with a zero-length one. + * Between each pair, we must wait for a PropertyNotify event + * which tells us that the next chunk has arrived. + */ + if (actual_type == incr_atom && !cutbuffer) { + incremental = True; + /* + * Immediately wait for the first chunk of real data. + */ + do { + XMaskEvent(disp, PropertyChangeMask, &ev); + } while (ev.xproperty.state != PropertyNewValue); + /* + * And loop straight back round to read it. + */ + continue; + } + + 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 (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) - break; + if (bytes_after == 0) { + /* + * We've come to the end of the property we're reading. + */ + if (incremental) { + /* + * For an incremental transfer, this means we wait for + * another property to be dumped in the same place on + * our window, and loop round again reading that. The + * exception is if the total length of the property we + * got was zero, which signals the end. + */ + if (nread == 0 && nitems == 0) + break; /* all done */ + + /* otherwise wait for the next chunk */ + do { + XMaskEvent(disp, PropertyChangeMask, &ev); + } while (ev.xproperty.state != PropertyNewValue); + + /* which we read from the beginning */ + nread = 0; + } else { + break; + } + } } }