|  | /* | 
|  | * This program is only intended for OS X, but the | 
|  | * ifdef __APPLE__ below lets us build it on all systems. | 
|  | * On non-OS X systems, you can use it to hold the snarf | 
|  | * buffer around after a program exits. | 
|  | */ | 
|  |  | 
|  | #include <u.h> | 
|  | #define Colormap	XColormap | 
|  | #define Cursor		XCursor | 
|  | #define Display		XDisplay | 
|  | #define Drawable	XDrawable | 
|  | #define Font		XFont | 
|  | #define GC		XGC | 
|  | #define Point		XPoint | 
|  | #define Rectangle	XRectangle | 
|  | #define Screen		XScreen | 
|  | #define Visual		XVisual | 
|  | #define Window		XWindow | 
|  | #include <X11/Xlib.h> | 
|  | #include <X11/Xatom.h> | 
|  | #include <X11/Xutil.h> | 
|  | #include <X11/keysym.h> | 
|  | #include <X11/IntrinsicP.h> | 
|  | #include <X11/StringDefs.h> | 
|  | #undef Colormap | 
|  | #undef Cursor | 
|  | #undef Display | 
|  | #undef Drawable | 
|  | #undef Font | 
|  | #undef GC | 
|  | #undef Point | 
|  | #undef Rectangle | 
|  | #undef Screen | 
|  | #undef Visual | 
|  | #undef Window | 
|  | AUTOLIB(X11); | 
|  | #ifdef __APPLE__ | 
|  | #define APPLESNARF | 
|  | #define Boolean AppleBoolean | 
|  | #define Rect AppleRect | 
|  | #define EventMask AppleEventMask | 
|  | #define Point ApplePoint | 
|  | #define Cursor AppleCursor | 
|  | #include <Carbon/Carbon.h> | 
|  | AUTOFRAMEWORK(Carbon) | 
|  | #undef Boolean | 
|  | #undef Rect | 
|  | #undef EventMask | 
|  | #undef Point | 
|  | #undef Cursor | 
|  | #endif | 
|  | #include <libc.h> | 
|  | #undef time | 
|  | AUTOLIB(draw)	/* to cause link of X11 */ | 
|  |  | 
|  | enum { | 
|  | SnarfSize = 65536 | 
|  | }; | 
|  | char snarf[3*SnarfSize+1]; | 
|  | Rune rsnarf[SnarfSize+1]; | 
|  | XDisplay *xdisplay; | 
|  | XWindow drawable; | 
|  | Atom xclipboard; | 
|  | Atom xutf8string; | 
|  | Atom xtargets; | 
|  | Atom xtext; | 
|  | Atom xcompoundtext; | 
|  |  | 
|  | void xselectionrequest(XEvent*); | 
|  | char *xgetsnarf(void); | 
|  | void appleputsnarf(void); | 
|  | void xputsnarf(void); | 
|  |  | 
|  | int verbose; | 
|  |  | 
|  | #ifdef __APPLE__ | 
|  | PasteboardRef appleclip; | 
|  | #endif | 
|  |  | 
|  | void | 
|  | usage(void) | 
|  | { | 
|  | fprint(2, "usage: snarfer [-v]\n"); | 
|  | exits("usage"); | 
|  | } | 
|  |  | 
|  | void | 
|  | main(int argc, char **argv) | 
|  | { | 
|  | XEvent xevent; | 
|  |  | 
|  | ARGBEGIN{ | 
|  | default: | 
|  | usage(); | 
|  | case 'v': | 
|  | verbose = 1; | 
|  | break; | 
|  | }ARGEND | 
|  |  | 
|  | if((xdisplay = XOpenDisplay(nil)) == nil) | 
|  | sysfatal("XOpenDisplay: %r"); | 
|  | drawable = XCreateWindow(xdisplay, DefaultRootWindow(xdisplay), | 
|  | 0, 0, 1, 1, 0, 0, | 
|  | InputOutput, DefaultVisual(xdisplay, DefaultScreen(xdisplay)), | 
|  | 0, 0); | 
|  | if(drawable == None) | 
|  | sysfatal("XCreateWindow: %r"); | 
|  | XFlush(xdisplay); | 
|  |  | 
|  | xclipboard = XInternAtom(xdisplay, "CLIPBOARD", False); | 
|  | xutf8string = XInternAtom(xdisplay, "UTF8_STRING", False); | 
|  | xtargets = XInternAtom(xdisplay, "TARGETS", False); | 
|  | xtext = XInternAtom(xdisplay, "TEXT", False); | 
|  | xcompoundtext = XInternAtom(xdisplay, "COMPOUND_TEXT", False); | 
|  |  | 
|  | #ifdef __APPLE__ | 
|  | if(PasteboardCreate(kPasteboardClipboard, &appleclip) != noErr) | 
|  | sysfatal("pasteboard create failed"); | 
|  | #endif | 
|  |  | 
|  | xgetsnarf(); | 
|  | appleputsnarf(); | 
|  | xputsnarf(); | 
|  |  | 
|  | for(;;){ | 
|  | XNextEvent(xdisplay, &xevent); | 
|  | switch(xevent.type){ | 
|  | case DestroyNotify: | 
|  | exits(0); | 
|  | case SelectionClear: | 
|  | xgetsnarf(); | 
|  | appleputsnarf(); | 
|  | xputsnarf(); | 
|  | if(verbose) | 
|  | print("snarf{%s}\n", snarf); | 
|  | break; | 
|  | case SelectionRequest: | 
|  | xselectionrequest(&xevent); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void | 
|  | xselectionrequest(XEvent *e) | 
|  | { | 
|  | char *name; | 
|  | Atom a[4]; | 
|  | XEvent r; | 
|  | XSelectionRequestEvent *xe; | 
|  | XDisplay *xd; | 
|  |  | 
|  | xd = xdisplay; | 
|  |  | 
|  | memset(&r, 0, sizeof r); | 
|  | xe = (XSelectionRequestEvent*)e; | 
|  | if(0) fprint(2, "xselect target=%d requestor=%d property=%d selection=%d\n", | 
|  | xe->target, xe->requestor, xe->property, xe->selection); | 
|  | r.xselection.property = xe->property; | 
|  | if(xe->target == xtargets){ | 
|  | a[0] = XA_STRING; | 
|  | a[1] = xutf8string; | 
|  | a[2] = xtext; | 
|  | a[3] = xcompoundtext; | 
|  |  | 
|  | XChangeProperty(xd, xe->requestor, xe->property, xe->target, | 
|  | 8, PropModeReplace, (uchar*)a, sizeof a); | 
|  | }else if(xe->target == XA_STRING || xe->target == xutf8string || xe->target == xtext || xe->target == xcompoundtext){ | 
|  | /* if the target is STRING we're supposed to reply with Latin1 XXX */ | 
|  | XChangeProperty(xd, xe->requestor, xe->property, xe->target, | 
|  | 8, PropModeReplace, (uchar*)snarf, strlen(snarf)); | 
|  | }else{ | 
|  | name = XGetAtomName(xd, xe->target); | 
|  | if(strcmp(name, "TIMESTAMP") != 0) | 
|  | fprint(2, "%s: cannot handle selection request for '%s' (%d)\n", argv0, name, (int)xe->target); | 
|  | r.xselection.property = None; | 
|  | } | 
|  |  | 
|  | r.xselection.display = xe->display; | 
|  | /* r.xselection.property filled above */ | 
|  | r.xselection.target = xe->target; | 
|  | r.xselection.type = SelectionNotify; | 
|  | r.xselection.requestor = xe->requestor; | 
|  | r.xselection.time = xe->time; | 
|  | r.xselection.send_event = True; | 
|  | r.xselection.selection = xe->selection; | 
|  | XSendEvent(xd, xe->requestor, False, 0, &r); | 
|  | XFlush(xd); | 
|  | } | 
|  |  | 
|  | char* | 
|  | xgetsnarf(void) | 
|  | { | 
|  | uchar *data, *xdata; | 
|  | Atom clipboard, type, prop; | 
|  | ulong len, lastlen, dummy; | 
|  | int fmt, i; | 
|  | XWindow w; | 
|  | XDisplay *xd; | 
|  |  | 
|  | xd = xdisplay; | 
|  |  | 
|  | w = None; | 
|  | clipboard = None; | 
|  |  | 
|  | /* | 
|  | * Is there a primary selection (highlighted text in an xterm)? | 
|  | */ | 
|  | if(0){ | 
|  | clipboard = XA_PRIMARY; | 
|  | w = XGetSelectionOwner(xd, XA_PRIMARY); | 
|  | if(w == drawable) | 
|  | return snarf; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * If not, is there a clipboard selection? | 
|  | */ | 
|  | if(w == None && xclipboard != None){ | 
|  | clipboard = xclipboard; | 
|  | w = XGetSelectionOwner(xd, xclipboard); | 
|  | if(w == drawable) | 
|  | return snarf; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * If not, give up. | 
|  | */ | 
|  | if(w == None) | 
|  | return nil; | 
|  |  | 
|  | /* | 
|  | * We should be waiting for SelectionNotify here, but it might never | 
|  | * come, and we have no way to time out.  Instead, we will clear | 
|  | * local property #1, request our buddy to fill it in for us, and poll | 
|  | * until he's done or we get tired of waiting. | 
|  | * | 
|  | * We should try to go for _x.utf8string instead of XA_STRING, | 
|  | * but that would add to the polling. | 
|  | */ | 
|  | prop = 1; | 
|  | XChangeProperty(xd, drawable, prop, XA_STRING, 8, PropModeReplace, (uchar*)"", 0); | 
|  | XConvertSelection(xd, clipboard, XA_STRING, prop, drawable, CurrentTime); | 
|  | XFlush(xd); | 
|  | lastlen = 0; | 
|  | for(i=0; i<10 || (lastlen!=0 && i<30); i++){ | 
|  | sleep(100); | 
|  | XGetWindowProperty(xd, drawable, prop, 0, 0, 0, AnyPropertyType, | 
|  | &type, &fmt, &dummy, &len, &data); | 
|  | if(lastlen == len && len > 0) | 
|  | break; | 
|  | lastlen = len; | 
|  | } | 
|  | if(i == 10) | 
|  | return nil; | 
|  | /* get the property */ | 
|  | data = nil; | 
|  | XGetWindowProperty(xd, drawable, prop, 0, SnarfSize/sizeof(ulong), 0, | 
|  | AnyPropertyType, &type, &fmt, &len, &dummy, &xdata); | 
|  | if(xdata == nil || (type != XA_STRING && type != xutf8string) || len == 0){ | 
|  | if(xdata) | 
|  | XFree(xdata); | 
|  | return nil; | 
|  | } | 
|  | if(strlen((char*)xdata) >= SnarfSize){ | 
|  | XFree(xdata); | 
|  | return nil; | 
|  | } | 
|  | strcpy(snarf, (char*)xdata); | 
|  | return snarf; | 
|  | } | 
|  |  | 
|  | void | 
|  | xputsnarf(void) | 
|  | { | 
|  | if(0) XSetSelectionOwner(xdisplay, XA_PRIMARY, drawable, CurrentTime); | 
|  | if(xclipboard != None) | 
|  | XSetSelectionOwner(xdisplay, xclipboard, drawable, CurrentTime); | 
|  | XFlush(xdisplay); | 
|  | } | 
|  |  | 
|  | void | 
|  | appleputsnarf(void) | 
|  | { | 
|  | #ifdef __APPLE__ | 
|  | CFDataRef cfdata; | 
|  | PasteboardSyncFlags flags; | 
|  |  | 
|  | runesnprint(rsnarf, nelem(rsnarf), "%s", snarf); | 
|  | if(PasteboardClear(appleclip) != noErr){ | 
|  | fprint(2, "apple pasteboard clear failed\n"); | 
|  | return; | 
|  | } | 
|  | flags = PasteboardSynchronize(appleclip); | 
|  | if((flags&kPasteboardModified) || !(flags&kPasteboardClientIsOwner)){ | 
|  | fprint(2, "apple pasteboard cannot assert ownership\n"); | 
|  | return; | 
|  | } | 
|  | cfdata = CFDataCreate(kCFAllocatorDefault, | 
|  | (uchar*)rsnarf, runestrlen(rsnarf)*2); | 
|  | if(cfdata == nil){ | 
|  | fprint(2, "apple pasteboard cfdatacreate failed\n"); | 
|  | return; | 
|  | } | 
|  | if(PasteboardPutItemFlavor(appleclip, (PasteboardItemID)1, | 
|  | CFSTR("public.utf16-plain-text"), cfdata, 0) != noErr){ | 
|  | fprint(2, "apple pasteboard putitem failed\n"); | 
|  | CFRelease(cfdata); | 
|  | return; | 
|  | } | 
|  | CFRelease(cfdata); | 
|  | #endif | 
|  | } | 
|  |  | 
|  |  |