diff --git a/Makefile.in b/Makefile.in index 42b3393..b67176d 100644 --- a/Makefile.in +++ b/Makefile.in @@ -21,10 +21,10 @@ all: xclip xclip: $(X11OBJ) $(OBJS) $(CC) $(OBJS) $(CFLAGS) -o $@ $(X11OBJ) $(LDFLAGS) -install: installbin install.man +install: install.bin install.man install.completion -.PHONY: installbin -installbin: xclip xclip-copyfile xclip-pastefile xclip-cutfile +.PHONY: install.bin +install.bin: xclip xclip-copyfile xclip-pastefile xclip-cutfile mkdir -p $(DESTDIR)$(bindir) $(INSTALL) $^ $(DESTDIR)$(bindir) @@ -34,6 +34,11 @@ install.man: xclip.1 xclip-copyfile.1 mkdir -p $(DESTDIR)$(mandir)/man1 $(INSTALL) -m 644 $^ $(DESTDIR)$(mandir)/man1 +.PHONY: install.completion +install.completion: xclip.bash-completion + mkdir -p $(DESTDIR)/etc/bash_completion.d/ + $(INSTALL) -m 755 $^ $(DESTDIR)/etc/bash_completion.d + .PHONY: clean clean: rm -f *.o *~ xclip xclip-$(VERSION).tar.gz borked @@ -52,22 +57,27 @@ xclip-$(VERSION).tar.gz: (cd /tmp/xclip-make-dist-dir; \ tar zcvf xclip-$(VERSION)/xclip-$(VERSION).tar.gz \ xclip-$(VERSION)/COPYING \ + xclip-$(VERSION)/INSTALL \ xclip-$(VERSION)/README \ xclip-$(VERSION)/ChangeLog \ xclip-$(VERSION)/configure \ xclip-$(VERSION)/configure.ac \ xclip-$(VERSION)/bootstrap \ - xclip-$(VERSION)/xclip-copyfile \ - xclip-$(VERSION)/xclip-pastefile \ - xclip-$(VERSION)/xclip-cutfile \ xclip-$(VERSION)/install-sh \ xclip-$(VERSION)/Makefile.in \ + xclip-$(VERSION)/xclip.bash-completion \ + xclip-$(VERSION)/xclip-copyfile \ + xclip-$(VERSION)/xclip-cutfile \ + xclip-$(VERSION)/xclip-pastefile \ xclip-$(VERSION)/xclip.spec \ + xclip-$(VERSION)/xctest \ xclip-$(VERSION)/*.c \ xclip-$(VERSION)/*.h \ + xclip-$(VERSION)/autogroff.sh \ xclip-$(VERSION)/xclip-copyfile.1 \ xclip-$(VERSION)/xclip.1 ) - rm -rf /tmp/xclip-make-dist-dir + rm /tmp/xclip-make-dist-dir/xclip-$(VERSION) + rmdir /tmp/xclip-make-dist-dir Makefile: Makefile.in configure ./config.status @@ -75,9 +85,27 @@ Makefile: Makefile.in configure configure: configure.ac ./bootstrap +# Create documentation from the man page +xclip.pdf: xclip.1 + groff -Tpdf -mandoc $^ > $@ + +xclip.cat: xclip.1 + nroff -mandoc $^ > $@ + +# Run the test script +test: xctest + ./xctest + +# borked is a test program which intentionally causes errors. borked: borked.c xclib.o xcprint.o $(CC) $^ $(CFLAGS) -o $@ $(X11OBJ) $(LDFLAGS) +# For ease of editing the man page, automatically regenerate the PDF every time +# xclip.1 (the groff source code of the man page) is saved. If the document is +# already being viewed in atril or evince, it will be reloaded immediately. +watch: + ./autogroff.sh + .SUFFIXES: .SUFFIXES: .c .o diff --git a/autogroff.sh b/autogroff.sh new file mode 100755 index 0000000..87dbe0f --- /dev/null +++ b/autogroff.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +trap cleanup exit; + +cleanup() { + for p in $(jobs -p); do + echo "Killing $p" + kill $p + done +} + +for f in *.1; do + file=${f%.1} + groff -Tpdf -P-e -P-pletter -mpdfmark -mandoc $file.1 > $file.pdf + echo "Waiting on $file.1. HIT ^C TO CANCEL" + while inotifywait -e MODIFY $file.1; do + groff -Tpdf -P-e -P-pletter -mpdfmark -mandoc $file.1 > $file.pdf + nroff -mandoc $file.1 > $file.cat + echo "Waiting on $file.1. HIT ^C TO CANCEL" + done & +done + +wait + + diff --git a/configure.ac b/configure.ac index fe43a6f..e846dbd 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT(xclip, 0.13) +AC_INIT([xclip],[0.14]) AC_CONFIG_SRCDIR([xclip.c]) @@ -8,8 +8,7 @@ if test "$GCC" = yes; then fi AC_PROG_INSTALL -AC_LANG_C -AC_HEADER_STDC +AC_LANG([C]) AC_PATH_XTRA AC_CHECK_TOOL(STRIP, strip, :) AC_CHECK_HEADER([X11/Xmu/Atoms.h], [], AC_MSG_ERROR([*** X11/Xmu/Atoms.h is missing ***])) @@ -19,4 +18,5 @@ AC_CHECK_HEADER([iconv.h], AC_DEFINE([HAVE_ICONV]), []), []) AC_CHECK_LIB(Xmu, XmuClientWindow, [], AC_MSG_ERROR([*** libXmu not found ***])) -AC_OUTPUT(Makefile) +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT diff --git a/xcdef.h b/xcdef.h index df7a811..156818f 100644 --- a/xcdef.h +++ b/xcdef.h @@ -3,7 +3,7 @@ * * xcdef.h - definitions for use throughout xclip * Copyright (C) 2001 Kim Saunders - * Copyright (C) 2007-2008 Peter Åstrand + * Copyright (C) 2007-2022 Peter Åstrand * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/xclib.c b/xclib.c index dbcb3e8..463a6b4 100644 --- a/xclib.c +++ b/xclib.c @@ -3,7 +3,7 @@ * * xclib.c - xclip library to look after xlib mechanics for xclip * Copyright (C) 2001 Kim Saunders - * Copyright (C) 2007-2008 Peter Åstrand + * Copyright (C) 2007-2022 Peter Åstrand * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,13 +24,20 @@ #include #include #include +#include +#include #include "xcdef.h" #include "xcprint.h" #include "xclib.h" -/* global verbosity output level, defaults to OSILENT */ +/* Global verbosity output level, defaults to OSILENT */ int xcverb = OSILENT; +/* Global variables that get used/set by xchandler() */ +Window xcourwin = 0; /* Our Window. Set by xcin, used by xchandler). */ +int xcerrflag = False; /* Set to True in xchandler() */ +XErrorEvent xcerrevt; /* Set to latest event in xchandler() */ + /* Table of event names from event numbers */ const char *evtstr[LASTEvent] = { "ProtocolError", "ProtocolReply", "KeyPress", "KeyRelease", @@ -44,12 +51,53 @@ const char *evtstr[LASTEvent] = { "SelectionRequest", "SelectionNotify", "ColormapNotify", "ClientMessage", "MappingNotify", "GenericEvent", }; +/* Table of Requests indexed by op-code (from Xproto.h) */ +const char *reqstr[X_NoOperation+1] = { "0", + "X_CreateWindow", "X_ChangeWindowAttributes", "X_GetWindowAttributes", + "X_DestroyWindow", "X_DestroySubwindows", "X_ChangeSaveSet", + "X_ReparentWindow", "X_MapWindow", "X_MapSubwindows", "X_UnmapWindow", + "X_UnmapSubwindows", "X_ConfigureWindow", "X_CirculateWindow", + "X_GetGeometry", "X_QueryTree", "X_InternAtom", "X_GetAtomName", + "X_ChangeProperty", "X_DeleteProperty", "X_GetProperty", + "X_ListProperties", "X_SetSelectionOwner", "X_GetSelectionOwner", + "X_ConvertSelection", "X_SendEvent", "X_GrabPointer", "X_UngrabPointer", + "X_GrabButton", "X_UngrabButton", "X_ChangeActivePointerGrab", + "X_GrabKeyboard", "X_UngrabKeyboard", "X_GrabKey", "X_UngrabKey", + "X_AllowEvents", "X_GrabServer", "X_UngrabServer", "X_QueryPointer", + "X_GetMotionEvents", "X_TranslateCoords", "X_WarpPointer", + "X_SetInputFocus", "X_GetInputFocus", "X_QueryKeymap", "X_OpenFont", + "X_CloseFont", "X_QueryFont", "X_QueryTextExtents", "X_ListFonts", + "X_ListFontsWithInfo", "X_SetFontPath", "X_GetFontPath", "X_CreatePixmap", + "X_FreePixmap", "X_CreateGC", "X_ChangeGC", "X_CopyGC", "X_SetDashes", + "X_SetClipRectangles", "X_FreeGC", "X_ClearArea", "X_CopyArea", + "X_CopyPlane", "X_PolyPoint", "X_PolyLine", "X_PolySegment", + "X_PolyRectangle", "X_PolyArc", "X_FillPoly", "X_PolyFillRectangle", + "X_PolyFillArc", "X_PutImage", "X_GetImage", "X_PolyText8", + "X_PolyText16", "X_ImageText8", "X_ImageText16", "X_CreateColormap", + "X_FreeColormap", "X_CopyColormapAndFree", "X_InstallColormap", + "X_UninstallColormap", "X_ListInstalledColormaps", "X_AllocColor", + "X_AllocNamedColor", "X_AllocColorCells", "X_AllocColorPlanes", + "X_FreeColors", "X_StoreColors", "X_StoreNamedColor", "X_QueryColors", + "X_LookupColor", "X_CreateCursor", "X_CreateGlyphCursor", "X_FreeCursor", + "X_RecolorCursor", "X_QueryBestSize", "X_QueryExtension", + "X_ListExtensions", "X_ChangeKeyboardMapping", "X_GetKeyboardMapping", + "X_ChangeKeyboardControl", "X_GetKeyboardControl", "X_Bell", + "X_ChangePointerControl", "X_GetPointerControl", "X_SetScreenSaver", + "X_GetScreenSaver", "X_ChangeHosts", "X_ListHosts", "X_SetAccessControl", + "X_SetCloseDownMode", "X_KillClient", "X_RotateProperties", + "X_ForceScreenSaver", "X_SetPointerMapping", "X_GetPointerMapping", + "X_SetModifierMapping", "X_GetModifierMapping", "120", "121", "122", + "123", "124", "125", "126", "X_NoOperation", +}; + + + /* a memset function that won't be optimized away by compiler */ void xcmemzero(void *ptr, size_t len) { if (xcverb >= ODEBUG) { - fprintf(stderr, "xclip: debug: Zeroing memory buffer\n"); + fprintf(stderr, "xclip: debug: Zeroing memory buffer.\n"); } memset_func(ptr, 0, len); } @@ -173,17 +221,37 @@ xcout(Display * dpy, *len = 0; } + if ( xcverb >= ODEBUG ) { + fprintf(stderr,"xclib: debug: XCOUT_NONE: " + "Requesting XConvertSelection("); + fprintf(stderr,"Display = %s, ", DisplayString(dpy)); + fprintf(stderr,"Selection = %s, ", xcatomstr(dpy, sel)); + fprintf(stderr,"Target = %s, ", xcatomstr(dpy, target)); + fprintf(stderr,"Property = %s, ", xcatomstr(dpy, pty)); + fprintf(stderr,"Window = %s, ", xcnamestr(dpy, win)); + fprintf(stderr,"Time = %ld)\n", CurrentTime); + } + /* send a selection request */ XConvertSelection(dpy, sel, target, pty, win, CurrentTime); *context = XCLIB_XCOUT_SENTCONVSEL; return (0); case XCLIB_XCOUT_SENTCONVSEL: - if (evt.type != SelectionNotify) + if (evt.type != SelectionNotify) { + if ( xcverb >= ODEBUG ) { + fprintf(stderr,"xclib: debug: XCLIB_XCOUT_SENTCONVSEL: " + "ignoring %s event.\n", evtstr[evt.type]); + } return (0); + } /* return failure when the current target failed */ if (evt.xselection.property == None) { + if ( xcverb >= ODEBUG ) { + fprintf(stderr,"xclib: debug: XCLIB_XCOUT_SENTCONVSEL: " + "received failure notification.\n"); + } *context = XCLIB_XCOUT_BAD_TARGET; return (0); } @@ -202,7 +270,7 @@ xcout(Display * dpy, /* start INCR mechanism by deleting property */ if (xcverb >= OVERBOSE) { fprintf(stderr, - "xclib: debug: Starting INCR by deleting property\n"); + "Starting INCR request by deleting property.\n"); } XDeleteProperty(dpy, win, pty); XFlush(dpy); @@ -211,13 +279,23 @@ xcout(Display * dpy, } /* not using INCR mechanism, just read the property */ - XGetWindowProperty(dpy, - win, - pty, - 0, + if (xcverb >= ODEBUG) { + fprintf(stderr,"xclib: debug: XCLIB_XCOUT_SENTCONVSEL: " + "Requesting XGetWindowProperty("); + fprintf(stderr,"Display = %s, ", DisplayString(dpy)); + fprintf(stderr,"Window = %s, ", xcnamestr(dpy, win)); + fprintf(stderr,"Property = %s, ", xcatomstr(dpy, pty)); + fprintf(stderr,"Offset = 0, "); + fprintf(stderr,"Length = %ld, ", pty_size); + fprintf(stderr,"Delete = False, "); + fprintf(stderr,"Req_type = AnyPropertyType"); + fprintf(stderr,"...)\n"); + } + + XGetWindowProperty(dpy, win, pty, 0, (long) pty_size, - False, - AnyPropertyType, type, &pty_format, &pty_items, &pty_size, &buffer); + False, AnyPropertyType, type, &pty_format, + &pty_items, &pty_size, &buffer); /* finished with property, delete it */ XDeleteProperty(dpy, win, pty); @@ -225,6 +303,10 @@ xcout(Display * dpy, /* compute the size of the data buffer we received */ pty_machsize = pty_items * mach_itemsize(pty_format); + if (xcverb >= OVERBOSE) { + fprintf(stderr, "Received %ld bytes of data.\n", pty_machsize); + } + /* copy the buffer to the pointer for returned data */ ltxt = (unsigned char *) xcmalloc(pty_machsize); memcpy(ltxt, buffer, pty_machsize); @@ -234,7 +316,8 @@ xcout(Display * dpy, *txt = ltxt; /* free the buffer */ - XFree(buffer); + if (buffer) + XFree(buffer); *context = XCLIB_XCOUT_NONE; @@ -247,53 +330,73 @@ xcout(Display * dpy, * event indicating that the property has been created, * then read it, delete it, etc. */ + + /* return failure if selection owner signals something went wrong */ + if (evt.type == SelectionNotify && evt.xselection.property == None) { + *context = XCLIB_XCOUT_SELECTION_REFUSED; + return (0); + } /* make sure that the event is relevant */ - if (evt.type != PropertyNotify) + if (evt.type != PropertyNotify) { + if ( xcverb >= ODEBUG ) { + fprintf(stderr, "xclib: debug: XCLIB_XCOUT_INCR: ignoring %s event.\n", + evtstr[evt.type]); + } return (0); + } /* skip unless the property has a new value */ - if (evt.xproperty.state != PropertyNewValue) + if (evt.xproperty.state != PropertyNewValue) { + if ( xcverb >= ODEBUG ) { + fprintf(stderr, "xclib: debug: XCLIB_XCOUT_INCR: " + "Skipping. Property does not have a new value.\n"); + } return (0); + } - /* check size and format of the property */ - XGetWindowProperty(dpy, - win, - pty, - 0, - 0, - False, - AnyPropertyType, - type, &pty_format, &pty_items, &pty_size, (unsigned char **) &buffer); + /* check size (pty_size) and format (pty_format) of the property */ + XGetWindowProperty(dpy, win, pty, 0, + 0, /* length = 0 means just get pty_size */ + False, AnyPropertyType, type, &pty_format, + &pty_items, &pty_size, (unsigned char **) &buffer); + + if (buffer) + XFree(buffer); if (pty_size == 0) { /* no more data, exit from loop */ - if (xcverb >= ODEBUG) { - fprintf(stderr, "INCR transfer complete\n"); + if (xcverb >= OVERBOSE) { + fprintf(stderr, "Finished requesting data for INCR transfer.\n"); } - XFree(buffer); XDeleteProperty(dpy, win, pty); *context = XCLIB_XCOUT_NONE; - /* this means that an INCR transfer is now - * complete, return 1 + /* Return 1 to indicate that the INCR transfer is now complete. */ return (1); } - XFree(buffer); - - /* if we have come this far, the property contains - * text, we know the size. + /* if we have come this far, the property contains data and we know the + * size. Retrieve it. */ - XGetWindowProperty(dpy, - win, - pty, - 0, + if (xcverb >= ODEBUG) { + fprintf(stderr,"xclib: debug: XCLIB_XCOUT_INCR: " + "Requesting XGetWindowProperty("); + fprintf(stderr,"Display = %s, ", DisplayString(dpy)); + fprintf(stderr,"Window = %s, ", xcnamestr(dpy, win)); + fprintf(stderr,"Property = %s, ", xcatomstr(dpy, pty)); + fprintf(stderr,"Offset = 0, "); + fprintf(stderr,"Length = %ld, ", pty_size); + fprintf(stderr,"Delete = False, "); + fprintf(stderr,"Req_type = AnyPropertyType"); + fprintf(stderr,"...)\n"); + } + + XGetWindowProperty(dpy, win, pty, 0, (long) pty_size, - False, - AnyPropertyType, - type, &pty_format, &pty_items, &pty_size, (unsigned char **) &buffer); + False, AnyPropertyType, type, &pty_format, + &pty_items, &pty_size, (unsigned char **) &buffer); /* compute the size of the data buffer we received */ pty_machsize = pty_items * mach_itemsize(pty_format); @@ -308,61 +411,84 @@ xcout(Display * dpy, ltxt = (unsigned char *) xcrealloc(ltxt, *len); } + if (xcverb >= OVERBOSE) { + fprintf(stderr, "Received %ld bytes of data.\n", pty_machsize); + } + /* add data to ltxt */ memcpy(<xt[*len - pty_machsize], buffer, pty_machsize); *txt = ltxt; XFree(buffer); - + /* delete property to get the next item */ XDeleteProperty(dpy, win, pty); XFlush(dpy); return (0); + + default: /* Should never happen, but just in case */ + fprintf(stderr, + "xclib: programming error: " + "xcout state machine is in unknown context %d.\n", *context); + exit(1); } return (0); } -/* put data into a selection, in response to a SelectionRequest event from + + +/* xcin: + * put data into a selection, in response to a SelectionRequest event from * another window (and any subsequent events relating to an INCR transfer). * - * Arguments are: + * ARGUMENTS are: * - * A display + * * A display * - * A window + * * Our window: just used to stash in a global variable, xcourwin, so our X + * error handler (xchandler) can send a message to our window to + * break the main loop out of blocking on XNextEvent() . + * + * * Their window: The window that sent us an event. * - * The event to respond to + * * The event to respond to * - * A pointer to an Atom. This gets set to the property nominated by the other - * app in it's SelectionRequest. Things are likely to break if you change the - * value of this yourself. + * * A pointer to an Atom. This gets set to the property nominated by the other + * app in it's SelectionRequest. Things are likely to break if you change the + * value of this yourself. * - * The target(UTF8_STRING or XA_STRING) to respond to + * * The target(UTF8_STRING or XA_STRING) to respond to * - * A pointer to an array of chars to read selection data from. + * * A pointer to an array of chars to read selection data from. * - * The length of the array of chars. + * * The length of the array of chars. * - * In the case of an INCR transfer, the position within the array of chars - * that is being processed. + * * In the case of an INCR transfer, the position within the array of chars + * that is being processed. * - * The context that event is the be processed within. + * * The context that event is the be processed within. + * + * RETURN VALUE + * * 1 if we are finished with a multipart request. + * * -1 if XChangeProperty returned an error. + * * 0 otherwise. */ int xcin(Display * dpy, - Window * win, + Window ourwin, + Window * theirwin, XEvent evt, - Atom * pty, Atom target, unsigned char *txt, unsigned long len, unsigned long *pos, - unsigned int *context, long *chunk_size) + Atom * pty, Atom target, unsigned char *txt, unsigned long len, + unsigned long *pos, unsigned int *context, long *chunk_size) { - unsigned long chunk_len; /* length of current chunk (for incr - * transfers only) - */ - XEvent res; /* response to event */ + unsigned long chunk_len; /* length of current chunk (for incr xfr only)*/ + XEvent res; /* response to event */ static Atom inc; static Atom targets; + xcourwin = ourwin; /* For sending error events to our caller */ + if (!targets) { targets = XInternAtom(dpy, "TARGETS", False); } @@ -371,40 +497,49 @@ xcin(Display * dpy, inc = XInternAtom(dpy, "INCR", False); } - /* We consider selections larger than a quarter of the maximum - request size to be "large". See ICCCM section 2.5 */ + /* Selections larger than the maximum request size must be sent + incrementally. See ICCCM section 2.5. Note that we must subtract some + bytes to account for the protocol header of a request. As of 2020, the + header is 32 bytes, but we'll leave aside 1024, just in case. */ if (!(*chunk_size)) { - *chunk_size = XExtendedMaxRequestSize(dpy) / 4; + *chunk_size = XExtendedMaxRequestSize(dpy) << 2; /* Words to bytes */ if (!(*chunk_size)) { - *chunk_size = XMaxRequestSize(dpy) / 4; - } + *chunk_size = XMaxRequestSize(dpy) << 2; + } /* Leave room for */ + *chunk_size -= 1024; /* request header */ + } + + /* If an Alloc error occurs during the storing of the selection data, + * all properties stored for this selection should be deleted and the + * ConvertSelection request should be refused (see ICCCM 2.2). + */ + int xcchangeproperr = False; /* Flag if XChangeProperty() failed */ + + /* A SelectionRequest while waiting for INCR should restart statemachine. */ + if (evt.type == SelectionRequest && *context == XCLIB_XCIN_INCR) { if ( xcverb >= ODEBUG ) { fprintf(stderr, - "xclib: debug: INCR chunk size is %ld\n", (*chunk_size)); + "xclib: debug: Got SelectionRequest, aborting INCR and restarting state machine.\n"); } + *context = XCLIB_XCIN_NONE; } switch (*context) { case XCLIB_XCIN_NONE: - if ( xcverb >= ODEBUG ) - fprintf(stderr, "xclib: debug: context: XCLIB_XCIN_NONE\n"); - - if ( xcverb >= ODEBUG && evt.xselectionrequest.target) { - char *tempstr = XGetAtomName(dpy, evt.xselectionrequest.target); - fprintf(stderr, "xclib: debug: target: %s\n", tempstr); - XFree(tempstr); - } - if (evt.type != SelectionRequest) { if ( xcverb >= ODEBUG ) { fprintf(stderr, - "xclib: debug: ignoring %s event (context is NONE)\n", + "xclib: debug: XCIN_NONE: ignoring %s event.\n", evtstr[evt.type]); } return (0); } + if ( xcverb >= ODEBUG && evt.xselectionrequest.target) { + fprintf(stderr, "xclib: debug: XCIN_NONE: target: %s.\n", + xcatomstr(dpy, evt.xselectionrequest.target)); + } /* set the window and property that is being used */ - *win = evt.xselectionrequest.requestor; + *theirwin = evt.xselectionrequest.requestor; *pty = evt.xselectionrequest.property; /* reset position to 0 */ @@ -415,63 +550,72 @@ xcin(Display * dpy, Atom types[2] = { targets, target }; if ( xcverb >= ODEBUG ) { - fprintf(stderr, "xclib: debug: sending list of TARGETS\n"); + fprintf(stderr,"xclib: debug: XCIN_NONE:" + "sending list of TARGETS.\n"); } /* send data all at once (not using INCR) */ - XChangeProperty(dpy, - *win, - *pty, - XA_ATOM, - 32, PropModeReplace, (unsigned char *) types, - (int) (sizeof(types) / sizeof(Atom)) - ); + xcchangeproperr = xcchangeprop(dpy, *theirwin, *pty, + XA_ATOM, 32, PropModeReplace, (unsigned char *) types, + (int) (sizeof(types) / sizeof(Atom)) ); + if (xcverb >= OVERBOSE && xcchangeproperr) { + fprintf(stderr, "Detected XChangeProperty failure.\n"); + } } else if (len > *chunk_size) { /* send INCR response */ if ( xcverb >= ODEBUG ) { - fprintf (stderr, "xclib: debug: Starting INCR response\n"); + fprintf (stderr, "xclib: debug: XCIN_NONE: " + "Starting INCR response.\n"); + } + xcchangeproperr = xcchangeprop( dpy, *theirwin, *pty, inc, + 32, PropModeReplace, 0, 0); + if (xcverb >= OVERBOSE && xcchangeproperr) { + fprintf(stderr, "Detected XChangeProperty failure.\n"); + } + else { /* No error */ + /* With the INCR mechanism, we need to know + * when the requestor window changes (deletes) + * its properties + */ + XSelectInput(dpy, *theirwin, PropertyChangeMask); + *context = XCLIB_XCIN_INCR; } - XChangeProperty(dpy, *win, *pty, inc, 32, PropModeReplace, 0, 0); - - /* With the INCR mechanism, we need to know - * when the requestor window changes (deletes) - * its properties - */ - XSelectInput(dpy, *win, PropertyChangeMask); - - *context = XCLIB_XCIN_INCR; } else { /* send data all at once (not using INCR) */ if ( xcverb >= ODEBUG ) { - fprintf(stderr, "xclib: debug: Sending data all at once" - " (%d bytes)\n", (int) len); + fprintf(stderr, "xclib: debug: XCIN_NONE: " + "Sending data all at once (%d bytes).\n", (int) len); } - XChangeProperty(dpy, - *win, - *pty, - target, - 8, PropModeReplace, (unsigned char *) txt, - (int) len); + xcchangeproperr = xcchangeprop( dpy, *theirwin, *pty, target, + 8, PropModeReplace, + (unsigned char *) txt, (int) len); + if (xcverb >= OVERBOSE && xcchangeproperr) { + fprintf(stderr, "Detected XChangeProperty failure.\n"); + } } - /* Perhaps FIXME: According to ICCCM section 2.5, we should - confirm that XChangeProperty succeeded without any Alloc - errors before replying with SelectionNotify. However, doing - so would require an error handler which modifies a global - variable, plus doing XSync after each XChangeProperty. */ - /* set values for the response event */ res.xselection.property = *pty; res.xselection.type = SelectionNotify; res.xselection.display = evt.xselectionrequest.display; - res.xselection.requestor = *win; + res.xselection.requestor = *theirwin; res.xselection.selection = evt.xselectionrequest.selection; res.xselection.target = evt.xselectionrequest.target; res.xselection.time = evt.xselectionrequest.time; + if (xcchangeproperr) { + /* if XChangeProp failed, refuse XSelectionRequest */ + XDeleteProperty(dpy, *theirwin, *pty); + res.xselection.property = None; + fprintf(stderr, "xclib: error: XCIN_NONE: " + "XChangeProp failed (error %d), " + "refusing request from %s.\n", xcchangeproperr, + xcnamestr(dpy, *theirwin)); + } + /* send the response event */ XSendEvent(dpy, evt.xselectionrequest.requestor, 0, 0, &res); XFlush(dpy); @@ -480,6 +624,10 @@ xcin(Display * dpy, if (evt.xselectionrequest.target == targets) return (1); /* Finished with request */ + /* if XChangeProp failed, requestor is done no matter what */ + if (xcchangeproperr) + return (-1); /* Error in request */ + /* if len <= chunk_size, then the data was sent all at * once and the transfer is now complete, return 1 */ @@ -491,11 +639,15 @@ xcin(Display * dpy, break; case XCLIB_XCIN_INCR: - /* length of current chunk */ - /* ignore non-property events */ - if (evt.type != PropertyNotify) + if (evt.type != PropertyNotify) { + if ( xcverb >= ODEBUG ) { + fprintf(stderr, + "xclib: debug: INCR: ignoring %s event.\n", + evtstr[evt.type]); + } return (0); + } /* ignore the event unless it's to report that the * property has been deleted @@ -504,10 +656,10 @@ xcin(Display * dpy, if ( xcverb >= ODEBUG ) { if ( evt.xproperty.state == 0 ) fprintf(stderr, - "xclib: debug: ignoring PropertyNewValue\n"); + "xclib: debug: INCR: ignoring PropertyNewValue.\n"); else fprintf(stderr, - "xclib: debug: ignoring state %d\n", + "xclib: debug: INCR: ignoring state %d.\n", evt.xproperty.state); } return (0); @@ -534,26 +686,56 @@ xcin(Display * dpy, /* put the chunk into the property */ if ( xcverb >= ODEBUG ) { fprintf(stderr, "xclib: debug: Sending chunk of " - " %d bytes\n", (int) chunk_len); + " %d bytes.\n", (int) chunk_len); + } + xcchangeproperr = xcchangeprop( dpy, *theirwin, *pty, target, + 8, PropModeReplace, &txt[*pos], + (int) chunk_len); + if (xcverb >= OVERBOSE && xcchangeproperr) { + fprintf(stderr, "Detected XChangeProperty failure.\n"); } - XChangeProperty(dpy, - *win, - *pty, - target, - 8, PropModeReplace, &txt[*pos], - (int) chunk_len); } else { /* make an empty property to show we've * finished the transfer */ if ( xcverb >= ODEBUG ) { - fprintf(stderr, "xclib: debug: Signalling end of INCR\n"); + fprintf(stderr, "xclib: debug: Signalling end of INCR.\n"); + } + xcchangeproperr = xcchangeprop( dpy, *theirwin, *pty, target, + 8, PropModeReplace, 0, 0); + if (xcverb >= OVERBOSE && xcchangeproperr) { + fprintf(stderr, "Detected XChangeProperty failure.\n"); } - XChangeProperty(dpy, *win, *pty, target, 8, PropModeReplace, 0, 0); } XFlush(dpy); + /* Did XChangeProp fail? Refuse xfr and return -1*/ + if (xcchangeproperr) { + if (xcverb >= ODEBUG) { + fprintf(stderr, "xclib: debug: Refusing INCR transfer.\n"); + } + *context = XCLIB_XCIN_NONE; + + /* set values for the response event */ + res.xselection.type = SelectionNotify; + res.xselection.display = evt.xselectionrequest.display; + res.xselection.requestor = *theirwin; + res.xselection.selection = evt.xselectionrequest.selection; + res.xselection.target = evt.xselectionrequest.target; + res.xselection.time = evt.xselectionrequest.time; + + /* Refuse selection request */ + res.xselection.property = None; + if (! XSendEvent(dpy, evt.xselectionrequest.requestor,0,0, &res)) { + fprintf(stderr, "xclib: error: Failed to send event " + "to requestor to signal our refusal.\n"); + } + + XFlush(dpy); + return (-1); + } + /* all data has been sent, break out of the loop */ if (!chunk_len) { if (xcverb >= ODEBUG) { @@ -564,8 +746,7 @@ xcin(Display * dpy, *pos += *chunk_size; - /* if chunk_len == 0, we just finished the transfer, - * return 1 + /* if chunk_len == 0, we just finished the transfer, return 1 */ if (chunk_len > 0) return (0); @@ -576,12 +757,22 @@ xcin(Display * dpy, return (0); } - /* xcfetchname(): a utility for finding the name of a given X window. * (Like XFetchName but recursively walks up tree of parent windows.) * Sets namep to point to the string of the name (must be freed with XFree). - * Returns 0 if it works. Not 0, otherwise. - * [See also, xcnamestr() wrapper below.] + * + * ARGUMENTS are: + * + * * The Display. + * + * * A Window to look up. + * + * * A pointer to a string to return the result. + * (Note that the caller must XFree() the string when finished.) + * + * RETURNS 0 if it works. Not 0, otherwise. + * + * [See also, xcnamestr() wrapper below for statically allocated string.] */ int xcfetchname(Display *display, Window w, char **namep) { @@ -589,9 +780,18 @@ xcfetchname(Display *display, Window w, char **namep) { if (w == None) return 1; /* No window, no name. */ + void *fn = XSetErrorHandler(xcnull); /* Suppress error messages */ + + xcerrflag = False; XFetchName(display, w, namep); - if (*namep) + if (xcerrflag == True) { /* Give up if the window disappeared */ + XSetErrorHandler(fn); + return 1; + } + if (*namep) { + XSetErrorHandler(fn); return 0; /* Hurrah! It worked on the first try. */ + } /* Otherwise, recursively try the parent windows */ Window p = w; @@ -604,6 +804,8 @@ xcfetchname(Display *display, Window w, char **namep) { XFetchName(display, p, namep); } } + + XSetErrorHandler(fn); return (*namep == NULL); } @@ -612,8 +814,14 @@ xcfetchname(Display *display, Window w, char **namep) { * used within printf calls with no need to later XFree anything. * It also smoothes over problems by returning the ID number if no name exists. * - * Given a Window ID number, e.g., 0xfa1afe1, return - * either the window name followed by the ID in parens, IFF it can be found, + * ARGUMENTS are: + * + * The Display + * + * A Window ID number, e.g., 0xfa1afe1 + * + * RETURNS a pointer to a statically allocated string containing + * either the window name followed by the ID in parens, if it can be found, * otherwise, the string "window id 0xfa1afe1". * * Example output: "'Falafel' (0xfa1afe1)" @@ -639,21 +847,73 @@ xcnamestr(Display *display, Window w) { return xcname; } +/* xcatomstr: A convenience wrapper for XGetAtomName() that returns a + * string so it can be used within printf calls with no need to later XFree + * anything. It also smoothes over problems by returning "(null)" if no name + * exists and ignoring errors. + * + * Given an Atom, return + * either the Atom name, IFF it can be found, + * otherwise, the string "(null)". + * + * String is statically allocated and is updated at each call. + */ +char xcatom[4096]; +char * +xcatomstr(Display *display, Atom a) { + char *atom_name; + void *fn = XSetErrorHandler(xcnull); /* Suppress error messages */ + atom_name = XGetAtomName(display, a); + XSetErrorHandler(fn); + if (atom_name && atom_name[0]) { + snprintf( xcatom, sizeof(xcatom)-1, "%s", atom_name); + } + else { + snprintf( xcatom, sizeof(xcatom)-1, "(null)"); + } + if (atom_name) + XFree(atom_name); + + xcatom[sizeof(xcatom) - 1] = '\0'; /* Ensure NULL termination */ + return xcatom; +} + + +/* xcnull() + * An X error handler that saves errors but does not print them. + * Writes to global variables xcerrflag and xcerrevt. + * + * Usage: + * void *fn = XSetErrorHandler(xcnull); + * ... [ do work where errors are expected ] ... + * XSetErrorHandler(fn); // Restore previous X error handler + */ +int xcnull(Display *dpy, XErrorEvent *evt) { + xcerrflag = True; + xcerrevt = *evt; + return 0; +} -/* Xlib Error handler that saves last error event */ -/* Usage: XSetErrorHandler(xchandler); */ -int xcerrflag = False; -XErrorEvent xcerrevt; +/* xchandler(): Xlib Error handler that saves last error event. + * + * Writes to global variables xcerrflag and xcerrevt. + * + * Also, wakes up the main loop from blocking on XNextEvent() when a BadWindow + * error is detected by sending an XClientMessage to our own window. Requires + * global xcourwin set to our window ID (currently done when xcin() is called). + * + * Usage: XSetErrorHandler(xchandler); + */ int xchandler(Display *dpy, XErrorEvent *evt) { + void *fn = XSetErrorHandler(XmuSimpleErrorHandler); xcerrflag = True; xcerrevt = *evt; - int len=255; - char buf[len+1]; - XGetErrorText(dpy, evt->error_code, buf, len); + char buf[256]; + XGetErrorText(dpy, evt->error_code, buf, sizeof(buf)-1); if (xcverb >= OVERBOSE) { - fprintf(stderr, "\tXErrorHandler: XError (type %d): %s\n", - evt->type, buf); + fprintf(stderr, "\tXErrorHandler: XError (code %d): %s.\n", + evt->error_code, buf); } if (xcverb >= ODEBUG) { fprintf(stderr, @@ -661,14 +921,94 @@ int xchandler(Display *dpy, XErrorEvent *evt) { "\t\tResource ID: 0x%lx\n" "\t\tSerial Num: %lu\n" "\t\tError code: %u\n" - "\t\tRequest op code: %u major, %u minor\n", + "\t\tRequest op-code: %u major, %u minor\n" + "\t\tFailed request: %s\n" + , evt->type, evt->resourceid, evt->serial, evt->error_code, evt->request_code, - evt->minor_code); + evt->minor_code, + reqstr[evt->request_code] ); + } + + XSetErrorHandler(fn); + + if (evt->error_code == BadWindow) { + if (xcourwin) { + /* We need to break the mainloop out of XNextEvent so it can delete + * the window from the list of requestors right away before another + * window with the same ID is created. + * Solution: Send ourselves a ClientMessage event. + */ + if (xcverb >= ODEBUG) { + fprintf(stderr, "\tSending event to self (0x%lx): " + "Window 0x%lx is bad.\n", xcourwin, evt->resourceid); + } + XClientMessageEvent e; + e.type = ClientMessage; + e.window = xcourwin; + e.format = 8; /* Message is 8-bit bytes, twenty of 'em */ + strncpy(e.data.b, "Break event loop", 20); + e.data.b[19]='\0'; + + if (! XSendEvent(dpy, xcourwin, 0, 0, (XEvent *)&e) ) { + fprintf(stderr, "xclib: error: Failed to send " + "event to main loop to handle BadWindow.\n"); + } + + XFlush(dpy); + } + else { /* else, xcourwin is 0 */ + if (xcverb >= ODEBUG && evt->error_code == BadWindow) { + fprintf(stderr, "xclib: debug: cannot send event " + "because xcourwin not initialized yet.\n"); + } + } } return 0; } + + +/* + * xcchangeprop: wrapper for XChangeProperty that gets errors from xchandler. + * Returns zero if there was no error. Not zero, otherwise. + * + * REQUIRES: XSetErrorHandler(xchandler) must have been previously called. + * [XSetErrorHandler(xcnull) would also work if an error is expected.] + * + * According to ICCCM section 2.5, we should confirm that XChangeProperty + * succeeded without any Alloc errors before replying with SelectionNotify. + * + * Doing so is a bit convoluted. We rely on an error handler, xchandler, + * which modifies a global variable, xcerrflag, which xcchangeprop examines + * after XChangeProperty() + XSync(). If xcerrflag is set, xcchangeprop + * returns non-zero to its caller (xcin, usually), which then sets the + * xcchangeproperr flag that, in turn, signals xclip to refuse the + * selection request by sending a SelectionNotify with the Property set to + * None. (Perhaps this would be easier with libxcb instead of libX11?) + * + * Note: Although XSetErrorHandler(xchandler) is required, this routine does + * not do it itself because it slows down xclip significantly (circa 2020). + * + */ +int +xcchangeprop(Display *display, Window w, Atom property, Atom type, + int format, int mode, unsigned char *data, int nelements) { + static char buf[256]; /* For holding error string */ + + xcerrflag = False; /* global, set by xchandler() */ + XChangeProperty(display, w, property, type, format, mode, data, nelements); + XSync(display, False); + if (xcverb >= OVERBOSE && xcerrflag) { + XGetErrorText(display, xcerrevt.error_code, buf, sizeof(buf)-1); + fprintf(stderr, "xclib: error: XChangeProp failed with %s.\n", buf); + } + + if (xcerrflag) + return xcerrevt.error_code; + else + return 0; +} diff --git a/xclib.h b/xclib.h index 3fe9440..0ca41f4 100644 --- a/xclib.h +++ b/xclib.h @@ -3,7 +3,7 @@ * * xclib.h - header file for functions in xclib.c * Copyright (C) 2001 Kim Saunders - * Copyright (C) 2007-2008 Peter Åstrand + * Copyright (C) 2007-2022 Peter Åstrand * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,10 +35,11 @@ extern XErrorEvent xcerrevt; #define ODEBUG 9 /* xcout() contexts */ -#define XCLIB_XCOUT_NONE 0 /* no context */ -#define XCLIB_XCOUT_SENTCONVSEL 1 /* sent a request */ -#define XCLIB_XCOUT_INCR 2 /* in an incr loop */ -#define XCLIB_XCOUT_BAD_TARGET 3 /* given target failed */ +#define XCLIB_XCOUT_NONE 0 /* no context */ +#define XCLIB_XCOUT_SENTCONVSEL 1 /* sent a request */ +#define XCLIB_XCOUT_INCR 2 /* in an incr loop */ +#define XCLIB_XCOUT_BAD_TARGET 3 /* given target failed */ +#define XCLIB_XCOUT_SELECTION_REFUSED 4 /* owner signaled an error */ /* xcin() contexts */ #define XCLIB_XCIN_NONE 0 @@ -59,6 +60,7 @@ extern int xcout( ); extern int xcin( Display*, + Window, Window*, XEvent, Atom*, @@ -75,13 +77,29 @@ extern void *xcstrdup(const char *); extern void xcmemcheck(void*); extern int xcfetchname(Display *, Window, char **); extern char *xcnamestr(Display *, Window); +extern char *xcatomstr(Display *, Atom); +extern void xcmemzero(void *ptr, size_t len); +extern int xchandler(Display *, XErrorEvent *); +extern int xcnull(Display *dpy, XErrorEvent *evt); +extern int xcchangeprop(Display *, Window, Atom, Atom, int, int, unsigned char *, int); /* volatile prevents compiler from causing dead-store elimination with optimization enabled */ typedef void *(*memset_t)(void *, int, size_t); static volatile memset_t memset_func = memset; -void xcmemzero(void *ptr, size_t len); -int xchandler(Display *, XErrorEvent *); /* Table of event names from event numbers */ extern const char *evtstr[LASTEvent]; + +struct requestor +{ + Window cwin; + Atom pty; + Time stamp; + unsigned int context; + unsigned long sel_pos; + int finished; + long chunk_size; + struct requestor *next; +}; + diff --git a/xclip.1 b/xclip.1 index c646c1f..b70b0b8 100644 --- a/xclip.1 +++ b/xclip.1 @@ -2,7 +2,7 @@ .\" .\" xclip.man - xclip manpage .\" Copyright (C) 2001 Kim Saunders -.\" Copyright (C) 2007-2008 Peter Åstrand +.\" Copyright (C) 2007-2022 Peter Åstrand .\" .\" This program is free software; you can redistribute it and/or modify .\" it under the terms of the GNU General Public License as published by diff --git a/xclip.bash-completion b/xclip.bash-completion new file mode 100644 index 0000000..7ca843d --- /dev/null +++ b/xclip.bash-completion @@ -0,0 +1,70 @@ +# -*- shell-script -*- +# bash completion for xclip, xclip-{copy,cut,paste}file. +# This file is part of the xclip program. +# This lists available TARGETS for -t, available selections for -se, +# available options for -, and files otherwise. +# +# To use once, simply 'source' this file. To have this setup at each login, +# put this file in /etc/bash_completion.d/ . + +_getselection() { + # Did the user specify which selection to use already? + # Look through words for "-selection" then return next word. + # (This affects which TARGETS are returned.) + local w nextandbreak + + for w in "${COMP_WORDS[@]}"; do + [[ -z "$nextandbreak" ]] || break + [[ $w == -sel* ]] && nextandbreak=1 + [[ $w == -c* ]] && break + done + + case "$w" in + "") + echo "Primary" + ;; + -c*) + echo "Clipboard" + ;; + *) + echo "$w" + ;; + esac +} + +_xclip() +{ + local cur prev selection options + + COMPREPLY=() + cur=${COMP_WORDS[COMP_CWORD]} + + prev=${COMP_WORDS[COMP_CWORD-1]} + case $prev in + -t*) + selection=$( _getselection ) + options=$( xclip -selection $selection -target TARGETS 2>/dev/null ) + COMPREPLY=( $( compgen -W "$options" -- $cur ) ) + return 0 + ;; + -sel*) + options="Primary Secondary Clipboard Buffer-cut" + COMPREPLY=( $( compgen -W "$options" -- ${cur^} ) ) + return 0 + ;; + esac + + # Begins with -, so command line option (or badly named file, I suppose) + if [[ "$cur" == -* ]]; then + options="-in -out -filter -selection -display -help" + options+=" -clipboard -target -TARGETS -rmlastnl -V -version" + options+=" -sensitive -wait -loops -noutf8" + options+=" -silent -quiet -v -verbose -debug" + COMPREPLY=( $( compgen -W "$options" -- $cur ) ) + return 0 + fi + + # Fall back to files + COMPREPLY=( $( compgen -f -- $cur ) ) +} +complete -F _xclip xclip diff --git a/xclip.c b/xclip.c index dc9b395..456b16e 100644 --- a/xclip.c +++ b/xclip.c @@ -3,7 +3,7 @@ * * xclip.c - command line interface to X server selections * Copyright (C) 2001 Kim Saunders - * Copyright (C) 2007-2008 Peter Åstrand + * Copyright (C) 2007-2022 Peter Åstrand * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,19 +30,20 @@ #include #include #include +#include #include "xcdef.h" #include "xcprint.h" #include "xclib.h" /* command line option table for XrmParseCommand() */ -XrmOptionDescRec opt_tab[17]; -int opt_tab_size; +XrmOptionDescRec opt_tab[29]; +int opt_tab_size; /* for sanity check later */ /* Options that get set on the command line */ int sloop = 0; /* number of loops */ char *sdisp = NULL; /* X display to connect to */ Atom sseln = XA_PRIMARY; /* X selection to work with */ -Atom target = XA_STRING; +Atom target = None; int wait = 0; /* wait: stop xclip after wait msec after last 'paste event', start counting after first 'paste event' */ @@ -67,85 +68,92 @@ char *rec_typ; int tempi = 0; -struct requestor -{ - Window cwin; - Atom pty; - unsigned int context; - unsigned long sel_pos; - int finished; - long chunk_size; - struct requestor *next; -}; +/* FIXME: Stub variable for fixing XSetSelectionOwner's timestamp. */ +/* Eventually this should append zero-length to a property and get timestamp + * from server's PropertyNotify event. */ +unsigned long int ownertime = CurrentTime; +// See xclip.h for definition of struct requestor. static struct requestor *requestors; -static struct requestor *get_requestor(Window win) +static struct requestor *get_requestor(Window win, Atom pty) { - struct requestor *requestor; + struct requestor *r; if (requestors) { - for (requestor = requestors; requestor != NULL; requestor = requestor->next) { - if (requestor->cwin == win) { + for (r = requestors; r != NULL; r = r->next) { + if (r->cwin == win && r->pty == pty) { if (xcverb >= OVERBOSE) { fprintf(stderr, - " = Reusing requestor for %s\n", - xcnamestr(dpy, win) ); + " = Reusing requestor %lx-%s for %s\n", win, + xcatomstr(dpy, pty), xcnamestr(dpy, win) ); } - return requestor; + return r; } } } if (xcverb >= OVERBOSE) { - fprintf(stderr, " + Creating new requestor for %s\n", - xcnamestr(dpy, win) ); + fprintf(stderr, " + Creating new requestor %lx-%s for %s\n", + win, xcatomstr(dpy, pty), xcnamestr(dpy, win) ); } - requestor = (struct requestor *)calloc(1, sizeof(struct requestor)); - if (!requestor) { + r = (struct requestor *)calloc(1, sizeof(struct requestor)); + if (!r) { errmalloc(); } else { - requestor->context = XCLIB_XCIN_NONE; + /* XXX Please Comment: Why do we not set r->cwin or r->pty here? */ + r->context = XCLIB_XCIN_NONE; + + /* xsel(1) hangs if given more than 4,000,000 bytes at a time. */ + /* FIXME: report bug to xsel and then remove this kludge */ + /* XXX: Do other programs have similar limits? */ + if (!strcmp(xcatomstr(dpy, pty), "XSEL_DATA")) { + r->chunk_size = 4*1000*1000; + if (xcverb >= ODEBUG) + fprintf(stderr, + "Kludging chunksize to 4,000,000 for xsel\n"); + } } if (!requestors) { - requestors = requestor; + requestors = r; } else { - requestor->next = requestors; - requestors = requestor; + r->next = requestors; + requestors = r; } - return requestor; + return r; } -static void del_requestor(struct requestor *requestor) +static void del_requestor(struct requestor *r) { struct requestor *reqitr; - if (!requestor) { + if (!r) { return; } if (xcverb >= OVERBOSE) { fprintf(stderr, - " - Deleting requestor for %s\n", - xcnamestr(dpy, requestor->cwin) ); + " - Deleting requestor %lx-%s for %s\n", + r->cwin, xcatomstr(dpy, r->pty), xcnamestr(dpy, r->cwin) ); } - if (requestors == requestor) { + if (requestors == r) { requestors = requestors->next; } else { for (reqitr = requestors; reqitr != NULL; reqitr = reqitr->next) { - if (reqitr->next == requestor) { + if (reqitr->next == r) { reqitr->next = reqitr->next->next; break; } } } - free(requestor); + free(r); + r = NULL; } int clean_requestors() { @@ -343,6 +351,12 @@ doOptTarget(void) if (xcverb >= OVERBOSE) fprintf(stderr, "Using target: %s\n", rec_val.addr); } + else if (XrmGetResource(opt_db, "xclip.TARGETS", "", &rec_typ, &rec_val) ) { + target = XInternAtom(dpy, "TARGETS", False); + fdiri = F; /* direction is always output when -T used */ + if (xcverb >= OVERBOSE) + fprintf(stderr, "Showing TARGETS.\n"); + } else { target = XA_UTF8_STRING(dpy); if (xcverb >= OVERBOSE) @@ -433,10 +447,20 @@ doIn(Window win, const char *progname) sel_len--; } - /* Handle cut buffer if needed */ + /* Handle old-style cut buffer ('-selection buffercut') */ if (sseln == XA_STRING) { + xcerrflag = False; XStoreBuffer(dpy, (char *) sel_buf, (int) sel_len, 0); - XSetSelectionOwner(dpy, sseln, None, CurrentTime); + XSetSelectionOwner(dpy, sseln, None, ownertime); + XSync(dpy, False); /* Force error to occur now or never */ + if (xcerrflag == True) { + fprintf(stderr, "xclip: error: Could not copy to old-style cut buffer\n"); + if (xcverb >= OVERBOSE) + XmuPrintDefaultErrorMessage(dpy, &xcerrevt, stderr); + xcmemzero(sel_buf,sel_len); + return EXIT_FAILURE; + } + xcmemzero(sel_buf,sel_len); return EXIT_SUCCESS; } @@ -445,7 +469,7 @@ doIn(Window win, const char *progname) * SelectionRequest events from other windows */ /* FIXME: Should not use CurrentTime, according to ICCCM section 2.1 */ - XSetSelectionOwner(dpy, sseln, win, CurrentTime); + XSetSelectionOwner(dpy, sseln, win, ownertime); /* Double-check SetSelectionOwner did not "merely appear to succeed". */ Window owner = XGetSelectionOwner(dpy, sseln); @@ -463,7 +487,7 @@ doIn(Window win, const char *progname) pid = fork(); /* exit the parent process; */ if (pid) { - XSetSelectionOwner(dpy, sseln, None, CurrentTime); + XSetSelectionOwner(dpy, sseln, None, ownertime); xcmemzero(sel_buf,sel_len); exit(EXIT_SUCCESS); } @@ -474,7 +498,7 @@ doIn(Window win, const char *progname) if (sloop == 1) fprintf(stderr, "Waiting for one selection request.\n"); - if (sloop < 1) + if (sloop == 0) fprintf(stderr, "Waiting for selection requests, Control-C to quit\n"); @@ -482,6 +506,11 @@ doIn(Window win, const char *progname) fprintf(stderr, "Waiting for %i selection request%s, Control-C to quit\n", sloop, (sloop==1)?"":"s"); + + if (sloop < 0) + fprintf(stderr, + "xclip: error: loops set to a negative number (%d).\n", + sloop); } /* Avoid making the current directory in use, in case it will need to be umounted */ @@ -496,9 +525,9 @@ doIn(Window win, const char *progname) /* loop and wait for the expected number of * SelectionRequest events */ - while (dloop < sloop || sloop < 1) { + while (dloop < sloop || sloop == 0) { if (xcverb >= ODEBUG) - fprintf(stderr, "\n========\n"); + fprintf(stderr, "\n________\n"); /* print messages about what we're waiting for * if not in silent mode @@ -510,14 +539,20 @@ doIn(Window win, const char *progname) if (sloop == 1) fprintf(stderr, " Waiting for a selection request.\n"); - if (sloop < 1) - fprintf(stderr, " Waiting for selection request number %i\n", dloop + 1); + if (sloop == 0) + fprintf(stderr, " Waiting for selection request number %i\n", + dloop + 1); + + if (sloop < 0) + fprintf(stderr, " This can't happen: negative sloop (%i)\n", + sloop); } /* wait for a SelectionRequest (paste) event */ while (1) { struct requestor *requestor; - Window requestor_id; + Window requestor_win; + Atom requestor_pty; int finished; if (!XPending(dpy) && wait > 0) { @@ -528,7 +563,7 @@ doIn(Window win, const char *progname) FD_ZERO(&in_fds); FD_SET(x11_fd, &in_fds); if (!select(x11_fd + 1, &in_fds, 0, 0, &tv)) { - XSetSelectionOwner(dpy, sseln, None, CurrentTime); + XSetSelectionOwner(dpy, sseln, None, ownertime); xcmemzero(sel_buf,sel_len); return EXIT_SUCCESS; } @@ -536,7 +571,7 @@ doIn(Window win, const char *progname) start: - XNextEvent(dpy, &evt); + XNextEvent(dpy, &evt); /* Wait until next request comes in */ if (xcverb >= ODEBUG) fprintf(stderr, "\n"); @@ -548,31 +583,33 @@ doIn(Window win, const char *progname) switch (evt.type) { case SelectionRequest: - requestor_id = evt.xselectionrequest.requestor; - requestor = get_requestor(requestor_id); + requestor_win = evt.xselectionrequest.requestor; + requestor_pty = evt.xselectionrequest.property; + requestor = get_requestor(requestor_win, requestor_pty); /* FIXME: ICCCM 2.2: check evt.time and refuse requests from * outside the period of time we have owned the selection. */ break; case PropertyNotify: - requestor_id = evt.xproperty.window; - requestor = get_requestor(requestor_id); + requestor_win = evt.xproperty.window; + requestor_pty = evt.xproperty.atom; + requestor = get_requestor(requestor_win, requestor_pty); break; case SelectionClear: if (xcverb >= OVERBOSE) { fprintf(stderr, "Lost selection ownership. "); - requestor_id = XGetSelectionOwner(dpy, sseln); - if (requestor_id == None) + requestor_win = XGetSelectionOwner(dpy, sseln); + if (requestor_win == None) fprintf(stderr, "(Some other client cleared the selection).\n"); else - fprintf(stderr, "(%s did a copy).\n", xcnamestr(dpy, requestor_id) ); + fprintf(stderr, "(%s did a copy).\n", xcnamestr(dpy, requestor_win) ); } /* If the client loses ownership(SelectionClear event) * while it has a transfer in progress, it must continue to * service the ongoing transfer until it is completed. * See ICCCM section 2.2. */ - /* Set dloop to force exit after all transfers finish. */ - dloop = sloop; + /* Set sloop to force exit after all transfers finish. */ + sloop = -1; /* remove requestors for dead windows */ clean_requestors(); /* if there are no more in-progress transfers, force exit */ @@ -586,19 +623,22 @@ doIn(Window win, const char *progname) if (xcverb >= OVERBOSE) { struct requestor *r = requestors; int i=0; - fprintf(stderr, "Requestors: "); + fprintf(stderr, "Requestors still alive: "); while (r) { fprintf(stderr, "0x%lx\t", r->cwin); r = r->next; i++; } fprintf(stderr, "\n"); - fprintf(stderr, - "Still transferring data to %d requestor%s.\n", - i, (i==1)?"":"s"); + fprintf(stderr, "Will exit after transfering data to " + "%d requestor%s.\n", i, (i==1)?"":"s"); } } continue; /* Wait for INCR PropertyNotify events */ + case ClientMessage: + /* xchandler asks us to remove requestors for dead windows */ + clean_requestors(); + continue; default: /* Ignore all other event types */ if (xcverb >= ODEBUG) { @@ -611,14 +651,31 @@ doIn(Window win, const char *progname) if (xcverb >= ODEBUG) { fprintf(stderr, "xclip: debug: event was sent by %s\n", - xcnamestr(dpy, requestor_id) ); - requestor_id=0; + xcnamestr(dpy, requestor_win) ); + requestor_win=0; } - finished = xcin(dpy, &(requestor->cwin), evt, &(requestor->pty), + xcerrflag = False; + + finished = xcin(dpy, win, + &(requestor->cwin), evt, &(requestor->pty), target, sel_buf, sel_len, &(requestor->sel_pos), &(requestor->context), &(requestor->chunk_size)); + if (xcerrflag == True) { + if (xcerrevt.error_code == BadWindow) { + if (xcverb >= OVERBOSE) { + fprintf(stderr, + "Requestor window 0x%lx disappeared\n", + requestor->cwin); + } + if (xcverb >= ODEBUG) { + XmuPrintDefaultErrorMessage(dpy, &xcerrevt, stderr); + } + del_requestor(requestor); + break; + } + } if (finished) { del_requestor(requestor); break; @@ -632,7 +689,7 @@ doIn(Window win, const char *progname) dloop++; /* increment loop counter */ } - XSetSelectionOwner(dpy, sseln, None, CurrentTime); + XSetSelectionOwner(dpy, sseln, None, ownertime); xcmemzero(sel_buf,sel_len); return EXIT_SUCCESS; @@ -646,9 +703,7 @@ printSelBuf(FILE * fout, Atom sel_type, unsigned char *sel_buf, size_t sel_len) #endif if (xcverb >= OVERBOSE) { /* print in verbose mode only */ - char *atom_name = XGetAtomName(dpy, sel_type); - fprintf(stderr, "Type is %s.\n", atom_name); - XFree(atom_name); + fprintf(stderr, "Type is %s.\n", xcatomstr(dpy, sel_type)); } if (sel_type == XA_INTEGER) { @@ -665,9 +720,7 @@ printSelBuf(FILE * fout, Atom sel_type, unsigned char *sel_buf, size_t sel_len) Atom *atom_buf = (Atom *) sel_buf; size_t atom_len = sel_len / sizeof(Atom); while (atom_len--) { - char *atom_name = XGetAtomName(dpy, *atom_buf++); - fprintf(fout, "%s\n", atom_name); - XFree(atom_name); + fprintf(fout, "%s\n", xcatomstr(dpy, *atom_buf++)); } return; } @@ -719,6 +772,13 @@ doOut(Window win) XEvent evt; /* X Event Structures */ unsigned int context = XCLIB_XCOUT_NONE; + if (xcverb >= OVERBOSE) { + Window owner = XGetSelectionOwner(dpy, sseln); + fprintf(stderr, "Current owner of %s ", xcatomstr(dpy, sseln)); + fprintf(stderr, "is %s.\n", xcnamestr(dpy, owner)); + } + + /* Handle old-style cut buffer if needed */ if (sseln == XA_STRING) sel_buf = (unsigned char *) XFetchBuffer(dpy, (int *) &sel_len, 0); else { @@ -730,9 +790,18 @@ doOut(Window win) /* fetch the selection, or part of it */ xcout(dpy, win, evt, sseln, target, &sel_type, &sel_buf, &sel_len, &context); + if (context == XCLIB_XCOUT_SELECTION_REFUSED) { + fprintf(stderr, "xclip: error: selection owner signaled an error\n"); + return EXIT_FAILURE; + } + if (context == XCLIB_XCOUT_BAD_TARGET) { if (target == XA_UTF8_STRING(dpy)) { /* fallback is needed. set XA_STRING to target and restart the loop. */ + if (xcverb >= OVERBOSE) { + fprintf(stderr, "Target UTF8_STRING failed, " + "falling back to XA_STRING\n"); + } context = XCLIB_XCOUT_NONE; target = XA_STRING; continue; @@ -741,7 +810,7 @@ doOut(Window win) /* no fallback available, exit with failure */ if (fsecm) { /* If user requested -sensitive, then prevent further pastes (even though we failed to paste) */ - XSetSelectionOwner(dpy, sseln, None, CurrentTime); + XSetSelectionOwner(dpy, sseln, None, ownertime); /* Clear memory buffer */ xcmemzero(sel_buf,sel_len); } @@ -763,14 +832,12 @@ doOut(Window win) } if (sel_len) { - /* only print the buffer out, and free it, if it's not - * empty - */ + /* only print the buffer out, and free it, if it's not empty */ printSelBuf(stdout, sel_type, sel_buf, sel_len); if (fsecm) { /* If user requested -sensitive, then prevent further pastes */ - XSetSelectionOwner(dpy, sseln, None, CurrentTime); + XSetSelectionOwner(dpy, sseln, None, ownertime); /* Clear memory buffer */ xcmemzero(sel_buf,sel_len); } @@ -837,6 +904,11 @@ main(int argc, char *argv[]) opt_tab[i].argKind = XrmoptionNoArg; opt_tab[i].value = (XPointer) xcstrdup(ST); i++; + opt_tab[i].option = xcstrdup("-f"); /* Ensure -f always means filter */ + opt_tab[i].specifier = xcstrdup(".filter"); + opt_tab[i].argKind = XrmoptionNoArg; + opt_tab[i].value = (XPointer) xcstrdup(ST); + i++; /* in option entry */ opt_tab[i].option = xcstrdup("-in"); @@ -844,6 +916,11 @@ main(int argc, char *argv[]) opt_tab[i].argKind = XrmoptionNoArg; opt_tab[i].value = (XPointer) xcstrdup("I"); i++; + opt_tab[i].option = xcstrdup("-i"); /* Ensure -i always mean -in */ + opt_tab[i].specifier = xcstrdup(".direction"); + opt_tab[i].argKind = XrmoptionNoArg; + opt_tab[i].value = (XPointer) xcstrdup("I"); + i++; /* out option entry */ opt_tab[i].option = xcstrdup("-out"); @@ -851,6 +928,11 @@ main(int argc, char *argv[]) opt_tab[i].argKind = XrmoptionNoArg; opt_tab[i].value = (XPointer) xcstrdup("O"); i++; + opt_tab[i].option = xcstrdup("-o"); /* Ensure -o always means -out */ + opt_tab[i].specifier = xcstrdup(".direction"); + opt_tab[i].argKind = XrmoptionNoArg; + opt_tab[i].value = (XPointer) xcstrdup("O"); + i++; /* version option entry */ opt_tab[i].option = xcstrdup("-version"); @@ -858,6 +940,11 @@ main(int argc, char *argv[]) opt_tab[i].argKind = XrmoptionNoArg; opt_tab[i].value = (XPointer) xcstrdup("V"); i++; + opt_tab[i].option = xcstrdup("-V"); /* Allow capital -V for version */ + opt_tab[i].specifier = xcstrdup(".print"); + opt_tab[i].argKind = XrmoptionNoArg; + opt_tab[i].value = (XPointer) xcstrdup("V"); + i++; /* help option entry */ opt_tab[i].option = xcstrdup("-help"); @@ -886,6 +973,11 @@ main(int argc, char *argv[]) opt_tab[i].argKind = XrmoptionNoArg; opt_tab[i].value = (XPointer) xcstrdup("V"); i++; + opt_tab[i].option = xcstrdup("-v"); /* Ensure -v always means verbose */ + opt_tab[i].specifier = xcstrdup(".olevel"); + opt_tab[i].argKind = XrmoptionNoArg; + opt_tab[i].value = (XPointer) xcstrdup("V"); + i++; /* debug option entry */ opt_tab[i].option = xcstrdup("-debug"); @@ -907,6 +999,44 @@ main(int argc, char *argv[]) opt_tab[i].argKind = XrmoptionSepArg; opt_tab[i].value = (XPointer) NULL; i++; + opt_tab[i].option = xcstrdup("-t"); /* Ensure -t always means -target */ + opt_tab[i].specifier = xcstrdup(".target"); + opt_tab[i].argKind = XrmoptionSepArg; + opt_tab[i].value = (XPointer) NULL; + i++; + + /* -T is shorthand for "-target TARGETS" */ + opt_tab[i].option = xcstrdup("-T"); + opt_tab[i].specifier = xcstrdup(".TARGETS"); + opt_tab[i].argKind = XrmoptionNoArg; + opt_tab[i].value = (XPointer) xcstrdup("T"); + i++; + /* Allow -TARGETS instead of -T, though why would anyone do that? */ + opt_tab[i].option = xcstrdup("-TARGETS"); + opt_tab[i].specifier = xcstrdup(".TARGETS"); + opt_tab[i].argKind = XrmoptionNoArg; + opt_tab[i].value = (XPointer) xcstrdup("T"); + i++; + + /* -c is shorthand for "-selection clipboard" */ + opt_tab[i].option = xcstrdup("-c"); + opt_tab[i].specifier = xcstrdup(".selection"); + opt_tab[i].argKind = XrmoptionNoArg; + opt_tab[i].value = (XPointer) xcstrdup("clipboard"); + i++; + /* Allow -clipboard instead of -c, but why would anyone do that? */ + opt_tab[i].option = xcstrdup("-clipboard"); + opt_tab[i].specifier = xcstrdup(".selection"); + opt_tab[i].argKind = XrmoptionNoArg; + opt_tab[i].value = (XPointer) xcstrdup("clipboard"); + i++; + + /* Allow -b as synonym for -c for folks used to xsel */ + opt_tab[i].option = xcstrdup("-b"); + opt_tab[i].specifier = xcstrdup(".selection"); + opt_tab[i].argKind = XrmoptionNoArg; + opt_tab[i].value = (XPointer) xcstrdup("clipboard"); + i++; /* "remove newline if it is the last character" entry */ opt_tab[i].option = xcstrdup("-rmlastnl"); @@ -914,6 +1044,11 @@ main(int argc, char *argv[]) opt_tab[i].argKind = XrmoptionNoArg; opt_tab[i].value = (XPointer) xcstrdup(ST); i++; + opt_tab[i].option = xcstrdup("-r"); /* Ensure -r always means -rmlastnl */ + opt_tab[i].specifier = xcstrdup(".rmlastnl"); + opt_tab[i].argKind = XrmoptionNoArg; + opt_tab[i].value = (XPointer) xcstrdup(ST); + i++; /* sensitive mode for pasting passwords */ opt_tab[i].option = xcstrdup("-sensitive"); @@ -933,7 +1068,8 @@ main(int argc, char *argv[]) opt_tab_size = i; if ( ( sizeof(opt_tab) / sizeof(opt_tab[0]) ) < opt_tab_size ) { fprintf(stderr, - "xclip: programming error: opt_tab declared to hold %ld options, but %d defined\n", + "xclip: programming error: " + "opt_tab[] declared to hold %ld options, but %d defined\n", sizeof(opt_tab) / sizeof(opt_tab[0]), opt_tab_size); return EXIT_FAILURE; } @@ -953,15 +1089,22 @@ main(int argc, char *argv[]) errxdisplay(sdisp); } - /* parse selection command line option */ + /* parse selection command line option; sets sseln */ doOptSel(); - /* parse noutf8 and target command line options */ - doOptTarget(); + /* parse noutf8 and target command line options; sets target */ + if (sseln != XA_STRING) + doOptTarget(); + else + target = XA_STRING; /* Old-style cut buffer had no target */ /* Create a window to trap events */ win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0, 1, 1, 0, 0, 0); + if (xcverb >= ODEBUG) { + fprintf(stderr,"xclip: debug: Our window is %s\n",xcnamestr(dpy, win)); + } + /* get events about property changes */ XSelectInput(dpy, win, PropertyChangeMask); diff --git a/xclip.spec b/xclip.spec index 8637f0c..f534b91 100644 --- a/xclip.spec +++ b/xclip.spec @@ -1,5 +1,5 @@ Name: xclip -Version: 0.13 +Version: 0.14 Release: 1%{?dist} License: GPLv2+ Group: Applications/System diff --git a/xcprint.c b/xcprint.c index 5bce978..03cc8b7 100644 --- a/xcprint.c +++ b/xcprint.c @@ -3,7 +3,7 @@ * * xcprint.c - functions to print help, version, errors, etc * Copyright (C) 2001 Kim Saunders - * Copyright (C) 2007-2008 Peter Åstrand + * Copyright (C) 2007-2022 Peter Åstrand * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -62,7 +62,7 @@ void prversion(void) { fprintf(stderr, "%s version %s\n", PACKAGE_NAME, PACKAGE_VERSION); - fprintf(stderr, "Copyright (C) 2001-2008 Kim Saunders et al.\n"); + fprintf(stderr, "Copyright (C) 2001-2022 Kim Saunders et al.\n"); fprintf(stderr, "Distributed under the terms of the GNU GPL\n"); exit(EXIT_SUCCESS); } diff --git a/xctest b/xctest index 509c12f..1bda882 100755 --- a/xctest +++ b/xctest @@ -20,17 +20,31 @@ cleanup() { # quietly remove temp files - rm "$tempi" "$tempo" 2>/dev/null + rm "$tempi" "$tempo" 2>/dev/null || true # Kill any remaining xclip processes - killall xclip 2>/dev/null + killall xclip 2>/dev/null || true + return 0 } trap cleanup EXIT HUP INT +s() { [ "$1" != "1" ] && printf "s"; } # Plural(s) -if sleep 0.1 2>/dev/null; then - delay=0.1 # seconds to wait before running xclip -o +# If terminal can show bold and standout (reverse), do so. +if tput bold >/dev/null 2>&1; then + PASS="$(tput bold)PASS$(tput sgr0)" else + PASS=PASS +fi +if tput smso >/dev/null 2>&1; then + FAIL="$(tput smso)FAIL$(tput rmso)" +else + FAIL=FAIL +fi + + +delay=0.01 # seconds to wait before running xclip -o +if ! sleep $delay 2>/dev/null; then delay=1 fi @@ -48,105 +62,141 @@ for param in "$@"; do esac done -echo "Testing whether xclip exits correctly when the selection is lost" -echo "hello" | ./xclip -q -i 2>/dev/null & +echo -n "Testing whether xclip exits correctly when the selection is lost: " +echo hello | ./xclip -q -i 2>/dev/null & sleep "$delay" -echo "goodbye" | ./xclip -i +echo goodbye | ./xclip -i sleep "$delay" if ps $! >/dev/null; then - echo "FAIL: Zombie xclip yet lives! Killing." + echo "$FAIL" + echo "Zombie xclip yet lives! Killing." killall xclip exit 1 else - echo "PASS: xclip exited correctly after losing selection" + echo "$PASS" fi +echo +echo "Testing whether xclip recognizes the advertised shorthand options" +echo -n "Trying: " +for opt in i o f "v -silent" c T r "t STRING" h V ; do + echo -n "-$opt, " + if ! ./xclip -$opt -out 2>/dev/null | ./xclip -$opt -in >/dev/null 2>&1 + then + echo "$FAIL" + echo "./xclip -$opt returned an error." + killall xclip + exit 1 + fi +done +printf "\t$PASS\n" + +echo # temp file names (in and out) tempi=`mktemp` || exit 1 tempo=`mktemp` || exit 1 +echo -n "Testing whether xclip can specify target with -t: " +echo hello | ./xclip -t xctest/datatype & +sleep $delay +if ! ./xclip -t TARGETS | grep -q xctest/datatype; then + echo "$FAIL" + killall xclip + exit 1 +elif [ hello != "$(./xclip -t xctest/datatype)" ]; then + echo "$FAIL" + killall xclip + exit 1 +fi +echo "$PASS" + # test xclip on different amounts of data (2^fold) to bring out any errors -c=0 # Number of folds completed. -lines=1 # Current file size (2 to the c power). -printf '%s' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_ ' > "$tempi" -printf '%s\n' 'abcdefghijklmnopqrstuvwzyz!@#$%^&*()' >> "$tempi" -for fold in 1 4 7 10 13; do - # double size of file by two +c=0 # Number of folds completed. +lines=1 # Current file size (2 to the c power). +printf 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_+<>!@#$%%^&*() ' > "$tempi" +printf 'abcdefghijklmnopqrstuvwzyz-=,.1234567890\n' >> "$tempi" +for fold in 0 4 7 10 15; do + # double size of file by two (2 to the $fold power) while [ "$c" -lt "$fold" ]; do - cat "$tempi" "$tempi" > "$tempo" && mv "$tempo" "$tempi" - lines=$(( 2 * lines )) - c=$(( c + 1 )) + cat "$tempi" "$tempi" > "$tempo" && mv "$tempo" "$tempi" + lines=$(( 2 * lines )) + c=$(( c + 1 )) done - # test piping the file to xclip, using all selections - echo "Piping a $lines line file to xclip" - for sel in primary secondary clipboard buffer; do - printf '%s' " Using the $sel selection " - cat "$tempi" | $checker ./xclip -sel "$sel" -i - sleep "$delay" - $checker ./xclip -sel "$sel" -o > "$tempo" - if diff "$tempi" "$tempo"; then - echo "PASS" - else - echo "FAIL" + # test sending the file to xclip, using all selections + nlines=$(printf "%d line%s" "$lines" $(s "$lines")) + printf "\nSending $nlines of text to xclip:" + for sel in PRIMARY SECONDARY CLIPBOARD BUFFERCUT; do + printf "\n\t$sel: " + + printf "\tPipe " + cat "$tempi" | $checker ./xclip -sel "$sel" -i + sleep "$delay" + $checker ./xclip -sel $sel -o > $tempo + if ! diff $tempi $tempo; then + echo "$FAIL" + echo "Could not pipe $nlines of text into the $sel selection" + exit 1 + fi + printf "$PASS" + + # test xclip reading the file + printf "\tRead " + $checker ./xclip -sel $sel -i $tempi + sleep $delay + $checker ./xclip -sel $sel -o > $tempo + if ! diff $tempi $tempo; then + echo "$FAIL" + echo "Could not read a $lines-line file into the $sel selection" + exit 1 + fi + printf "$PASS" + + # test xclip filtering a file + printf "\tFilter " + $checker ./xclip -sel $sel -f < $tempi > $tempo + sleep "$delay" + if ! diff "$tempi" "$tempo"; then + echo "$FAIL" + echo "Could not filter $nlines of text through the $sel selection" exit 1 fi - done - echo + printf "$PASS" - # test xclip reading the file - echo "Reading a $lines line file with xclip" - for sel in primary secondary clipboard buffer; do - printf '%s' " Using the $sel selection " - $checker ./xclip -sel "$sel" -i "$tempi" - sleep "$delay" - $checker ./xclip -sel "$sel" -o > "$tempo" - if diff "$tempi" "$tempo"; then - echo "PASS" - else - echo "FAIL" - exit 1 - fi done echo - - # test xclip filtering a file - echo "Filtering a $lines line file through xclip" - for sel in primary secondary clipboard buffer; do - printf '%s' " Using the $sel selection " - $checker ./xclip -sel "$sel" -f < "$tempi" > "$tempo" - sleep "$delay" - if diff "$tempi" "$tempo"; then - echo "PASS" - else - echo "FAIL" - exit 1 - fi - done - echo - done +echo -# test xclip on files >1MB to force INCR mode -for i in 1 2 16; do +# test xclip on files >1MB to force INCR mode. +echo +printf "Binary files large enough to force INCR mode\n" +for i in 1 4 16; do dd if=/dev/zero bs=1024 count=$((i*1024)) of="$tempi" >/dev/null 2>&1 - echo "Binary file large enough to force INCR mode ($i MB)" - for sel in primary secondary clipboard buffer; do - printf '%s' " Using the $sel selection " - $checker ./xclip -sel "$sel" -i -t image/jpeg < "$tempi" - sleep "$delay" - $checker ./xclip -sel "$sel" -o -t image/jpeg > "$tempo" - if diff "$tempi" "$tempo"; then - echo "PASS" - else - echo "FAIL" + # Note: not testing "buffercut" because it cannot do INCR. + for sel in PRIMARY SECONDARY CLIPBOARD; do + printf "\t%10s ($i MiB)\t" $sel + $checker ./xclip -sel "$sel" -i -t image/jpeg < "$tempi" + sleep "$delay" + $checker ./xclip -sel "$sel" -o -t image/jpeg > "$tempo" + if ! diff "$tempi" "$tempo"; then + echo "$FAIL" + echo "xclip could not incrementally transfer $i MiB using the $sel selection" exit 1 fi + echo "$PASS" done + echo done -# Kill any remain xclip processes -killall xclip +echo +echo "All tests $PASS" # quietly remove temp files -rm "$tempi" "$tempo" 2>/dev/null +rm "$tempi" "$tempo" 2>/dev/null || true + +# Kill any remaining xclip processes +killall xclip || true + +# No errors +exit 0