| /* |
| * 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 |
| } |
| |
| |