1*4882a593Smuzhiyun/* x-selection.m -- proxies between NSPasteboard and X11 selections 2*4882a593Smuzhiyun * 3*4882a593Smuzhiyun * Copyright (c) 2002-2012 Apple Inc. All rights reserved. 4*4882a593Smuzhiyun * 5*4882a593Smuzhiyun * Permission is hereby granted, free of charge, to any person 6*4882a593Smuzhiyun * obtaining a copy of this software and associated documentation files 7*4882a593Smuzhiyun * (the "Software"), to deal in the Software without restriction, 8*4882a593Smuzhiyun * including without limitation the rights to use, copy, modify, merge, 9*4882a593Smuzhiyun * publish, distribute, sublicense, and/or sell copies of the Software, 10*4882a593Smuzhiyun * and to permit persons to whom the Software is furnished to do so, 11*4882a593Smuzhiyun * subject to the following conditions: 12*4882a593Smuzhiyun * 13*4882a593Smuzhiyun * The above copyright notice and this permission notice shall be 14*4882a593Smuzhiyun * included in all copies or substantial portions of the Software. 15*4882a593Smuzhiyun * 16*4882a593Smuzhiyun * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17*4882a593Smuzhiyun * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18*4882a593Smuzhiyun * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19*4882a593Smuzhiyun * NONINFRINGEMENT. IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT 20*4882a593Smuzhiyun * HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21*4882a593Smuzhiyun * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22*4882a593Smuzhiyun * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23*4882a593Smuzhiyun * DEALINGS IN THE SOFTWARE. 24*4882a593Smuzhiyun * 25*4882a593Smuzhiyun * Except as contained in this notice, the name(s) of the above 26*4882a593Smuzhiyun * copyright holders shall not be used in advertising or otherwise to 27*4882a593Smuzhiyun * promote the sale, use or other dealings in this Software without 28*4882a593Smuzhiyun * prior written authorization. 29*4882a593Smuzhiyun */ 30*4882a593Smuzhiyun 31*4882a593Smuzhiyun#import "x-selection.h" 32*4882a593Smuzhiyun 33*4882a593Smuzhiyun#include <stdio.h> 34*4882a593Smuzhiyun#include <stdlib.h> 35*4882a593Smuzhiyun#include <X11/Xatom.h> 36*4882a593Smuzhiyun#include <X11/Xutil.h> 37*4882a593Smuzhiyun#import <AppKit/NSGraphics.h> 38*4882a593Smuzhiyun#import <AppKit/NSImage.h> 39*4882a593Smuzhiyun#import <AppKit/NSBitmapImageRep.h> 40*4882a593Smuzhiyun 41*4882a593Smuzhiyun/* 42*4882a593Smuzhiyun * The basic design of the pbproxy code is as follows. 43*4882a593Smuzhiyun * 44*4882a593Smuzhiyun * When a client selects text, say from an xterm - we only copy it when the 45*4882a593Smuzhiyun * X11 Edit->Copy menu item is pressed or the shortcut activated. In this 46*4882a593Smuzhiyun * case we take the PRIMARY selection, and set it as the NSPasteboard data. 47*4882a593Smuzhiyun * 48*4882a593Smuzhiyun * When an X11 client copies something to the CLIPBOARD, pbproxy greedily grabs 49*4882a593Smuzhiyun * the data, sets it as the NSPasteboard data, and finally sets itself as 50*4882a593Smuzhiyun * owner of the CLIPBOARD. 51*4882a593Smuzhiyun * 52*4882a593Smuzhiyun * When an X11 window is activated we check to see if the NSPasteboard has 53*4882a593Smuzhiyun * changed. If the NSPasteboard has changed, then we set pbproxy as owner 54*4882a593Smuzhiyun * of the PRIMARY and CLIPBOARD and respond to requests for text and images. 55*4882a593Smuzhiyun * 56*4882a593Smuzhiyun * The behavior is now dynamic since the information above was written. 57*4882a593Smuzhiyun * The behavior is now dependent on the pbproxy_prefs below. 58*4882a593Smuzhiyun */ 59*4882a593Smuzhiyun 60*4882a593Smuzhiyun/* 61*4882a593Smuzhiyun * TODO: 62*4882a593Smuzhiyun * 1. handle MULTIPLE - I need to study the ICCCM further, and find a test app. 63*4882a593Smuzhiyun * 2. Handle NSPasteboard updates immediately, not on active/inactive 64*4882a593Smuzhiyun * - Open xterm, run 'cat readme.txt | pbcopy' 65*4882a593Smuzhiyun */ 66*4882a593Smuzhiyun 67*4882a593Smuzhiyunstatic struct { 68*4882a593Smuzhiyun BOOL active; 69*4882a593Smuzhiyun BOOL primary_on_grab; /* This is provided as an option for people who 70*4882a593Smuzhiyun * want it and has issues that won't ever be 71*4882a593Smuzhiyun * addressed to make it *always* work. 72*4882a593Smuzhiyun */ 73*4882a593Smuzhiyun BOOL clipboard_to_pasteboard; 74*4882a593Smuzhiyun BOOL pasteboard_to_primary; 75*4882a593Smuzhiyun BOOL pasteboard_to_clipboard; 76*4882a593Smuzhiyun} pbproxy_prefs = { YES, NO, YES, YES, YES }; 77*4882a593Smuzhiyun 78*4882a593Smuzhiyun@implementation x_selection 79*4882a593Smuzhiyun 80*4882a593Smuzhiyunstatic struct propdata null_propdata = { 81*4882a593Smuzhiyun NULL, 0, 0 82*4882a593Smuzhiyun}; 83*4882a593Smuzhiyun 84*4882a593Smuzhiyun#ifdef DEBUG 85*4882a593Smuzhiyunstatic void 86*4882a593Smuzhiyundump_prefs() 87*4882a593Smuzhiyun{ 88*4882a593Smuzhiyun ErrorF("pbproxy preferences:\n" 89*4882a593Smuzhiyun "\tactive %u\n" 90*4882a593Smuzhiyun "\tprimary_on_grab %u\n" 91*4882a593Smuzhiyun "\tclipboard_to_pasteboard %u\n" 92*4882a593Smuzhiyun "\tpasteboard_to_primary %u\n" 93*4882a593Smuzhiyun "\tpasteboard_to_clipboard %u\n", 94*4882a593Smuzhiyun pbproxy_prefs.active, 95*4882a593Smuzhiyun pbproxy_prefs.primary_on_grab, 96*4882a593Smuzhiyun pbproxy_prefs.clipboard_to_pasteboard, 97*4882a593Smuzhiyun pbproxy_prefs.pasteboard_to_primary, 98*4882a593Smuzhiyun pbproxy_prefs.pasteboard_to_clipboard); 99*4882a593Smuzhiyun} 100*4882a593Smuzhiyun#endif 101*4882a593Smuzhiyun 102*4882a593Smuzhiyunextern CFStringRef app_prefs_domain_cfstr; 103*4882a593Smuzhiyun 104*4882a593Smuzhiyunstatic BOOL 105*4882a593Smuzhiyunprefs_get_bool(CFStringRef key, BOOL defaultValue) 106*4882a593Smuzhiyun{ 107*4882a593Smuzhiyun Boolean value, ok; 108*4882a593Smuzhiyun 109*4882a593Smuzhiyun value = CFPreferencesGetAppBooleanValue(key, app_prefs_domain_cfstr, &ok); 110*4882a593Smuzhiyun 111*4882a593Smuzhiyun return ok ? (BOOL)value : defaultValue; 112*4882a593Smuzhiyun} 113*4882a593Smuzhiyun 114*4882a593Smuzhiyunstatic void 115*4882a593Smuzhiyuninit_propdata(struct propdata *pdata) 116*4882a593Smuzhiyun{ 117*4882a593Smuzhiyun *pdata = null_propdata; 118*4882a593Smuzhiyun} 119*4882a593Smuzhiyun 120*4882a593Smuzhiyunstatic void 121*4882a593Smuzhiyunfree_propdata(struct propdata *pdata) 122*4882a593Smuzhiyun{ 123*4882a593Smuzhiyun free(pdata->data); 124*4882a593Smuzhiyun *pdata = null_propdata; 125*4882a593Smuzhiyun} 126*4882a593Smuzhiyun 127*4882a593Smuzhiyun/* 128*4882a593Smuzhiyun * Return True if an error occurs. Return False if pdata has data 129*4882a593Smuzhiyun * and we finished. 130*4882a593Smuzhiyun * The property is only deleted when bytesleft is 0 if delete is True. 131*4882a593Smuzhiyun */ 132*4882a593Smuzhiyunstatic Bool 133*4882a593Smuzhiyunget_property(Window win, Atom property, struct propdata *pdata, Bool delete, 134*4882a593Smuzhiyun Atom *type) 135*4882a593Smuzhiyun{ 136*4882a593Smuzhiyun long offset = 0; 137*4882a593Smuzhiyun unsigned long numitems, bytesleft = 0; 138*4882a593Smuzhiyun#ifdef TEST 139*4882a593Smuzhiyun /* This is used to test the growth handling. */ 140*4882a593Smuzhiyun unsigned long length = 4UL; 141*4882a593Smuzhiyun#else 142*4882a593Smuzhiyun unsigned long length = (100000UL + 3) / 4; 143*4882a593Smuzhiyun#endif 144*4882a593Smuzhiyun unsigned char *buf = NULL, *chunk = NULL; 145*4882a593Smuzhiyun size_t buflen = 0, chunkbytesize = 0; 146*4882a593Smuzhiyun int format; 147*4882a593Smuzhiyun 148*4882a593Smuzhiyun TRACE(); 149*4882a593Smuzhiyun 150*4882a593Smuzhiyun if (None == property) 151*4882a593Smuzhiyun return True; 152*4882a593Smuzhiyun 153*4882a593Smuzhiyun do { 154*4882a593Smuzhiyun unsigned long newbuflen = 0; 155*4882a593Smuzhiyun unsigned char *newbuf = NULL; 156*4882a593Smuzhiyun 157*4882a593Smuzhiyun#ifdef TEST 158*4882a593Smuzhiyun ErrorF("bytesleft %lu\n", bytesleft); 159*4882a593Smuzhiyun#endif 160*4882a593Smuzhiyun 161*4882a593Smuzhiyun if (Success != XGetWindowProperty(xpbproxy_dpy, win, property, 162*4882a593Smuzhiyun offset, length, delete, 163*4882a593Smuzhiyun AnyPropertyType, 164*4882a593Smuzhiyun type, &format, &numitems, 165*4882a593Smuzhiyun &bytesleft, &chunk)) { 166*4882a593Smuzhiyun DebugF("Error while getting window property.\n"); 167*4882a593Smuzhiyun *pdata = null_propdata; 168*4882a593Smuzhiyun free(buf); 169*4882a593Smuzhiyun return True; 170*4882a593Smuzhiyun } 171*4882a593Smuzhiyun 172*4882a593Smuzhiyun#ifdef TEST 173*4882a593Smuzhiyun ErrorF("format %d numitems %lu bytesleft %lu\n", 174*4882a593Smuzhiyun format, numitems, bytesleft); 175*4882a593Smuzhiyun 176*4882a593Smuzhiyun ErrorF("type %s\n", XGetAtomName(xpbproxy_dpy, *type)); 177*4882a593Smuzhiyun#endif 178*4882a593Smuzhiyun 179*4882a593Smuzhiyun /* Format is the number of bits. */ 180*4882a593Smuzhiyun if (format == 8) 181*4882a593Smuzhiyun chunkbytesize = numitems; 182*4882a593Smuzhiyun else if (format == 16) 183*4882a593Smuzhiyun chunkbytesize = numitems * sizeof(short); 184*4882a593Smuzhiyun else if (format == 32) 185*4882a593Smuzhiyun chunkbytesize = numitems * sizeof(long); 186*4882a593Smuzhiyun 187*4882a593Smuzhiyun#ifdef TEST 188*4882a593Smuzhiyun ErrorF("chunkbytesize %zu\n", chunkbytesize); 189*4882a593Smuzhiyun#endif 190*4882a593Smuzhiyun newbuflen = buflen + chunkbytesize; 191*4882a593Smuzhiyun if (newbuflen > 0) { 192*4882a593Smuzhiyun newbuf = realloc(buf, newbuflen); 193*4882a593Smuzhiyun 194*4882a593Smuzhiyun if (NULL == newbuf) { 195*4882a593Smuzhiyun XFree(chunk); 196*4882a593Smuzhiyun free(buf); 197*4882a593Smuzhiyun return True; 198*4882a593Smuzhiyun } 199*4882a593Smuzhiyun 200*4882a593Smuzhiyun memcpy(newbuf + buflen, chunk, chunkbytesize); 201*4882a593Smuzhiyun XFree(chunk); 202*4882a593Smuzhiyun buf = newbuf; 203*4882a593Smuzhiyun buflen = newbuflen; 204*4882a593Smuzhiyun /* offset is a multiple of 32 bits*/ 205*4882a593Smuzhiyun offset += chunkbytesize / 4; 206*4882a593Smuzhiyun } 207*4882a593Smuzhiyun else { 208*4882a593Smuzhiyun if (chunk) 209*4882a593Smuzhiyun XFree(chunk); 210*4882a593Smuzhiyun } 211*4882a593Smuzhiyun 212*4882a593Smuzhiyun#ifdef TEST 213*4882a593Smuzhiyun ErrorF("bytesleft %lu\n", bytesleft); 214*4882a593Smuzhiyun#endif 215*4882a593Smuzhiyun } while (bytesleft > 0); 216*4882a593Smuzhiyun 217*4882a593Smuzhiyun pdata->data = buf; 218*4882a593Smuzhiyun pdata->length = buflen; 219*4882a593Smuzhiyun pdata->format = format; 220*4882a593Smuzhiyun 221*4882a593Smuzhiyun return /*success*/ False; 222*4882a593Smuzhiyun} 223*4882a593Smuzhiyun 224*4882a593Smuzhiyun/* Implementation methods */ 225*4882a593Smuzhiyun 226*4882a593Smuzhiyun/* This finds the preferred type from a TARGETS list.*/ 227*4882a593Smuzhiyun- (Atom) find_preferred:(struct propdata *)pdata 228*4882a593Smuzhiyun{ 229*4882a593Smuzhiyun Atom a = None; 230*4882a593Smuzhiyun size_t i, step; 231*4882a593Smuzhiyun Bool png = False, jpeg = False, utf8 = False, string = False; 232*4882a593Smuzhiyun 233*4882a593Smuzhiyun TRACE(); 234*4882a593Smuzhiyun 235*4882a593Smuzhiyun if (pdata->format != 32) { 236*4882a593Smuzhiyun ErrorF( 237*4882a593Smuzhiyun "Atom list is expected to be formatted as an array of 32bit values.\n"); 238*4882a593Smuzhiyun return None; 239*4882a593Smuzhiyun } 240*4882a593Smuzhiyun 241*4882a593Smuzhiyun for (i = 0, step = sizeof(long); i < pdata->length; i += step) { 242*4882a593Smuzhiyun a = (Atom) * (long *)(pdata->data + i); 243*4882a593Smuzhiyun 244*4882a593Smuzhiyun if (a == atoms->image_png) { 245*4882a593Smuzhiyun png = True; 246*4882a593Smuzhiyun } 247*4882a593Smuzhiyun else if (a == atoms->image_jpeg) { 248*4882a593Smuzhiyun jpeg = True; 249*4882a593Smuzhiyun } 250*4882a593Smuzhiyun else if (a == atoms->utf8_string) { 251*4882a593Smuzhiyun utf8 = True; 252*4882a593Smuzhiyun } 253*4882a593Smuzhiyun else if (a == atoms->string) { 254*4882a593Smuzhiyun string = True; 255*4882a593Smuzhiyun } 256*4882a593Smuzhiyun else { 257*4882a593Smuzhiyun char *type = XGetAtomName(xpbproxy_dpy, a); 258*4882a593Smuzhiyun if (type) { 259*4882a593Smuzhiyun DebugF("Unhandled X11 mime type: %s", type); 260*4882a593Smuzhiyun XFree(type); 261*4882a593Smuzhiyun } 262*4882a593Smuzhiyun } 263*4882a593Smuzhiyun } 264*4882a593Smuzhiyun 265*4882a593Smuzhiyun /*We prefer PNG over strings, and UTF8 over a Latin-1 string.*/ 266*4882a593Smuzhiyun if (png) 267*4882a593Smuzhiyun return atoms->image_png; 268*4882a593Smuzhiyun 269*4882a593Smuzhiyun if (jpeg) 270*4882a593Smuzhiyun return atoms->image_jpeg; 271*4882a593Smuzhiyun 272*4882a593Smuzhiyun if (utf8) 273*4882a593Smuzhiyun return atoms->utf8_string; 274*4882a593Smuzhiyun 275*4882a593Smuzhiyun if (string) 276*4882a593Smuzhiyun return atoms->string; 277*4882a593Smuzhiyun 278*4882a593Smuzhiyun /* This is evidently something we don't know how to handle.*/ 279*4882a593Smuzhiyun return None; 280*4882a593Smuzhiyun} 281*4882a593Smuzhiyun 282*4882a593Smuzhiyun/* Return True if this is an INCR-style transfer. */ 283*4882a593Smuzhiyun- (Bool) is_incr_type:(XSelectionEvent *)e 284*4882a593Smuzhiyun{ 285*4882a593Smuzhiyun Atom seltype; 286*4882a593Smuzhiyun int format; 287*4882a593Smuzhiyun unsigned long numitems = 0UL, bytesleft = 0UL; 288*4882a593Smuzhiyun unsigned char *chunk; 289*4882a593Smuzhiyun 290*4882a593Smuzhiyun TRACE(); 291*4882a593Smuzhiyun 292*4882a593Smuzhiyun if (Success != XGetWindowProperty(xpbproxy_dpy, e->requestor, e->property, 293*4882a593Smuzhiyun /*offset*/ 0L, /*length*/ 4UL, 294*4882a593Smuzhiyun /*Delete*/ False, 295*4882a593Smuzhiyun AnyPropertyType, &seltype, &format, 296*4882a593Smuzhiyun &numitems, &bytesleft, &chunk)) { 297*4882a593Smuzhiyun return False; 298*4882a593Smuzhiyun } 299*4882a593Smuzhiyun 300*4882a593Smuzhiyun if (chunk) 301*4882a593Smuzhiyun XFree(chunk); 302*4882a593Smuzhiyun 303*4882a593Smuzhiyun return (seltype == atoms->incr) ? True : False; 304*4882a593Smuzhiyun} 305*4882a593Smuzhiyun 306*4882a593Smuzhiyun/* 307*4882a593Smuzhiyun * This should be called after a selection has been copied, 308*4882a593Smuzhiyun * or when the selection is unfinished before a transfer completes. 309*4882a593Smuzhiyun */ 310*4882a593Smuzhiyun- (void) release_pending 311*4882a593Smuzhiyun{ 312*4882a593Smuzhiyun TRACE(); 313*4882a593Smuzhiyun 314*4882a593Smuzhiyun free_propdata(&pending.propdata); 315*4882a593Smuzhiyun pending.requestor = None; 316*4882a593Smuzhiyun pending.selection = None; 317*4882a593Smuzhiyun} 318*4882a593Smuzhiyun 319*4882a593Smuzhiyun/* Return True if an error occurs during an append.*/ 320*4882a593Smuzhiyun/* Return False if the append succeeds. */ 321*4882a593Smuzhiyun- (Bool) append_to_pending:(struct propdata *)pdata requestor:(Window) 322*4882a593Smuzhiyun requestor 323*4882a593Smuzhiyun{ 324*4882a593Smuzhiyun unsigned char *newdata; 325*4882a593Smuzhiyun size_t newlength; 326*4882a593Smuzhiyun 327*4882a593Smuzhiyun TRACE(); 328*4882a593Smuzhiyun 329*4882a593Smuzhiyun if (requestor != pending.requestor) { 330*4882a593Smuzhiyun [self release_pending]; 331*4882a593Smuzhiyun pending.requestor = requestor; 332*4882a593Smuzhiyun } 333*4882a593Smuzhiyun 334*4882a593Smuzhiyun newlength = pending.propdata.length + pdata->length; 335*4882a593Smuzhiyun newdata = realloc(pending.propdata.data, newlength); 336*4882a593Smuzhiyun 337*4882a593Smuzhiyun if (NULL == newdata) { 338*4882a593Smuzhiyun perror("realloc propdata"); 339*4882a593Smuzhiyun [self release_pending]; 340*4882a593Smuzhiyun return True; 341*4882a593Smuzhiyun } 342*4882a593Smuzhiyun 343*4882a593Smuzhiyun memcpy(newdata + pending.propdata.length, pdata->data, pdata->length); 344*4882a593Smuzhiyun pending.propdata.data = newdata; 345*4882a593Smuzhiyun pending.propdata.length = newlength; 346*4882a593Smuzhiyun 347*4882a593Smuzhiyun return False; 348*4882a593Smuzhiyun} 349*4882a593Smuzhiyun 350*4882a593Smuzhiyun/* Called when X11 becomes active (i.e. has key focus) */ 351*4882a593Smuzhiyun- (void) x_active:(Time)timestamp 352*4882a593Smuzhiyun{ 353*4882a593Smuzhiyun static NSInteger changeCount; 354*4882a593Smuzhiyun NSInteger countNow; 355*4882a593Smuzhiyun NSPasteboard *pb; 356*4882a593Smuzhiyun 357*4882a593Smuzhiyun TRACE(); 358*4882a593Smuzhiyun 359*4882a593Smuzhiyun pb = [NSPasteboard generalPasteboard]; 360*4882a593Smuzhiyun 361*4882a593Smuzhiyun if (nil == pb) 362*4882a593Smuzhiyun return; 363*4882a593Smuzhiyun 364*4882a593Smuzhiyun countNow = [pb changeCount]; 365*4882a593Smuzhiyun 366*4882a593Smuzhiyun if (countNow != changeCount) { 367*4882a593Smuzhiyun DebugF("changed pasteboard!\n"); 368*4882a593Smuzhiyun changeCount = countNow; 369*4882a593Smuzhiyun 370*4882a593Smuzhiyun if (pbproxy_prefs.pasteboard_to_primary) { 371*4882a593Smuzhiyun XSetSelectionOwner(xpbproxy_dpy, atoms->primary, 372*4882a593Smuzhiyun _selection_window, 373*4882a593Smuzhiyun CurrentTime); 374*4882a593Smuzhiyun } 375*4882a593Smuzhiyun 376*4882a593Smuzhiyun if (pbproxy_prefs.pasteboard_to_clipboard) { 377*4882a593Smuzhiyun [self own_clipboard]; 378*4882a593Smuzhiyun } 379*4882a593Smuzhiyun } 380*4882a593Smuzhiyun 381*4882a593Smuzhiyun#if 0 382*4882a593Smuzhiyun /*gstaplin: we should perhaps investigate something like this branch above...*/ 383*4882a593Smuzhiyun if ([_pasteboard availableTypeFromArray: _known_types] != nil) { 384*4882a593Smuzhiyun /* Pasteboard has data we should proxy; I think it makes 385*4882a593Smuzhiyun sense to put it on both CLIPBOARD and PRIMARY */ 386*4882a593Smuzhiyun 387*4882a593Smuzhiyun XSetSelectionOwner(xpbproxy_dpy, atoms->clipboard, 388*4882a593Smuzhiyun _selection_window, timestamp); 389*4882a593Smuzhiyun XSetSelectionOwner(xpbproxy_dpy, atoms->primary, 390*4882a593Smuzhiyun _selection_window, timestamp); 391*4882a593Smuzhiyun } 392*4882a593Smuzhiyun#endif 393*4882a593Smuzhiyun} 394*4882a593Smuzhiyun 395*4882a593Smuzhiyun/* Called when X11 loses key focus */ 396*4882a593Smuzhiyun- (void) x_inactive:(Time)timestamp 397*4882a593Smuzhiyun{ 398*4882a593Smuzhiyun TRACE(); 399*4882a593Smuzhiyun} 400*4882a593Smuzhiyun 401*4882a593Smuzhiyun/* This requests the TARGETS list from the PRIMARY selection owner. */ 402*4882a593Smuzhiyun- (void) x_copy_request_targets 403*4882a593Smuzhiyun{ 404*4882a593Smuzhiyun TRACE(); 405*4882a593Smuzhiyun 406*4882a593Smuzhiyun request_atom = atoms->targets; 407*4882a593Smuzhiyun XConvertSelection(xpbproxy_dpy, atoms->primary, atoms->targets, 408*4882a593Smuzhiyun atoms->primary, _selection_window, CurrentTime); 409*4882a593Smuzhiyun} 410*4882a593Smuzhiyun 411*4882a593Smuzhiyun/* Called when the Edit/Copy item on the main X11 menubar is selected 412*4882a593Smuzhiyun * and no appkit window claims it. */ 413*4882a593Smuzhiyun- (void) x_copy:(Time)timestamp 414*4882a593Smuzhiyun{ 415*4882a593Smuzhiyun Window w; 416*4882a593Smuzhiyun 417*4882a593Smuzhiyun TRACE(); 418*4882a593Smuzhiyun 419*4882a593Smuzhiyun w = XGetSelectionOwner(xpbproxy_dpy, atoms->primary); 420*4882a593Smuzhiyun 421*4882a593Smuzhiyun if (None != w) { 422*4882a593Smuzhiyun ++pending_copy; 423*4882a593Smuzhiyun 424*4882a593Smuzhiyun if (1 == pending_copy) { 425*4882a593Smuzhiyun /* 426*4882a593Smuzhiyun * There are no other copy operations in progress, so we 427*4882a593Smuzhiyun * can proceed safely. Otherwise the copy_completed method 428*4882a593Smuzhiyun * will see that the pending_copy is > 1, and do another copy. 429*4882a593Smuzhiyun */ 430*4882a593Smuzhiyun [self x_copy_request_targets]; 431*4882a593Smuzhiyun } 432*4882a593Smuzhiyun } 433*4882a593Smuzhiyun} 434*4882a593Smuzhiyun 435*4882a593Smuzhiyun/* Set pbproxy as owner of the SELECTION_MANAGER selection. 436*4882a593Smuzhiyun * This prevents tools like xclipboard from causing havoc. 437*4882a593Smuzhiyun * Returns TRUE on success 438*4882a593Smuzhiyun */ 439*4882a593Smuzhiyun- (BOOL) set_clipboard_manager_status:(BOOL)value 440*4882a593Smuzhiyun{ 441*4882a593Smuzhiyun TRACE(); 442*4882a593Smuzhiyun 443*4882a593Smuzhiyun Window owner = XGetSelectionOwner(xpbproxy_dpy, atoms->clipboard_manager); 444*4882a593Smuzhiyun 445*4882a593Smuzhiyun if (value) { 446*4882a593Smuzhiyun if (owner == _selection_window) 447*4882a593Smuzhiyun return TRUE; 448*4882a593Smuzhiyun 449*4882a593Smuzhiyun if (owner != None) { 450*4882a593Smuzhiyun ErrorF( 451*4882a593Smuzhiyun "A clipboard manager using window 0x%lx already owns the clipboard selection. " 452*4882a593Smuzhiyun "pbproxy will not sync clipboard to pasteboard.\n", owner); 453*4882a593Smuzhiyun return FALSE; 454*4882a593Smuzhiyun } 455*4882a593Smuzhiyun 456*4882a593Smuzhiyun XSetSelectionOwner(xpbproxy_dpy, atoms->clipboard_manager, 457*4882a593Smuzhiyun _selection_window, 458*4882a593Smuzhiyun CurrentTime); 459*4882a593Smuzhiyun return (_selection_window == 460*4882a593Smuzhiyun XGetSelectionOwner(xpbproxy_dpy, atoms->clipboard_manager)); 461*4882a593Smuzhiyun } 462*4882a593Smuzhiyun else { 463*4882a593Smuzhiyun if (owner != _selection_window) 464*4882a593Smuzhiyun return TRUE; 465*4882a593Smuzhiyun 466*4882a593Smuzhiyun XSetSelectionOwner(xpbproxy_dpy, atoms->clipboard_manager, None, 467*4882a593Smuzhiyun CurrentTime); 468*4882a593Smuzhiyun return (None == 469*4882a593Smuzhiyun XGetSelectionOwner(xpbproxy_dpy, atoms->clipboard_manager)); 470*4882a593Smuzhiyun } 471*4882a593Smuzhiyun 472*4882a593Smuzhiyun return FALSE; 473*4882a593Smuzhiyun} 474*4882a593Smuzhiyun 475*4882a593Smuzhiyun/* 476*4882a593Smuzhiyun * This occurs when we previously owned a selection, 477*4882a593Smuzhiyun * and then lost it from another client. 478*4882a593Smuzhiyun */ 479*4882a593Smuzhiyun- (void) clear_event:(XSelectionClearEvent *)e 480*4882a593Smuzhiyun{ 481*4882a593Smuzhiyun 482*4882a593Smuzhiyun TRACE(); 483*4882a593Smuzhiyun 484*4882a593Smuzhiyun DebugF("e->selection %s\n", XGetAtomName(xpbproxy_dpy, e->selection)); 485*4882a593Smuzhiyun 486*4882a593Smuzhiyun if (e->selection == atoms->clipboard) { 487*4882a593Smuzhiyun /* 488*4882a593Smuzhiyun * We lost ownership of the CLIPBOARD. 489*4882a593Smuzhiyun */ 490*4882a593Smuzhiyun ++pending_clipboard; 491*4882a593Smuzhiyun 492*4882a593Smuzhiyun if (1 == pending_clipboard) { 493*4882a593Smuzhiyun /* Claim the clipboard contents from the new owner. */ 494*4882a593Smuzhiyun [self claim_clipboard]; 495*4882a593Smuzhiyun } 496*4882a593Smuzhiyun } 497*4882a593Smuzhiyun else if (e->selection == atoms->clipboard_manager) { 498*4882a593Smuzhiyun if (pbproxy_prefs.clipboard_to_pasteboard) { 499*4882a593Smuzhiyun /* Another CLIPBOARD_MANAGER has set itself as owner. Disable syncing 500*4882a593Smuzhiyun * to avoid a race. 501*4882a593Smuzhiyun */ 502*4882a593Smuzhiyun ErrorF("Another clipboard manager was started! " 503*4882a593Smuzhiyun "xpbproxy is disabling syncing with clipboard.\n"); 504*4882a593Smuzhiyun pbproxy_prefs.clipboard_to_pasteboard = NO; 505*4882a593Smuzhiyun } 506*4882a593Smuzhiyun } 507*4882a593Smuzhiyun} 508*4882a593Smuzhiyun 509*4882a593Smuzhiyun/* 510*4882a593Smuzhiyun * We greedily acquire the clipboard after it changes, and on startup. 511*4882a593Smuzhiyun */ 512*4882a593Smuzhiyun- (void) claim_clipboard 513*4882a593Smuzhiyun{ 514*4882a593Smuzhiyun Window owner; 515*4882a593Smuzhiyun 516*4882a593Smuzhiyun TRACE(); 517*4882a593Smuzhiyun 518*4882a593Smuzhiyun if (!pbproxy_prefs.clipboard_to_pasteboard) 519*4882a593Smuzhiyun return; 520*4882a593Smuzhiyun 521*4882a593Smuzhiyun owner = XGetSelectionOwner(xpbproxy_dpy, atoms->clipboard); 522*4882a593Smuzhiyun if (None == owner) { 523*4882a593Smuzhiyun /* 524*4882a593Smuzhiyun * The owner probably died or we are just starting up pbproxy. 525*4882a593Smuzhiyun * Set pbproxy's _selection_window as the owner, and continue. 526*4882a593Smuzhiyun */ 527*4882a593Smuzhiyun DebugF("No clipboard owner.\n"); 528*4882a593Smuzhiyun [self copy_completed:atoms->clipboard]; 529*4882a593Smuzhiyun return; 530*4882a593Smuzhiyun } 531*4882a593Smuzhiyun else if (owner == _selection_window) { 532*4882a593Smuzhiyun [self copy_completed:atoms->clipboard]; 533*4882a593Smuzhiyun return; 534*4882a593Smuzhiyun } 535*4882a593Smuzhiyun 536*4882a593Smuzhiyun DebugF("requesting targets\n"); 537*4882a593Smuzhiyun 538*4882a593Smuzhiyun request_atom = atoms->targets; 539*4882a593Smuzhiyun XConvertSelection(xpbproxy_dpy, atoms->clipboard, atoms->targets, 540*4882a593Smuzhiyun atoms->clipboard, _selection_window, CurrentTime); 541*4882a593Smuzhiyun XFlush(xpbproxy_dpy); 542*4882a593Smuzhiyun /* Now we will get a SelectionNotify event in the future. */ 543*4882a593Smuzhiyun} 544*4882a593Smuzhiyun 545*4882a593Smuzhiyun/* Greedily acquire the clipboard. */ 546*4882a593Smuzhiyun- (void) own_clipboard 547*4882a593Smuzhiyun{ 548*4882a593Smuzhiyun 549*4882a593Smuzhiyun TRACE(); 550*4882a593Smuzhiyun 551*4882a593Smuzhiyun /* We should perhaps have a boundary limit on the number of iterations... */ 552*4882a593Smuzhiyun do { 553*4882a593Smuzhiyun XSetSelectionOwner(xpbproxy_dpy, atoms->clipboard, _selection_window, 554*4882a593Smuzhiyun CurrentTime); 555*4882a593Smuzhiyun } while (_selection_window != XGetSelectionOwner(xpbproxy_dpy, 556*4882a593Smuzhiyun atoms->clipboard)); 557*4882a593Smuzhiyun} 558*4882a593Smuzhiyun 559*4882a593Smuzhiyun- (void) init_reply:(XEvent *)reply request:(XSelectionRequestEvent *)e 560*4882a593Smuzhiyun{ 561*4882a593Smuzhiyun reply->xselection.type = SelectionNotify; 562*4882a593Smuzhiyun reply->xselection.selection = e->selection; 563*4882a593Smuzhiyun reply->xselection.target = e->target; 564*4882a593Smuzhiyun reply->xselection.requestor = e->requestor; 565*4882a593Smuzhiyun reply->xselection.time = e->time; 566*4882a593Smuzhiyun reply->xselection.property = None; 567*4882a593Smuzhiyun} 568*4882a593Smuzhiyun 569*4882a593Smuzhiyun- (void) send_reply:(XEvent *)reply 570*4882a593Smuzhiyun{ 571*4882a593Smuzhiyun /* 572*4882a593Smuzhiyun * We are supposed to use an empty event mask, and not propagate 573*4882a593Smuzhiyun * the event, according to the ICCCM. 574*4882a593Smuzhiyun */ 575*4882a593Smuzhiyun DebugF("reply->xselection.requestor 0x%lx\n", reply->xselection.requestor); 576*4882a593Smuzhiyun 577*4882a593Smuzhiyun XSendEvent(xpbproxy_dpy, reply->xselection.requestor, False, 0, reply); 578*4882a593Smuzhiyun XFlush(xpbproxy_dpy); 579*4882a593Smuzhiyun} 580*4882a593Smuzhiyun 581*4882a593Smuzhiyun/* 582*4882a593Smuzhiyun * This responds to a TARGETS request. 583*4882a593Smuzhiyun * The result is a list of a ATOMs that correspond to the types available 584*4882a593Smuzhiyun * for a selection. 585*4882a593Smuzhiyun * For instance an application might provide a UTF8_STRING and a STRING 586*4882a593Smuzhiyun * (in Latin-1 encoding). The requestor can then make the choice based on 587*4882a593Smuzhiyun * the list. 588*4882a593Smuzhiyun */ 589*4882a593Smuzhiyun- (void) send_targets:(XSelectionRequestEvent *)e pasteboard:(NSPasteboard *) 590*4882a593Smuzhiyun pb 591*4882a593Smuzhiyun{ 592*4882a593Smuzhiyun XEvent reply; 593*4882a593Smuzhiyun NSArray *pbtypes; 594*4882a593Smuzhiyun 595*4882a593Smuzhiyun [self init_reply:&reply request:e]; 596*4882a593Smuzhiyun 597*4882a593Smuzhiyun pbtypes = [pb types]; 598*4882a593Smuzhiyun if (pbtypes) { 599*4882a593Smuzhiyun long list[7]; /* Don't forget to increase this if we handle more types! */ 600*4882a593Smuzhiyun long count = 0; 601*4882a593Smuzhiyun 602*4882a593Smuzhiyun /* 603*4882a593Smuzhiyun * I'm not sure if this is needed, but some toolkits/clients list 604*4882a593Smuzhiyun * TARGETS in response to targets. 605*4882a593Smuzhiyun */ 606*4882a593Smuzhiyun list[count] = atoms->targets; 607*4882a593Smuzhiyun ++count; 608*4882a593Smuzhiyun 609*4882a593Smuzhiyun if ([pbtypes containsObject:NSStringPboardType]) { 610*4882a593Smuzhiyun /* We have a string type that we can convert to UTF8, or Latin-1... */ 611*4882a593Smuzhiyun DebugF("NSStringPboardType\n"); 612*4882a593Smuzhiyun list[count] = atoms->utf8_string; 613*4882a593Smuzhiyun ++count; 614*4882a593Smuzhiyun list[count] = atoms->string; 615*4882a593Smuzhiyun ++count; 616*4882a593Smuzhiyun list[count] = atoms->compound_text; 617*4882a593Smuzhiyun ++count; 618*4882a593Smuzhiyun } 619*4882a593Smuzhiyun 620*4882a593Smuzhiyun /* TODO add the NSPICTPboardType back again, once we have conversion 621*4882a593Smuzhiyun * functionality in send_image. 622*4882a593Smuzhiyun */ 623*4882a593Smuzhiyun#ifdef __clang__ 624*4882a593Smuzhiyun#pragma clang diagnostic push 625*4882a593Smuzhiyun#pragma clang diagnostic ignored "-Wdeprecated-declarations" // NSPICTPboardType 626*4882a593Smuzhiyun#endif 627*4882a593Smuzhiyun 628*4882a593Smuzhiyun if ([pbtypes containsObject:NSPICTPboardType] 629*4882a593Smuzhiyun || [pbtypes containsObject:NSTIFFPboardType]) { 630*4882a593Smuzhiyun /* We can convert a TIFF to a PNG or JPEG. */ 631*4882a593Smuzhiyun DebugF("NSTIFFPboardType\n"); 632*4882a593Smuzhiyun list[count] = atoms->image_png; 633*4882a593Smuzhiyun ++count; 634*4882a593Smuzhiyun list[count] = atoms->image_jpeg; 635*4882a593Smuzhiyun ++count; 636*4882a593Smuzhiyun } 637*4882a593Smuzhiyun 638*4882a593Smuzhiyun#ifdef __clang__ 639*4882a593Smuzhiyun#pragma clang diagnostic pop 640*4882a593Smuzhiyun#endif 641*4882a593Smuzhiyun 642*4882a593Smuzhiyun if (count) { 643*4882a593Smuzhiyun /* We have a list of ATOMs to send. */ 644*4882a593Smuzhiyun XChangeProperty(xpbproxy_dpy, e->requestor, e->property, 645*4882a593Smuzhiyun atoms->atom, 32, 646*4882a593Smuzhiyun PropModeReplace, (unsigned char *)list, 647*4882a593Smuzhiyun count); 648*4882a593Smuzhiyun 649*4882a593Smuzhiyun reply.xselection.property = e->property; 650*4882a593Smuzhiyun } 651*4882a593Smuzhiyun } 652*4882a593Smuzhiyun 653*4882a593Smuzhiyun [self send_reply:&reply]; 654*4882a593Smuzhiyun} 655*4882a593Smuzhiyun 656*4882a593Smuzhiyun- (void) send_string:(XSelectionRequestEvent *)e utf8:(BOOL)utf8 pasteboard:( 657*4882a593Smuzhiyun NSPasteboard *)pb 658*4882a593Smuzhiyun{ 659*4882a593Smuzhiyun XEvent reply; 660*4882a593Smuzhiyun NSArray *pbtypes; 661*4882a593Smuzhiyun NSString *data; 662*4882a593Smuzhiyun const char *bytes; 663*4882a593Smuzhiyun NSUInteger length; 664*4882a593Smuzhiyun 665*4882a593Smuzhiyun TRACE(); 666*4882a593Smuzhiyun 667*4882a593Smuzhiyun [self init_reply:&reply request:e]; 668*4882a593Smuzhiyun 669*4882a593Smuzhiyun pbtypes = [pb types]; 670*4882a593Smuzhiyun 671*4882a593Smuzhiyun if (![pbtypes containsObject:NSStringPboardType]) { 672*4882a593Smuzhiyun [self send_reply:&reply]; 673*4882a593Smuzhiyun return; 674*4882a593Smuzhiyun } 675*4882a593Smuzhiyun 676*4882a593Smuzhiyun DebugF("pbtypes retainCount after containsObject: %lu\n", 677*4882a593Smuzhiyun [pbtypes retainCount]); 678*4882a593Smuzhiyun 679*4882a593Smuzhiyun data = [pb stringForType:NSStringPboardType]; 680*4882a593Smuzhiyun 681*4882a593Smuzhiyun if (nil == data) { 682*4882a593Smuzhiyun [self send_reply:&reply]; 683*4882a593Smuzhiyun return; 684*4882a593Smuzhiyun } 685*4882a593Smuzhiyun 686*4882a593Smuzhiyun if (utf8) { 687*4882a593Smuzhiyun bytes = [data UTF8String]; 688*4882a593Smuzhiyun /* 689*4882a593Smuzhiyun * We don't want the UTF-8 string length here. 690*4882a593Smuzhiyun * We want the length in bytes. 691*4882a593Smuzhiyun */ 692*4882a593Smuzhiyun length = strlen(bytes); 693*4882a593Smuzhiyun 694*4882a593Smuzhiyun if (length < 50) { 695*4882a593Smuzhiyun DebugF("UTF-8: %s\n", bytes); 696*4882a593Smuzhiyun DebugF("UTF-8 length: %lu\n", length); 697*4882a593Smuzhiyun } 698*4882a593Smuzhiyun } 699*4882a593Smuzhiyun else { 700*4882a593Smuzhiyun DebugF("Latin-1\n"); 701*4882a593Smuzhiyun bytes = [data cStringUsingEncoding:NSISOLatin1StringEncoding]; 702*4882a593Smuzhiyun /*WARNING: bytes is not NUL-terminated. */ 703*4882a593Smuzhiyun length = [data lengthOfBytesUsingEncoding:NSISOLatin1StringEncoding]; 704*4882a593Smuzhiyun } 705*4882a593Smuzhiyun 706*4882a593Smuzhiyun DebugF("e->target %s\n", XGetAtomName(xpbproxy_dpy, e->target)); 707*4882a593Smuzhiyun 708*4882a593Smuzhiyun XChangeProperty(xpbproxy_dpy, e->requestor, e->property, e->target, 709*4882a593Smuzhiyun 8, PropModeReplace, (unsigned char *)bytes, length); 710*4882a593Smuzhiyun 711*4882a593Smuzhiyun reply.xselection.property = e->property; 712*4882a593Smuzhiyun 713*4882a593Smuzhiyun [self send_reply:&reply]; 714*4882a593Smuzhiyun} 715*4882a593Smuzhiyun 716*4882a593Smuzhiyun- (void) send_compound_text:(XSelectionRequestEvent *)e pasteboard:( 717*4882a593Smuzhiyun NSPasteboard *)pb 718*4882a593Smuzhiyun{ 719*4882a593Smuzhiyun XEvent reply; 720*4882a593Smuzhiyun NSArray *pbtypes; 721*4882a593Smuzhiyun 722*4882a593Smuzhiyun TRACE(); 723*4882a593Smuzhiyun 724*4882a593Smuzhiyun [self init_reply:&reply request:e]; 725*4882a593Smuzhiyun 726*4882a593Smuzhiyun pbtypes = [pb types]; 727*4882a593Smuzhiyun 728*4882a593Smuzhiyun if ([pbtypes containsObject: NSStringPboardType]) { 729*4882a593Smuzhiyun NSString *data = [pb stringForType:NSStringPboardType]; 730*4882a593Smuzhiyun if (nil != data) { 731*4882a593Smuzhiyun /* 732*4882a593Smuzhiyun * Cast to (void *) to avoid a const warning. 733*4882a593Smuzhiyun * AFAIK Xutf8TextListToTextProperty does not modify the input memory. 734*4882a593Smuzhiyun */ 735*4882a593Smuzhiyun void *utf8 = (void *)[data UTF8String]; 736*4882a593Smuzhiyun char *list[] = { utf8, NULL }; 737*4882a593Smuzhiyun XTextProperty textprop; 738*4882a593Smuzhiyun 739*4882a593Smuzhiyun textprop.value = NULL; 740*4882a593Smuzhiyun 741*4882a593Smuzhiyun if (Success == Xutf8TextListToTextProperty(xpbproxy_dpy, list, 1, 742*4882a593Smuzhiyun XCompoundTextStyle, 743*4882a593Smuzhiyun &textprop)) { 744*4882a593Smuzhiyun 745*4882a593Smuzhiyun if (8 != textprop.format) 746*4882a593Smuzhiyun DebugF( 747*4882a593Smuzhiyun "textprop.format is unexpectedly not 8 - it's %d instead\n", 748*4882a593Smuzhiyun textprop.format); 749*4882a593Smuzhiyun 750*4882a593Smuzhiyun XChangeProperty(xpbproxy_dpy, e->requestor, e->property, 751*4882a593Smuzhiyun atoms->compound_text, textprop.format, 752*4882a593Smuzhiyun PropModeReplace, textprop.value, 753*4882a593Smuzhiyun textprop.nitems); 754*4882a593Smuzhiyun 755*4882a593Smuzhiyun reply.xselection.property = e->property; 756*4882a593Smuzhiyun } 757*4882a593Smuzhiyun 758*4882a593Smuzhiyun if (textprop.value) 759*4882a593Smuzhiyun XFree(textprop.value); 760*4882a593Smuzhiyun 761*4882a593Smuzhiyun } 762*4882a593Smuzhiyun } 763*4882a593Smuzhiyun 764*4882a593Smuzhiyun [self send_reply:&reply]; 765*4882a593Smuzhiyun} 766*4882a593Smuzhiyun 767*4882a593Smuzhiyun/* Finding a test application that uses MULTIPLE has proven to be difficult. */ 768*4882a593Smuzhiyun- (void) send_multiple:(XSelectionRequestEvent *)e 769*4882a593Smuzhiyun{ 770*4882a593Smuzhiyun XEvent reply; 771*4882a593Smuzhiyun 772*4882a593Smuzhiyun TRACE(); 773*4882a593Smuzhiyun 774*4882a593Smuzhiyun [self init_reply:&reply request:e]; 775*4882a593Smuzhiyun 776*4882a593Smuzhiyun if (None != e->property) {} 777*4882a593Smuzhiyun 778*4882a593Smuzhiyun [self send_reply:&reply]; 779*4882a593Smuzhiyun} 780*4882a593Smuzhiyun 781*4882a593Smuzhiyun/* Return nil if an error occurred. */ 782*4882a593Smuzhiyun/* DO NOT retain the encdata for longer than the length of an event response. 783*4882a593Smuzhiyun * The autorelease pool will reuse/free it. 784*4882a593Smuzhiyun */ 785*4882a593Smuzhiyun- (NSData *) encode_image_data:(NSData *)data type:(NSBitmapImageFileType) 786*4882a593Smuzhiyun enctype 787*4882a593Smuzhiyun{ 788*4882a593Smuzhiyun NSBitmapImageRep *bmimage = nil; 789*4882a593Smuzhiyun NSData *encdata = nil; 790*4882a593Smuzhiyun NSDictionary *dict = nil; 791*4882a593Smuzhiyun 792*4882a593Smuzhiyun bmimage = [[NSBitmapImageRep alloc] initWithData:data]; 793*4882a593Smuzhiyun 794*4882a593Smuzhiyun if (nil == bmimage) 795*4882a593Smuzhiyun return nil; 796*4882a593Smuzhiyun 797*4882a593Smuzhiyun dict = [[NSDictionary alloc] init]; 798*4882a593Smuzhiyun encdata = [bmimage representationUsingType:enctype properties:dict]; 799*4882a593Smuzhiyun 800*4882a593Smuzhiyun if (nil == encdata) { 801*4882a593Smuzhiyun [dict autorelease]; 802*4882a593Smuzhiyun [bmimage autorelease]; 803*4882a593Smuzhiyun return nil; 804*4882a593Smuzhiyun } 805*4882a593Smuzhiyun 806*4882a593Smuzhiyun [dict autorelease]; 807*4882a593Smuzhiyun [bmimage autorelease]; 808*4882a593Smuzhiyun 809*4882a593Smuzhiyun return encdata; 810*4882a593Smuzhiyun} 811*4882a593Smuzhiyun 812*4882a593Smuzhiyun/* Return YES when an error has occurred when trying to send the PICT. */ 813*4882a593Smuzhiyun/* The caller should send a default response with a property of None when an error occurs. */ 814*4882a593Smuzhiyun- (BOOL) send_image_pict_reply:(XSelectionRequestEvent *)e 815*4882a593Smuzhiyun pasteboard:(NSPasteboard *)pb 816*4882a593Smuzhiyun type:(NSBitmapImageFileType)imagetype 817*4882a593Smuzhiyun{ 818*4882a593Smuzhiyun XEvent reply; 819*4882a593Smuzhiyun NSImage *img = nil; 820*4882a593Smuzhiyun NSData *data = nil, *encdata = nil; 821*4882a593Smuzhiyun NSUInteger length; 822*4882a593Smuzhiyun const void *bytes = NULL; 823*4882a593Smuzhiyun 824*4882a593Smuzhiyun img = [[NSImage alloc] initWithPasteboard:pb]; 825*4882a593Smuzhiyun 826*4882a593Smuzhiyun if (nil == img) { 827*4882a593Smuzhiyun return YES; 828*4882a593Smuzhiyun } 829*4882a593Smuzhiyun 830*4882a593Smuzhiyun data = [img TIFFRepresentation]; 831*4882a593Smuzhiyun 832*4882a593Smuzhiyun if (nil == data) { 833*4882a593Smuzhiyun [img autorelease]; 834*4882a593Smuzhiyun ErrorF("unable to convert PICT to TIFF!\n"); 835*4882a593Smuzhiyun return YES; 836*4882a593Smuzhiyun } 837*4882a593Smuzhiyun 838*4882a593Smuzhiyun encdata = [self encode_image_data:data type:imagetype]; 839*4882a593Smuzhiyun if (nil == encdata) { 840*4882a593Smuzhiyun [img autorelease]; 841*4882a593Smuzhiyun return YES; 842*4882a593Smuzhiyun } 843*4882a593Smuzhiyun 844*4882a593Smuzhiyun [self init_reply:&reply request:e]; 845*4882a593Smuzhiyun 846*4882a593Smuzhiyun length = [encdata length]; 847*4882a593Smuzhiyun bytes = [encdata bytes]; 848*4882a593Smuzhiyun 849*4882a593Smuzhiyun XChangeProperty(xpbproxy_dpy, e->requestor, e->property, e->target, 850*4882a593Smuzhiyun 8, PropModeReplace, bytes, length); 851*4882a593Smuzhiyun reply.xselection.property = e->property; 852*4882a593Smuzhiyun 853*4882a593Smuzhiyun [self send_reply:&reply]; 854*4882a593Smuzhiyun 855*4882a593Smuzhiyun [img autorelease]; 856*4882a593Smuzhiyun 857*4882a593Smuzhiyun return NO; /*no error*/ 858*4882a593Smuzhiyun} 859*4882a593Smuzhiyun 860*4882a593Smuzhiyun/* Return YES if an error occurred. */ 861*4882a593Smuzhiyun/* The caller should send a reply with a property of None when an error occurs. */ 862*4882a593Smuzhiyun- (BOOL) send_image_tiff_reply:(XSelectionRequestEvent *)e 863*4882a593Smuzhiyun pasteboard:(NSPasteboard *)pb 864*4882a593Smuzhiyun type:(NSBitmapImageFileType)imagetype 865*4882a593Smuzhiyun{ 866*4882a593Smuzhiyun XEvent reply; 867*4882a593Smuzhiyun NSData *data = nil; 868*4882a593Smuzhiyun NSData *encdata = nil; 869*4882a593Smuzhiyun NSUInteger length; 870*4882a593Smuzhiyun const void *bytes = NULL; 871*4882a593Smuzhiyun 872*4882a593Smuzhiyun data = [pb dataForType:NSTIFFPboardType]; 873*4882a593Smuzhiyun 874*4882a593Smuzhiyun if (nil == data) 875*4882a593Smuzhiyun return YES; 876*4882a593Smuzhiyun 877*4882a593Smuzhiyun encdata = [self encode_image_data:data type:imagetype]; 878*4882a593Smuzhiyun 879*4882a593Smuzhiyun if (nil == encdata) 880*4882a593Smuzhiyun return YES; 881*4882a593Smuzhiyun 882*4882a593Smuzhiyun [self init_reply:&reply request:e]; 883*4882a593Smuzhiyun 884*4882a593Smuzhiyun length = [encdata length]; 885*4882a593Smuzhiyun bytes = [encdata bytes]; 886*4882a593Smuzhiyun 887*4882a593Smuzhiyun XChangeProperty(xpbproxy_dpy, e->requestor, e->property, e->target, 888*4882a593Smuzhiyun 8, PropModeReplace, bytes, length); 889*4882a593Smuzhiyun reply.xselection.property = e->property; 890*4882a593Smuzhiyun 891*4882a593Smuzhiyun [self send_reply:&reply]; 892*4882a593Smuzhiyun 893*4882a593Smuzhiyun return NO; /*no error*/ 894*4882a593Smuzhiyun} 895*4882a593Smuzhiyun 896*4882a593Smuzhiyun- (void) send_image:(XSelectionRequestEvent *)e pasteboard:(NSPasteboard *)pb 897*4882a593Smuzhiyun{ 898*4882a593Smuzhiyun NSArray *pbtypes = nil; 899*4882a593Smuzhiyun NSBitmapImageFileType imagetype = NSPNGFileType; 900*4882a593Smuzhiyun 901*4882a593Smuzhiyun TRACE(); 902*4882a593Smuzhiyun 903*4882a593Smuzhiyun if (e->target == atoms->image_png) 904*4882a593Smuzhiyun imagetype = NSPNGFileType; 905*4882a593Smuzhiyun else if (e->target == atoms->image_jpeg) 906*4882a593Smuzhiyun imagetype = NSJPEGFileType; 907*4882a593Smuzhiyun else { 908*4882a593Smuzhiyun ErrorF( 909*4882a593Smuzhiyun "internal failure in xpbproxy! imagetype being sent isn't PNG or JPEG.\n"); 910*4882a593Smuzhiyun } 911*4882a593Smuzhiyun 912*4882a593Smuzhiyun pbtypes = [pb types]; 913*4882a593Smuzhiyun 914*4882a593Smuzhiyun if (pbtypes) { 915*4882a593Smuzhiyun if ([pbtypes containsObject:NSTIFFPboardType]) { 916*4882a593Smuzhiyun if (NO == 917*4882a593Smuzhiyun [self send_image_tiff_reply:e pasteboard:pb type:imagetype]) 918*4882a593Smuzhiyun return; 919*4882a593Smuzhiyun } 920*4882a593Smuzhiyun#ifdef __clang__ 921*4882a593Smuzhiyun#pragma clang diagnostic push 922*4882a593Smuzhiyun#pragma clang diagnostic ignored "-Wdeprecated-declarations" // NSPICTPboardType 923*4882a593Smuzhiyun#endif 924*4882a593Smuzhiyun else if ([pbtypes containsObject:NSPICTPboardType]) 925*4882a593Smuzhiyun#ifdef __clang__ 926*4882a593Smuzhiyun#pragma clang diagnostic pop 927*4882a593Smuzhiyun#endif 928*4882a593Smuzhiyun { 929*4882a593Smuzhiyun if (NO == 930*4882a593Smuzhiyun [self send_image_pict_reply:e pasteboard:pb type:imagetype]) 931*4882a593Smuzhiyun return; 932*4882a593Smuzhiyun 933*4882a593Smuzhiyun /* Fall through intentionally to the send_none: */ 934*4882a593Smuzhiyun } 935*4882a593Smuzhiyun } 936*4882a593Smuzhiyun 937*4882a593Smuzhiyun [self send_none:e]; 938*4882a593Smuzhiyun} 939*4882a593Smuzhiyun 940*4882a593Smuzhiyun- (void)send_none:(XSelectionRequestEvent *)e 941*4882a593Smuzhiyun{ 942*4882a593Smuzhiyun XEvent reply; 943*4882a593Smuzhiyun 944*4882a593Smuzhiyun TRACE(); 945*4882a593Smuzhiyun 946*4882a593Smuzhiyun [self init_reply:&reply request:e]; 947*4882a593Smuzhiyun [self send_reply:&reply]; 948*4882a593Smuzhiyun} 949*4882a593Smuzhiyun 950*4882a593Smuzhiyun/* Another client requested the data or targets of data available from the clipboard. */ 951*4882a593Smuzhiyun- (void)request_event:(XSelectionRequestEvent *)e 952*4882a593Smuzhiyun{ 953*4882a593Smuzhiyun NSPasteboard *pb; 954*4882a593Smuzhiyun 955*4882a593Smuzhiyun TRACE(); 956*4882a593Smuzhiyun 957*4882a593Smuzhiyun /* TODO We should also keep track of the time of the selection, and 958*4882a593Smuzhiyun * according to the ICCCM "refuse the request" if the event timestamp 959*4882a593Smuzhiyun * is before we owned it. 960*4882a593Smuzhiyun * What should we base the time on? How can we get the current time just 961*4882a593Smuzhiyun * before an XSetSelectionOwner? Is it the server's time, or the clients? 962*4882a593Smuzhiyun * According to the XSelectionRequestEvent manual page, the Time value 963*4882a593Smuzhiyun * may be set to CurrentTime or a time, so that makes it a bit different. 964*4882a593Smuzhiyun * Perhaps we should just punt and ignore races. 965*4882a593Smuzhiyun */ 966*4882a593Smuzhiyun 967*4882a593Smuzhiyun /*TODO we need a COMPOUND_TEXT test app*/ 968*4882a593Smuzhiyun /*TODO we need a MULTIPLE test app*/ 969*4882a593Smuzhiyun 970*4882a593Smuzhiyun pb = [NSPasteboard generalPasteboard]; 971*4882a593Smuzhiyun if (nil == pb) { 972*4882a593Smuzhiyun [self send_none:e]; 973*4882a593Smuzhiyun return; 974*4882a593Smuzhiyun } 975*4882a593Smuzhiyun 976*4882a593Smuzhiyun if (None != e->target) 977*4882a593Smuzhiyun DebugF("e->target %s\n", XGetAtomName(xpbproxy_dpy, e->target)); 978*4882a593Smuzhiyun 979*4882a593Smuzhiyun if (e->target == atoms->targets) { 980*4882a593Smuzhiyun /* The paste requestor wants to know what TARGETS we support. */ 981*4882a593Smuzhiyun [self send_targets:e pasteboard:pb]; 982*4882a593Smuzhiyun } 983*4882a593Smuzhiyun else if (e->target == atoms->multiple) { 984*4882a593Smuzhiyun /* 985*4882a593Smuzhiyun * This isn't finished, and may never be, unless I can find 986*4882a593Smuzhiyun * a good test app. 987*4882a593Smuzhiyun */ 988*4882a593Smuzhiyun [self send_multiple:e]; 989*4882a593Smuzhiyun } 990*4882a593Smuzhiyun else if (e->target == atoms->utf8_string) { 991*4882a593Smuzhiyun [self send_string:e utf8:YES pasteboard:pb]; 992*4882a593Smuzhiyun } 993*4882a593Smuzhiyun else if (e->target == atoms->string) { 994*4882a593Smuzhiyun [self send_string:e utf8:NO pasteboard:pb]; 995*4882a593Smuzhiyun } 996*4882a593Smuzhiyun else if (e->target == atoms->compound_text) { 997*4882a593Smuzhiyun [self send_compound_text:e pasteboard:pb]; 998*4882a593Smuzhiyun } 999*4882a593Smuzhiyun else if (e->target == atoms->multiple) { 1000*4882a593Smuzhiyun [self send_multiple:e]; 1001*4882a593Smuzhiyun } 1002*4882a593Smuzhiyun else if (e->target == atoms->image_png || e->target == 1003*4882a593Smuzhiyun atoms->image_jpeg) { 1004*4882a593Smuzhiyun [self send_image:e pasteboard:pb]; 1005*4882a593Smuzhiyun } 1006*4882a593Smuzhiyun else { 1007*4882a593Smuzhiyun [self send_none:e]; 1008*4882a593Smuzhiyun } 1009*4882a593Smuzhiyun} 1010*4882a593Smuzhiyun 1011*4882a593Smuzhiyun/* This handles the events resulting from an XConvertSelection request. */ 1012*4882a593Smuzhiyun- (void) notify_event:(XSelectionEvent *)e 1013*4882a593Smuzhiyun{ 1014*4882a593Smuzhiyun Atom type; 1015*4882a593Smuzhiyun struct propdata pdata; 1016*4882a593Smuzhiyun 1017*4882a593Smuzhiyun TRACE(); 1018*4882a593Smuzhiyun 1019*4882a593Smuzhiyun [self release_pending]; 1020*4882a593Smuzhiyun 1021*4882a593Smuzhiyun if (None == e->property) { 1022*4882a593Smuzhiyun DebugF("e->property is None.\n"); 1023*4882a593Smuzhiyun [self copy_completed:e->selection]; 1024*4882a593Smuzhiyun /* Nothing is selected. */ 1025*4882a593Smuzhiyun return; 1026*4882a593Smuzhiyun } 1027*4882a593Smuzhiyun 1028*4882a593Smuzhiyun#if 0 1029*4882a593Smuzhiyun ErrorF("e->selection %s\n", XGetAtomName(xpbproxy_dpy, e->selection)); 1030*4882a593Smuzhiyun ErrorF("e->property %s\n", XGetAtomName(xpbproxy_dpy, e->property)); 1031*4882a593Smuzhiyun#endif 1032*4882a593Smuzhiyun 1033*4882a593Smuzhiyun if ([self is_incr_type:e]) { 1034*4882a593Smuzhiyun /* 1035*4882a593Smuzhiyun * This is an INCR-style transfer, which means that we 1036*4882a593Smuzhiyun * will get the data after a series of PropertyNotify events. 1037*4882a593Smuzhiyun */ 1038*4882a593Smuzhiyun DebugF("is INCR\n"); 1039*4882a593Smuzhiyun 1040*4882a593Smuzhiyun if (get_property(e->requestor, e->property, &pdata, /*Delete*/ True, 1041*4882a593Smuzhiyun &type)) { 1042*4882a593Smuzhiyun /* 1043*4882a593Smuzhiyun * An error occurred, so we should invoke the copy_completed:, but 1044*4882a593Smuzhiyun * not handle_selection:type:propdata: 1045*4882a593Smuzhiyun */ 1046*4882a593Smuzhiyun [self copy_completed:e->selection]; 1047*4882a593Smuzhiyun return; 1048*4882a593Smuzhiyun } 1049*4882a593Smuzhiyun 1050*4882a593Smuzhiyun free_propdata(&pdata); 1051*4882a593Smuzhiyun 1052*4882a593Smuzhiyun pending.requestor = e->requestor; 1053*4882a593Smuzhiyun pending.selection = e->selection; 1054*4882a593Smuzhiyun 1055*4882a593Smuzhiyun DebugF("set pending.requestor to 0x%lx\n", pending.requestor); 1056*4882a593Smuzhiyun } 1057*4882a593Smuzhiyun else { 1058*4882a593Smuzhiyun if (get_property(e->requestor, e->property, &pdata, /*Delete*/ True, 1059*4882a593Smuzhiyun &type)) { 1060*4882a593Smuzhiyun [self copy_completed:e->selection]; 1061*4882a593Smuzhiyun return; 1062*4882a593Smuzhiyun } 1063*4882a593Smuzhiyun 1064*4882a593Smuzhiyun /* We have the complete selection data.*/ 1065*4882a593Smuzhiyun [self handle_selection:e->selection type:type propdata:&pdata]; 1066*4882a593Smuzhiyun 1067*4882a593Smuzhiyun DebugF("handled selection with the first notify_event\n"); 1068*4882a593Smuzhiyun } 1069*4882a593Smuzhiyun} 1070*4882a593Smuzhiyun 1071*4882a593Smuzhiyun/* This is used for INCR transfers. See the ICCCM for the details. */ 1072*4882a593Smuzhiyun/* This is used to retrieve PRIMARY and CLIPBOARD selections. */ 1073*4882a593Smuzhiyun- (void) property_event:(XPropertyEvent *)e 1074*4882a593Smuzhiyun{ 1075*4882a593Smuzhiyun struct propdata pdata; 1076*4882a593Smuzhiyun Atom type; 1077*4882a593Smuzhiyun 1078*4882a593Smuzhiyun TRACE(); 1079*4882a593Smuzhiyun 1080*4882a593Smuzhiyun if (None != e->atom) { 1081*4882a593Smuzhiyun#ifdef DEBUG 1082*4882a593Smuzhiyun char *name = XGetAtomName(xpbproxy_dpy, e->atom); 1083*4882a593Smuzhiyun 1084*4882a593Smuzhiyun if (name) { 1085*4882a593Smuzhiyun DebugF("e->atom %s\n", name); 1086*4882a593Smuzhiyun XFree(name); 1087*4882a593Smuzhiyun } 1088*4882a593Smuzhiyun#endif 1089*4882a593Smuzhiyun } 1090*4882a593Smuzhiyun 1091*4882a593Smuzhiyun if (None != pending.requestor && PropertyNewValue == e->state) { 1092*4882a593Smuzhiyun DebugF("pending.requestor 0x%lx\n", pending.requestor); 1093*4882a593Smuzhiyun 1094*4882a593Smuzhiyun if (get_property(e->window, e->atom, &pdata, /*Delete*/ True, 1095*4882a593Smuzhiyun &type)) { 1096*4882a593Smuzhiyun [self copy_completed:pending.selection]; 1097*4882a593Smuzhiyun [self release_pending]; 1098*4882a593Smuzhiyun return; 1099*4882a593Smuzhiyun } 1100*4882a593Smuzhiyun 1101*4882a593Smuzhiyun if (0 == pdata.length) { 1102*4882a593Smuzhiyun /* 1103*4882a593Smuzhiyun * We completed the transfer. 1104*4882a593Smuzhiyun * handle_selection will call copy_completed: for us. 1105*4882a593Smuzhiyun */ 1106*4882a593Smuzhiyun [self handle_selection:pending.selection type:type propdata:& 1107*4882a593Smuzhiyun pending.propdata]; 1108*4882a593Smuzhiyun free_propdata(&pdata); 1109*4882a593Smuzhiyun pending.propdata = null_propdata; 1110*4882a593Smuzhiyun pending.requestor = None; 1111*4882a593Smuzhiyun pending.selection = None; 1112*4882a593Smuzhiyun } 1113*4882a593Smuzhiyun else { 1114*4882a593Smuzhiyun [self append_to_pending:&pdata requestor:e->window]; 1115*4882a593Smuzhiyun free_propdata(&pdata); 1116*4882a593Smuzhiyun } 1117*4882a593Smuzhiyun } 1118*4882a593Smuzhiyun} 1119*4882a593Smuzhiyun 1120*4882a593Smuzhiyun- (void) xfixes_selection_notify:(XFixesSelectionNotifyEvent *)e 1121*4882a593Smuzhiyun{ 1122*4882a593Smuzhiyun if (!pbproxy_prefs.active) 1123*4882a593Smuzhiyun return; 1124*4882a593Smuzhiyun 1125*4882a593Smuzhiyun switch (e->subtype) { 1126*4882a593Smuzhiyun case XFixesSetSelectionOwnerNotify: 1127*4882a593Smuzhiyun if (e->selection == atoms->primary && pbproxy_prefs.primary_on_grab) 1128*4882a593Smuzhiyun [self x_copy:e->timestamp]; 1129*4882a593Smuzhiyun break; 1130*4882a593Smuzhiyun 1131*4882a593Smuzhiyun case XFixesSelectionWindowDestroyNotify: 1132*4882a593Smuzhiyun case XFixesSelectionClientCloseNotify: 1133*4882a593Smuzhiyun default: 1134*4882a593Smuzhiyun ErrorF("Unhandled XFixesSelectionNotifyEvent: subtype=%d\n", 1135*4882a593Smuzhiyun e->subtype); 1136*4882a593Smuzhiyun break; 1137*4882a593Smuzhiyun } 1138*4882a593Smuzhiyun} 1139*4882a593Smuzhiyun 1140*4882a593Smuzhiyun- (void) handle_targets: (Atom)selection propdata:(struct propdata *)pdata 1141*4882a593Smuzhiyun{ 1142*4882a593Smuzhiyun /* Find a type we can handle and prefer from the list of ATOMs. */ 1143*4882a593Smuzhiyun Atom preferred; 1144*4882a593Smuzhiyun char *name; 1145*4882a593Smuzhiyun 1146*4882a593Smuzhiyun TRACE(); 1147*4882a593Smuzhiyun 1148*4882a593Smuzhiyun preferred = [self find_preferred:pdata]; 1149*4882a593Smuzhiyun 1150*4882a593Smuzhiyun if (None == preferred) { 1151*4882a593Smuzhiyun /* 1152*4882a593Smuzhiyun * This isn't required by the ICCCM, but some apps apparently 1153*4882a593Smuzhiyun * don't respond to TARGETS properly. 1154*4882a593Smuzhiyun */ 1155*4882a593Smuzhiyun preferred = atoms->string; 1156*4882a593Smuzhiyun } 1157*4882a593Smuzhiyun 1158*4882a593Smuzhiyun (void)name; /* Avoid a warning with non-debug compiles. */ 1159*4882a593Smuzhiyun#ifdef DEBUG 1160*4882a593Smuzhiyun name = XGetAtomName(xpbproxy_dpy, preferred); 1161*4882a593Smuzhiyun 1162*4882a593Smuzhiyun if (name) { 1163*4882a593Smuzhiyun DebugF("requesting %s\n", name); 1164*4882a593Smuzhiyun } 1165*4882a593Smuzhiyun#endif 1166*4882a593Smuzhiyun request_atom = preferred; 1167*4882a593Smuzhiyun XConvertSelection(xpbproxy_dpy, selection, preferred, selection, 1168*4882a593Smuzhiyun _selection_window, CurrentTime); 1169*4882a593Smuzhiyun} 1170*4882a593Smuzhiyun 1171*4882a593Smuzhiyun/* This handles the image type of selection (typically in CLIPBOARD). */ 1172*4882a593Smuzhiyun/* We convert to a TIFF, so that other applications can paste more easily. */ 1173*4882a593Smuzhiyun- (void) handle_image: (struct propdata *)pdata pasteboard:(NSPasteboard *)pb 1174*4882a593Smuzhiyun{ 1175*4882a593Smuzhiyun NSArray *pbtypes; 1176*4882a593Smuzhiyun NSUInteger length; 1177*4882a593Smuzhiyun NSData *data, *tiff; 1178*4882a593Smuzhiyun NSBitmapImageRep *bmimage; 1179*4882a593Smuzhiyun 1180*4882a593Smuzhiyun TRACE(); 1181*4882a593Smuzhiyun 1182*4882a593Smuzhiyun length = pdata->length; 1183*4882a593Smuzhiyun data = [[NSData alloc] initWithBytes:pdata->data length:length]; 1184*4882a593Smuzhiyun 1185*4882a593Smuzhiyun if (nil == data) { 1186*4882a593Smuzhiyun DebugF("unable to create NSData object!\n"); 1187*4882a593Smuzhiyun return; 1188*4882a593Smuzhiyun } 1189*4882a593Smuzhiyun 1190*4882a593Smuzhiyun DebugF("data retainCount before NSBitmapImageRep initWithData: %lu\n", 1191*4882a593Smuzhiyun [data retainCount]); 1192*4882a593Smuzhiyun 1193*4882a593Smuzhiyun bmimage = [[NSBitmapImageRep alloc] initWithData:data]; 1194*4882a593Smuzhiyun 1195*4882a593Smuzhiyun if (nil == bmimage) { 1196*4882a593Smuzhiyun [data autorelease]; 1197*4882a593Smuzhiyun DebugF("unable to create NSBitmapImageRep!\n"); 1198*4882a593Smuzhiyun return; 1199*4882a593Smuzhiyun } 1200*4882a593Smuzhiyun 1201*4882a593Smuzhiyun DebugF("data retainCount after NSBitmapImageRep initWithData: %lu\n", 1202*4882a593Smuzhiyun [data retainCount]); 1203*4882a593Smuzhiyun 1204*4882a593Smuzhiyun @try 1205*4882a593Smuzhiyun { 1206*4882a593Smuzhiyun tiff = [bmimage TIFFRepresentation]; 1207*4882a593Smuzhiyun } 1208*4882a593Smuzhiyun 1209*4882a593Smuzhiyun @catch (NSException *e) 1210*4882a593Smuzhiyun { 1211*4882a593Smuzhiyun DebugF("NSTIFFException!\n"); 1212*4882a593Smuzhiyun [data autorelease]; 1213*4882a593Smuzhiyun [bmimage autorelease]; 1214*4882a593Smuzhiyun return; 1215*4882a593Smuzhiyun } 1216*4882a593Smuzhiyun 1217*4882a593Smuzhiyun DebugF("bmimage retainCount after TIFFRepresentation %lu\n", 1218*4882a593Smuzhiyun [bmimage retainCount]); 1219*4882a593Smuzhiyun 1220*4882a593Smuzhiyun pbtypes = [NSArray arrayWithObjects:NSTIFFPboardType, nil]; 1221*4882a593Smuzhiyun 1222*4882a593Smuzhiyun if (nil == pbtypes) { 1223*4882a593Smuzhiyun [data autorelease]; 1224*4882a593Smuzhiyun [bmimage autorelease]; 1225*4882a593Smuzhiyun return; 1226*4882a593Smuzhiyun } 1227*4882a593Smuzhiyun 1228*4882a593Smuzhiyun [pb declareTypes:pbtypes owner:nil]; 1229*4882a593Smuzhiyun if (YES != [pb setData:tiff forType:NSTIFFPboardType]) { 1230*4882a593Smuzhiyun DebugF("writing pasteboard data failed!\n"); 1231*4882a593Smuzhiyun } 1232*4882a593Smuzhiyun 1233*4882a593Smuzhiyun [data autorelease]; 1234*4882a593Smuzhiyun 1235*4882a593Smuzhiyun DebugF("bmimage retainCount before release %lu\n", [bmimage retainCount]); 1236*4882a593Smuzhiyun 1237*4882a593Smuzhiyun [bmimage autorelease]; 1238*4882a593Smuzhiyun} 1239*4882a593Smuzhiyun 1240*4882a593Smuzhiyun/* This handles the UTF8_STRING type of selection. */ 1241*4882a593Smuzhiyun- (void) handle_utf8_string:(struct propdata *)pdata pasteboard:(NSPasteboard 1242*4882a593Smuzhiyun *)pb 1243*4882a593Smuzhiyun{ 1244*4882a593Smuzhiyun NSString *string; 1245*4882a593Smuzhiyun NSArray *pbtypes; 1246*4882a593Smuzhiyun 1247*4882a593Smuzhiyun TRACE(); 1248*4882a593Smuzhiyun 1249*4882a593Smuzhiyun string = 1250*4882a593Smuzhiyun [[NSString alloc] initWithBytes:pdata->data length:pdata->length 1251*4882a593Smuzhiyun encoding: 1252*4882a593Smuzhiyun NSUTF8StringEncoding]; 1253*4882a593Smuzhiyun 1254*4882a593Smuzhiyun if (nil == string) 1255*4882a593Smuzhiyun return; 1256*4882a593Smuzhiyun 1257*4882a593Smuzhiyun pbtypes = [NSArray arrayWithObjects:NSStringPboardType, nil]; 1258*4882a593Smuzhiyun 1259*4882a593Smuzhiyun if (nil == pbtypes) { 1260*4882a593Smuzhiyun [string autorelease]; 1261*4882a593Smuzhiyun return; 1262*4882a593Smuzhiyun } 1263*4882a593Smuzhiyun 1264*4882a593Smuzhiyun [pb declareTypes:pbtypes owner:nil]; 1265*4882a593Smuzhiyun 1266*4882a593Smuzhiyun if (YES != [pb setString:string forType:NSStringPboardType]) { 1267*4882a593Smuzhiyun ErrorF("pasteboard setString:forType: failed!\n"); 1268*4882a593Smuzhiyun } 1269*4882a593Smuzhiyun [string autorelease]; 1270*4882a593Smuzhiyun DebugF("done handling utf8 string\n"); 1271*4882a593Smuzhiyun} 1272*4882a593Smuzhiyun 1273*4882a593Smuzhiyun/* This handles the STRING type, which should be in Latin-1. */ 1274*4882a593Smuzhiyun- (void) handle_string: (struct propdata *)pdata pasteboard:(NSPasteboard *) 1275*4882a593Smuzhiyun pb 1276*4882a593Smuzhiyun{ 1277*4882a593Smuzhiyun NSString *string; 1278*4882a593Smuzhiyun NSArray *pbtypes; 1279*4882a593Smuzhiyun 1280*4882a593Smuzhiyun TRACE(); 1281*4882a593Smuzhiyun 1282*4882a593Smuzhiyun string = 1283*4882a593Smuzhiyun [[NSString alloc] initWithBytes:pdata->data length:pdata->length 1284*4882a593Smuzhiyun encoding: 1285*4882a593Smuzhiyun NSISOLatin1StringEncoding]; 1286*4882a593Smuzhiyun 1287*4882a593Smuzhiyun if (nil == string) 1288*4882a593Smuzhiyun return; 1289*4882a593Smuzhiyun 1290*4882a593Smuzhiyun pbtypes = [NSArray arrayWithObjects:NSStringPboardType, nil]; 1291*4882a593Smuzhiyun 1292*4882a593Smuzhiyun if (nil == pbtypes) { 1293*4882a593Smuzhiyun [string autorelease]; 1294*4882a593Smuzhiyun return; 1295*4882a593Smuzhiyun } 1296*4882a593Smuzhiyun 1297*4882a593Smuzhiyun [pb declareTypes:pbtypes owner:nil]; 1298*4882a593Smuzhiyun if (YES != [pb setString:string forType:NSStringPboardType]) { 1299*4882a593Smuzhiyun ErrorF("pasteboard setString:forType failed in handle_string!\n"); 1300*4882a593Smuzhiyun } 1301*4882a593Smuzhiyun [string autorelease]; 1302*4882a593Smuzhiyun} 1303*4882a593Smuzhiyun 1304*4882a593Smuzhiyun/* This is called when the selection is completely retrieved from another client. */ 1305*4882a593Smuzhiyun/* Warning: this frees the propdata. */ 1306*4882a593Smuzhiyun- (void) handle_selection:(Atom)selection type:(Atom)type propdata:(struct 1307*4882a593Smuzhiyun propdata 1308*4882a593Smuzhiyun *)pdata 1309*4882a593Smuzhiyun{ 1310*4882a593Smuzhiyun NSPasteboard *pb; 1311*4882a593Smuzhiyun 1312*4882a593Smuzhiyun TRACE(); 1313*4882a593Smuzhiyun 1314*4882a593Smuzhiyun pb = [NSPasteboard generalPasteboard]; 1315*4882a593Smuzhiyun 1316*4882a593Smuzhiyun if (nil == pb) { 1317*4882a593Smuzhiyun [self copy_completed:selection]; 1318*4882a593Smuzhiyun free_propdata(pdata); 1319*4882a593Smuzhiyun return; 1320*4882a593Smuzhiyun } 1321*4882a593Smuzhiyun 1322*4882a593Smuzhiyun /* 1323*4882a593Smuzhiyun * Some apps it seems set the type to TARGETS instead of ATOM, such as Eterm. 1324*4882a593Smuzhiyun * These aren't ICCCM compliant apps, but we need these to work... 1325*4882a593Smuzhiyun */ 1326*4882a593Smuzhiyun if (request_atom == atoms->targets 1327*4882a593Smuzhiyun && (type == atoms->atom || type == atoms->targets)) { 1328*4882a593Smuzhiyun [self handle_targets:selection propdata:pdata]; 1329*4882a593Smuzhiyun free_propdata(pdata); 1330*4882a593Smuzhiyun return; 1331*4882a593Smuzhiyun } 1332*4882a593Smuzhiyun else if (type == atoms->image_png) { 1333*4882a593Smuzhiyun [self handle_image:pdata pasteboard:pb]; 1334*4882a593Smuzhiyun } 1335*4882a593Smuzhiyun else if (type == atoms->image_jpeg) { 1336*4882a593Smuzhiyun [self handle_image:pdata pasteboard:pb]; 1337*4882a593Smuzhiyun } 1338*4882a593Smuzhiyun else if (type == atoms->utf8_string) { 1339*4882a593Smuzhiyun [self handle_utf8_string:pdata pasteboard:pb]; 1340*4882a593Smuzhiyun } 1341*4882a593Smuzhiyun else if (type == atoms->string) { 1342*4882a593Smuzhiyun [self handle_string:pdata pasteboard:pb]; 1343*4882a593Smuzhiyun } 1344*4882a593Smuzhiyun 1345*4882a593Smuzhiyun free_propdata(pdata); 1346*4882a593Smuzhiyun 1347*4882a593Smuzhiyun [self copy_completed:selection]; 1348*4882a593Smuzhiyun} 1349*4882a593Smuzhiyun 1350*4882a593Smuzhiyun- (void) copy_completed:(Atom)selection 1351*4882a593Smuzhiyun{ 1352*4882a593Smuzhiyun TRACE(); 1353*4882a593Smuzhiyun char *name; 1354*4882a593Smuzhiyun 1355*4882a593Smuzhiyun (void)name; /* Avoid warning with non-debug compiles. */ 1356*4882a593Smuzhiyun#ifdef DEBUG 1357*4882a593Smuzhiyun name = XGetAtomName(xpbproxy_dpy, selection); 1358*4882a593Smuzhiyun if (name) { 1359*4882a593Smuzhiyun DebugF("copy_completed: %s\n", name); 1360*4882a593Smuzhiyun XFree(name); 1361*4882a593Smuzhiyun } 1362*4882a593Smuzhiyun#endif 1363*4882a593Smuzhiyun 1364*4882a593Smuzhiyun if (selection == atoms->primary && pending_copy > 0) { 1365*4882a593Smuzhiyun --pending_copy; 1366*4882a593Smuzhiyun if (pending_copy > 0) { 1367*4882a593Smuzhiyun /* Copy PRIMARY again. */ 1368*4882a593Smuzhiyun [self x_copy_request_targets]; 1369*4882a593Smuzhiyun return; 1370*4882a593Smuzhiyun } 1371*4882a593Smuzhiyun } 1372*4882a593Smuzhiyun else if (selection == atoms->clipboard && pending_clipboard > 0) { 1373*4882a593Smuzhiyun --pending_clipboard; 1374*4882a593Smuzhiyun if (pending_clipboard > 0) { 1375*4882a593Smuzhiyun /* Copy CLIPBOARD. */ 1376*4882a593Smuzhiyun [self claim_clipboard]; 1377*4882a593Smuzhiyun return; 1378*4882a593Smuzhiyun } 1379*4882a593Smuzhiyun else { 1380*4882a593Smuzhiyun /* We got the final data. Now set pbproxy as the owner. */ 1381*4882a593Smuzhiyun [self own_clipboard]; 1382*4882a593Smuzhiyun return; 1383*4882a593Smuzhiyun } 1384*4882a593Smuzhiyun } 1385*4882a593Smuzhiyun 1386*4882a593Smuzhiyun /* 1387*4882a593Smuzhiyun * We had 1 or more primary in progress, and the clipboard arrived 1388*4882a593Smuzhiyun * while we were busy. 1389*4882a593Smuzhiyun */ 1390*4882a593Smuzhiyun if (pending_clipboard > 0) { 1391*4882a593Smuzhiyun [self claim_clipboard]; 1392*4882a593Smuzhiyun } 1393*4882a593Smuzhiyun} 1394*4882a593Smuzhiyun 1395*4882a593Smuzhiyun- (void) reload_preferences 1396*4882a593Smuzhiyun{ 1397*4882a593Smuzhiyun /* 1398*4882a593Smuzhiyun * It's uncertain how we could handle the synchronization failing, so cast to void. 1399*4882a593Smuzhiyun * The prefs_get_bool should fall back to defaults if the org.x.X11 plist doesn't exist or is invalid. 1400*4882a593Smuzhiyun */ 1401*4882a593Smuzhiyun (void)CFPreferencesAppSynchronize(app_prefs_domain_cfstr); 1402*4882a593Smuzhiyun#ifdef STANDALONE_XPBPROXY 1403*4882a593Smuzhiyun if (xpbproxy_is_standalone) 1404*4882a593Smuzhiyun pbproxy_prefs.active = YES; 1405*4882a593Smuzhiyun else 1406*4882a593Smuzhiyun#endif 1407*4882a593Smuzhiyun pbproxy_prefs.active = prefs_get_bool(CFSTR( 1408*4882a593Smuzhiyun "sync_pasteboard"), 1409*4882a593Smuzhiyun pbproxy_prefs.active); 1410*4882a593Smuzhiyun pbproxy_prefs.primary_on_grab = 1411*4882a593Smuzhiyun prefs_get_bool(CFSTR( 1412*4882a593Smuzhiyun "sync_primary_on_select"), 1413*4882a593Smuzhiyun pbproxy_prefs.primary_on_grab); 1414*4882a593Smuzhiyun pbproxy_prefs.clipboard_to_pasteboard = 1415*4882a593Smuzhiyun prefs_get_bool(CFSTR( 1416*4882a593Smuzhiyun "sync_clipboard_to_pasteboard"), 1417*4882a593Smuzhiyun pbproxy_prefs.clipboard_to_pasteboard); 1418*4882a593Smuzhiyun pbproxy_prefs.pasteboard_to_primary = 1419*4882a593Smuzhiyun prefs_get_bool(CFSTR( 1420*4882a593Smuzhiyun "sync_pasteboard_to_primary"), 1421*4882a593Smuzhiyun pbproxy_prefs.pasteboard_to_primary); 1422*4882a593Smuzhiyun pbproxy_prefs.pasteboard_to_clipboard = 1423*4882a593Smuzhiyun prefs_get_bool(CFSTR( 1424*4882a593Smuzhiyun "sync_pasteboard_to_clipboard"), 1425*4882a593Smuzhiyun pbproxy_prefs.pasteboard_to_clipboard); 1426*4882a593Smuzhiyun 1427*4882a593Smuzhiyun /* This is used for debugging. */ 1428*4882a593Smuzhiyun //dump_prefs(); 1429*4882a593Smuzhiyun 1430*4882a593Smuzhiyun if (pbproxy_prefs.active && pbproxy_prefs.primary_on_grab && 1431*4882a593Smuzhiyun !xpbproxy_have_xfixes) { 1432*4882a593Smuzhiyun ErrorF( 1433*4882a593Smuzhiyun "Disabling sync_primary_on_select functionality due to missing XFixes extension.\n"); 1434*4882a593Smuzhiyun pbproxy_prefs.primary_on_grab = NO; 1435*4882a593Smuzhiyun } 1436*4882a593Smuzhiyun 1437*4882a593Smuzhiyun /* Claim or release the CLIPBOARD_MANAGER atom */ 1438*4882a593Smuzhiyun if (![self set_clipboard_manager_status:(pbproxy_prefs.active && 1439*4882a593Smuzhiyun pbproxy_prefs. 1440*4882a593Smuzhiyun clipboard_to_pasteboard)]) 1441*4882a593Smuzhiyun pbproxy_prefs.clipboard_to_pasteboard = NO; 1442*4882a593Smuzhiyun 1443*4882a593Smuzhiyun if (pbproxy_prefs.active && pbproxy_prefs.clipboard_to_pasteboard) 1444*4882a593Smuzhiyun [self claim_clipboard]; 1445*4882a593Smuzhiyun} 1446*4882a593Smuzhiyun 1447*4882a593Smuzhiyun- (BOOL) is_active 1448*4882a593Smuzhiyun{ 1449*4882a593Smuzhiyun return pbproxy_prefs.active; 1450*4882a593Smuzhiyun} 1451*4882a593Smuzhiyun 1452*4882a593Smuzhiyun/* NSPasteboard-required methods */ 1453*4882a593Smuzhiyun 1454*4882a593Smuzhiyun- (void) paste:(id)sender 1455*4882a593Smuzhiyun{ 1456*4882a593Smuzhiyun TRACE(); 1457*4882a593Smuzhiyun} 1458*4882a593Smuzhiyun 1459*4882a593Smuzhiyun- (void) pasteboard:(NSPasteboard *)pb provideDataForType:(NSString *)type 1460*4882a593Smuzhiyun{ 1461*4882a593Smuzhiyun TRACE(); 1462*4882a593Smuzhiyun} 1463*4882a593Smuzhiyun 1464*4882a593Smuzhiyun- (void) pasteboardChangedOwner:(NSPasteboard *)pb 1465*4882a593Smuzhiyun{ 1466*4882a593Smuzhiyun TRACE(); 1467*4882a593Smuzhiyun 1468*4882a593Smuzhiyun /* Right now we don't care with this. */ 1469*4882a593Smuzhiyun} 1470*4882a593Smuzhiyun 1471*4882a593Smuzhiyun/* Allocation */ 1472*4882a593Smuzhiyun 1473*4882a593Smuzhiyun- (id) init 1474*4882a593Smuzhiyun{ 1475*4882a593Smuzhiyun unsigned long pixel; 1476*4882a593Smuzhiyun 1477*4882a593Smuzhiyun self = [super init]; 1478*4882a593Smuzhiyun if (self == nil) 1479*4882a593Smuzhiyun return nil; 1480*4882a593Smuzhiyun 1481*4882a593Smuzhiyun atoms->primary = XInternAtom(xpbproxy_dpy, "PRIMARY", False); 1482*4882a593Smuzhiyun atoms->clipboard = XInternAtom(xpbproxy_dpy, "CLIPBOARD", False); 1483*4882a593Smuzhiyun atoms->text = XInternAtom(xpbproxy_dpy, "TEXT", False); 1484*4882a593Smuzhiyun atoms->utf8_string = XInternAtom(xpbproxy_dpy, "UTF8_STRING", False); 1485*4882a593Smuzhiyun atoms->string = XInternAtom(xpbproxy_dpy, "STRING", False); 1486*4882a593Smuzhiyun atoms->targets = XInternAtom(xpbproxy_dpy, "TARGETS", False); 1487*4882a593Smuzhiyun atoms->multiple = XInternAtom(xpbproxy_dpy, "MULTIPLE", False); 1488*4882a593Smuzhiyun atoms->cstring = XInternAtom(xpbproxy_dpy, "CSTRING", False); 1489*4882a593Smuzhiyun atoms->image_png = XInternAtom(xpbproxy_dpy, "image/png", False); 1490*4882a593Smuzhiyun atoms->image_jpeg = XInternAtom(xpbproxy_dpy, "image/jpeg", False); 1491*4882a593Smuzhiyun atoms->incr = XInternAtom(xpbproxy_dpy, "INCR", False); 1492*4882a593Smuzhiyun atoms->atom = XInternAtom(xpbproxy_dpy, "ATOM", False); 1493*4882a593Smuzhiyun atoms->clipboard_manager = XInternAtom(xpbproxy_dpy, "CLIPBOARD_MANAGER", 1494*4882a593Smuzhiyun False); 1495*4882a593Smuzhiyun atoms->compound_text = XInternAtom(xpbproxy_dpy, "COMPOUND_TEXT", False); 1496*4882a593Smuzhiyun atoms->atom_pair = XInternAtom(xpbproxy_dpy, "ATOM_PAIR", False); 1497*4882a593Smuzhiyun 1498*4882a593Smuzhiyun pixel = BlackPixel(xpbproxy_dpy, DefaultScreen(xpbproxy_dpy)); 1499*4882a593Smuzhiyun _selection_window = 1500*4882a593Smuzhiyun XCreateSimpleWindow(xpbproxy_dpy, DefaultRootWindow(xpbproxy_dpy), 1501*4882a593Smuzhiyun 0, 0, 1, 1, 0, pixel, pixel); 1502*4882a593Smuzhiyun 1503*4882a593Smuzhiyun /* This is used to get PropertyNotify events when doing INCR transfers. */ 1504*4882a593Smuzhiyun XSelectInput(xpbproxy_dpy, _selection_window, PropertyChangeMask); 1505*4882a593Smuzhiyun 1506*4882a593Smuzhiyun request_atom = None; 1507*4882a593Smuzhiyun 1508*4882a593Smuzhiyun init_propdata(&pending.propdata); 1509*4882a593Smuzhiyun pending.requestor = None; 1510*4882a593Smuzhiyun pending.selection = None; 1511*4882a593Smuzhiyun 1512*4882a593Smuzhiyun pending_copy = 0; 1513*4882a593Smuzhiyun pending_clipboard = 0; 1514*4882a593Smuzhiyun 1515*4882a593Smuzhiyun if (xpbproxy_have_xfixes) 1516*4882a593Smuzhiyun XFixesSelectSelectionInput(xpbproxy_dpy, _selection_window, 1517*4882a593Smuzhiyun atoms->primary, 1518*4882a593Smuzhiyun XFixesSetSelectionOwnerNotifyMask); 1519*4882a593Smuzhiyun 1520*4882a593Smuzhiyun [self reload_preferences]; 1521*4882a593Smuzhiyun 1522*4882a593Smuzhiyun return self; 1523*4882a593Smuzhiyun} 1524*4882a593Smuzhiyun 1525*4882a593Smuzhiyun- (void) dealloc 1526*4882a593Smuzhiyun{ 1527*4882a593Smuzhiyun if (None != _selection_window) { 1528*4882a593Smuzhiyun XDestroyWindow(xpbproxy_dpy, _selection_window); 1529*4882a593Smuzhiyun _selection_window = None; 1530*4882a593Smuzhiyun } 1531*4882a593Smuzhiyun 1532*4882a593Smuzhiyun free_propdata(&pending.propdata); 1533*4882a593Smuzhiyun 1534*4882a593Smuzhiyun [super dealloc]; 1535*4882a593Smuzhiyun} 1536*4882a593Smuzhiyun 1537*4882a593Smuzhiyun@end 1538