snarfer.c (7227B)
1 /* 2 * This program is only intended for OS X, but the 3 * ifdef __APPLE__ below lets us build it on all systems. 4 * On non-OS X systems, you can use it to hold the snarf 5 * buffer around after a program exits. 6 */ 7 8 #include <u.h> 9 #define Colormap XColormap 10 #define Cursor XCursor 11 #define Display XDisplay 12 #define Drawable XDrawable 13 #define Font XFont 14 #define GC XGC 15 #define Point XPoint 16 #define Rectangle XRectangle 17 #define Screen XScreen 18 #define Visual XVisual 19 #define Window XWindow 20 #include <X11/Xlib.h> 21 #include <X11/Xatom.h> 22 #include <X11/Xutil.h> 23 #include <X11/keysym.h> 24 #include <X11/IntrinsicP.h> 25 #include <X11/StringDefs.h> 26 #undef Colormap 27 #undef Cursor 28 #undef Display 29 #undef Drawable 30 #undef Font 31 #undef GC 32 #undef Point 33 #undef Rectangle 34 #undef Screen 35 #undef Visual 36 #undef Window 37 AUTOLIB(X11); 38 #ifdef __APPLE__ 39 #define APPLESNARF 40 #define Boolean AppleBoolean 41 #define Rect AppleRect 42 #define EventMask AppleEventMask 43 #define Point ApplePoint 44 #define Cursor AppleCursor 45 #include <Carbon/Carbon.h> 46 AUTOFRAMEWORK(Carbon) 47 #undef Boolean 48 #undef Rect 49 #undef EventMask 50 #undef Point 51 #undef Cursor 52 #endif 53 #include <libc.h> 54 #undef time 55 AUTOLIB(draw) /* to cause link of X11 */ 56 57 enum { 58 SnarfSize = 65536 59 }; 60 char snarf[3*SnarfSize+1]; 61 Rune rsnarf[SnarfSize+1]; 62 XDisplay *xdisplay; 63 XWindow drawable; 64 Atom xclipboard; 65 Atom xutf8string; 66 Atom xtargets; 67 Atom xtext; 68 Atom xcompoundtext; 69 70 void xselectionrequest(XEvent*); 71 char *xgetsnarf(void); 72 void appleputsnarf(void); 73 void xputsnarf(void); 74 75 int verbose; 76 77 #ifdef __APPLE__ 78 PasteboardRef appleclip; 79 #endif 80 81 void 82 usage(void) 83 { 84 fprint(2, "usage: snarfer [-v]\n"); 85 exits("usage"); 86 } 87 88 void 89 main(int argc, char **argv) 90 { 91 XEvent xevent; 92 93 ARGBEGIN{ 94 default: 95 usage(); 96 case 'v': 97 verbose = 1; 98 break; 99 }ARGEND 100 101 if((xdisplay = XOpenDisplay(nil)) == nil) 102 sysfatal("XOpenDisplay: %r"); 103 drawable = XCreateWindow(xdisplay, DefaultRootWindow(xdisplay), 104 0, 0, 1, 1, 0, 0, 105 InputOutput, DefaultVisual(xdisplay, DefaultScreen(xdisplay)), 106 0, 0); 107 if(drawable == None) 108 sysfatal("XCreateWindow: %r"); 109 XFlush(xdisplay); 110 111 xclipboard = XInternAtom(xdisplay, "CLIPBOARD", False); 112 xutf8string = XInternAtom(xdisplay, "UTF8_STRING", False); 113 xtargets = XInternAtom(xdisplay, "TARGETS", False); 114 xtext = XInternAtom(xdisplay, "TEXT", False); 115 xcompoundtext = XInternAtom(xdisplay, "COMPOUND_TEXT", False); 116 117 #ifdef __APPLE__ 118 if(PasteboardCreate(kPasteboardClipboard, &appleclip) != noErr) 119 sysfatal("pasteboard create failed"); 120 #endif 121 122 xgetsnarf(); 123 appleputsnarf(); 124 xputsnarf(); 125 126 for(;;){ 127 XNextEvent(xdisplay, &xevent); 128 switch(xevent.type){ 129 case DestroyNotify: 130 exits(0); 131 case SelectionClear: 132 xgetsnarf(); 133 appleputsnarf(); 134 xputsnarf(); 135 if(verbose) 136 print("snarf{%s}\n", snarf); 137 break; 138 case SelectionRequest: 139 xselectionrequest(&xevent); 140 break; 141 } 142 } 143 } 144 145 void 146 xselectionrequest(XEvent *e) 147 { 148 char *name; 149 Atom a[4]; 150 XEvent r; 151 XSelectionRequestEvent *xe; 152 XDisplay *xd; 153 154 xd = xdisplay; 155 156 memset(&r, 0, sizeof r); 157 xe = (XSelectionRequestEvent*)e; 158 if(0) fprint(2, "xselect target=%d requestor=%d property=%d selection=%d\n", 159 xe->target, xe->requestor, xe->property, xe->selection); 160 r.xselection.property = xe->property; 161 if(xe->target == xtargets){ 162 a[0] = XA_STRING; 163 a[1] = xutf8string; 164 a[2] = xtext; 165 a[3] = xcompoundtext; 166 167 XChangeProperty(xd, xe->requestor, xe->property, xe->target, 168 8, PropModeReplace, (uchar*)a, sizeof a); 169 }else if(xe->target == XA_STRING || xe->target == xutf8string || xe->target == xtext || xe->target == xcompoundtext){ 170 /* if the target is STRING we're supposed to reply with Latin1 XXX */ 171 XChangeProperty(xd, xe->requestor, xe->property, xe->target, 172 8, PropModeReplace, (uchar*)snarf, strlen(snarf)); 173 }else{ 174 name = XGetAtomName(xd, xe->target); 175 if(strcmp(name, "TIMESTAMP") != 0) 176 fprint(2, "%s: cannot handle selection request for '%s' (%d)\n", argv0, name, (int)xe->target); 177 r.xselection.property = None; 178 } 179 180 r.xselection.display = xe->display; 181 /* r.xselection.property filled above */ 182 r.xselection.target = xe->target; 183 r.xselection.type = SelectionNotify; 184 r.xselection.requestor = xe->requestor; 185 r.xselection.time = xe->time; 186 r.xselection.send_event = True; 187 r.xselection.selection = xe->selection; 188 XSendEvent(xd, xe->requestor, False, 0, &r); 189 XFlush(xd); 190 } 191 192 char* 193 xgetsnarf(void) 194 { 195 uchar *data, *xdata; 196 Atom clipboard, type, prop; 197 ulong len, lastlen, dummy; 198 int fmt, i; 199 XWindow w; 200 XDisplay *xd; 201 202 xd = xdisplay; 203 204 w = None; 205 clipboard = None; 206 207 /* 208 * Is there a primary selection (highlighted text in an xterm)? 209 */ 210 if(0){ 211 clipboard = XA_PRIMARY; 212 w = XGetSelectionOwner(xd, XA_PRIMARY); 213 if(w == drawable) 214 return snarf; 215 } 216 217 /* 218 * If not, is there a clipboard selection? 219 */ 220 if(w == None && xclipboard != None){ 221 clipboard = xclipboard; 222 w = XGetSelectionOwner(xd, xclipboard); 223 if(w == drawable) 224 return snarf; 225 } 226 227 /* 228 * If not, give up. 229 */ 230 if(w == None) 231 return nil; 232 233 /* 234 * We should be waiting for SelectionNotify here, but it might never 235 * come, and we have no way to time out. Instead, we will clear 236 * local property #1, request our buddy to fill it in for us, and poll 237 * until he's done or we get tired of waiting. 238 * 239 * We should try to go for _x.utf8string instead of XA_STRING, 240 * but that would add to the polling. 241 */ 242 prop = 1; 243 XChangeProperty(xd, drawable, prop, XA_STRING, 8, PropModeReplace, (uchar*)"", 0); 244 XConvertSelection(xd, clipboard, XA_STRING, prop, drawable, CurrentTime); 245 XFlush(xd); 246 lastlen = 0; 247 for(i=0; i<10 || (lastlen!=0 && i<30); i++){ 248 sleep(100); 249 XGetWindowProperty(xd, drawable, prop, 0, 0, 0, AnyPropertyType, 250 &type, &fmt, &dummy, &len, &data); 251 if(lastlen == len && len > 0) 252 break; 253 lastlen = len; 254 } 255 if(i == 10) 256 return nil; 257 /* get the property */ 258 data = nil; 259 XGetWindowProperty(xd, drawable, prop, 0, SnarfSize/sizeof(ulong), 0, 260 AnyPropertyType, &type, &fmt, &len, &dummy, &xdata); 261 if(xdata == nil || (type != XA_STRING && type != xutf8string) || len == 0){ 262 if(xdata) 263 XFree(xdata); 264 return nil; 265 } 266 if(strlen((char*)xdata) >= SnarfSize){ 267 XFree(xdata); 268 return nil; 269 } 270 strcpy(snarf, (char*)xdata); 271 return snarf; 272 } 273 274 void 275 xputsnarf(void) 276 { 277 if(0) XSetSelectionOwner(xdisplay, XA_PRIMARY, drawable, CurrentTime); 278 if(xclipboard != None) 279 XSetSelectionOwner(xdisplay, xclipboard, drawable, CurrentTime); 280 XFlush(xdisplay); 281 } 282 283 void 284 appleputsnarf(void) 285 { 286 #ifdef __APPLE__ 287 CFDataRef cfdata; 288 PasteboardSyncFlags flags; 289 290 runesnprint(rsnarf, nelem(rsnarf), "%s", snarf); 291 if(PasteboardClear(appleclip) != noErr){ 292 fprint(2, "apple pasteboard clear failed\n"); 293 return; 294 } 295 flags = PasteboardSynchronize(appleclip); 296 if((flags&kPasteboardModified) || !(flags&kPasteboardClientIsOwner)){ 297 fprint(2, "apple pasteboard cannot assert ownership\n"); 298 return; 299 } 300 cfdata = CFDataCreate(kCFAllocatorDefault, 301 (uchar*)rsnarf, runestrlen(rsnarf)*2); 302 if(cfdata == nil){ 303 fprint(2, "apple pasteboard cfdatacreate failed\n"); 304 return; 305 } 306 if(PasteboardPutItemFlavor(appleclip, (PasteboardItemID)1, 307 CFSTR("public.utf16-plain-text"), cfdata, 0) != noErr){ 308 fprint(2, "apple pasteboard putitem failed\n"); 309 CFRelease(cfdata); 310 return; 311 } 312 CFRelease(cfdata); 313 #endif 314 }