Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions xclib.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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)) {
Expand Down Expand Up @@ -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");
Expand All @@ -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");
Expand Down Expand Up @@ -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
*/
Expand Down
3 changes: 3 additions & 0 deletions xclib.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
25 changes: 23 additions & 2 deletions xclip.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
Expand Down
35 changes: 35 additions & 0 deletions xctest
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down