diff --git a/xclib.c b/xclib.c index 1aeea17..14c623e 100644 --- a/xclib.c +++ b/xclib.c @@ -363,6 +363,7 @@ xcin(Display * dpy, static Atom inc; static Atom targets; static Atom alt_target; + static Atom timestamp; if (!alt_target) { alt_target = XInternAtom(dpy, "STRING", False); @@ -377,6 +378,10 @@ xcin(Display * dpy, inc = XInternAtom(dpy, "INCR", False); } + if (!timestamp) { + timestamp = XInternAtom(dpy, "TIMESTAMP", False); + } + /* We consider selections larger than a quarter of the maximum request size to be "large". See ICCCM section 2.5 */ if (!(*chunk_size)) { @@ -418,8 +423,8 @@ xcin(Display * dpy, /* put the data into a property */ if (evt.xselectionrequest.target == targets) { - Atom types[3] = { targets, target, alt_target }; - int types_count = alt_txt == NULL ? 2 : 3; + Atom types[4] = { targets, target, timestamp, alt_target }; + int types_count = alt_txt == NULL ? 3 : 4; if ( xcverb >= ODEBUG ) { fprintf(stderr, "xclib: debug: sending list of TARGETS\n"); @@ -434,6 +439,21 @@ xcin(Display * dpy, types_count ); } + else if (evt.xselectionrequest.target == timestamp) { + if ( xcverb >= ODEBUG ) { + fprintf(stderr, "xclib: debug: sending TIMESTAMP\n"); + } + + /* Respond with the ownership timestamp as XA_INTEGER 32-bit. + * TIMESTAMP is a single-shot response, no INCR needed. */ + long ts = (long) sel_timestamp; + XChangeProperty(dpy, + *win, + *pty, + XA_INTEGER, + 32, PropModeReplace, (unsigned char *) &ts, + 1); + } else if (evt.xselectionrequest.target == alt_target && alt_txt) { if ( xcverb >= ODEBUG ) { fprintf(stderr, "xclib: debug: sending alternative text\n"); @@ -503,6 +523,10 @@ xcin(Display * dpy, if (evt.xselectionrequest.target == alt_target) return (1); /* Finished with request */ + /* don't treat TIMESTAMP request as contents request */ + if (evt.xselectionrequest.target == timestamp) + return (1); /* Finished with request */ + /* if len <= chunk_size, then the data was sent all at * once and the transfer is now complete, return 1 */ diff --git a/xclib.h b/xclib.h index 896b19b..ab610cb 100644 --- a/xclib.h +++ b/xclib.h @@ -24,6 +24,9 @@ /* global verbosity output level */ extern int xcverb; +/* ownership timestamp for ICCCM TIMESTAMP target */ +extern Time sel_timestamp; + /* global error flags from xchandler() */ extern int xcerrflag; extern XErrorEvent xcerrevt; diff --git a/xclip.c b/xclip.c index 81e9f6f..9f0180a 100644 --- a/xclip.c +++ b/xclip.c @@ -55,6 +55,7 @@ static int frmnl = F; /* remove (single) newline character at the very end if p static int fsecm = F; /* zero out selection buffer before exiting */ Display *dpy; /* connection to X11 display */ +Time sel_timestamp = CurrentTime; /* ownership timestamp for ICCCM TIMESTAMP target */ XrmDatabase opt_db = NULL; /* database for options */ char **fil_names; /* names of files to read */ @@ -366,6 +367,26 @@ doOptTarget(void) } } +/* Acquire a real X server timestamp per ICCCM ยง2.1: + * do a zero-length property append, then wait for PropertyNotify. */ +static Time +acquire_timestamp(Display *dpy, Window win) +{ + Atom ts_atom = XInternAtom(dpy, "XCLIP_TIMESTAMP_PROP", False); + XEvent evt; + + XSelectInput(dpy, win, PropertyChangeMask); + XChangeProperty(dpy, win, ts_atom, XA_INTEGER, 32, PropModeAppend, NULL, 0); + XFlush(dpy); + while (1) { + XNextEvent(dpy, &evt); + if (evt.type == PropertyNotify) { + XDeleteProperty(dpy, win, ts_atom); + return evt.xproperty.time; + } + } +} + static int doIn(Window win, const char *progname) { @@ -472,8 +493,8 @@ doIn(Window win, const char *progname) /* take control of the selection so that we receive * SelectionRequest events from other windows */ - /* FIXME: Should not use CurrentTime, according to ICCCM section 2.1 */ - XSetSelectionOwner(dpy, sseln, win, CurrentTime); + sel_timestamp = acquire_timestamp(dpy, win); + XSetSelectionOwner(dpy, sseln, win, sel_timestamp); /* Double-check SetSelectionOwner did not "merely appear to succeed". */ Window owner = XGetSelectionOwner(dpy, sseln); diff --git a/xctest b/xctest index d1c0662..a47e2b2 100755 --- a/xctest +++ b/xctest @@ -61,6 +61,41 @@ else echo "PASS: xclip exited correctly after losing selection" fi +# test that TARGETS response includes TIMESTAMP when xclip owns a selection +echo "Testing that TARGETS response includes TIMESTAMP" +printf '%s' " xclip -o -sel clipboard -t TARGETS includes TIMESTAMP " +echo "test data" | $checker ./xclip -sel clipboard -i +sleep "$delay" +actual=$($checker ./xclip -o -sel clipboard -t TARGETS) +if echo "$actual" | grep -qx TIMESTAMP; then + echo "PASS" +else + echo "FAIL: TARGETS output was:" + echo "$actual" + exit 1 +fi + +# test that TIMESTAMP target returns a positive integer, not raw clipboard data +echo "Testing that TIMESTAMP target returns a positive integer" +printf '%s' " xclip -o -sel clipboard -t TIMESTAMP returns a positive integer " +echo "test data" | $checker ./xclip -sel clipboard -i +sleep "$delay" +actual=$($checker ./xclip -o -sel clipboard -t TIMESTAMP) +case "$actual" in + "" | *[!0-9]*) + echo "FAIL: expected a positive integer, got: $actual" + exit 1 + ;; + *) + if [ "$actual" -gt 0 ]; then + echo "PASS" + else + echo "FAIL: expected a positive integer, got: $actual" + exit 1 + fi + ;; +esac + # temp file names (in and out) tempi=`mktemp` || exit 1 tempo=`mktemp` || exit 1