1 /* x-selection.m -- proxies between NSPasteboard and X11 selections
3 * Copyright (c) 2002-2012 Apple Inc. All rights reserved.
5 * Permission is hereby granted, free of charge, to any person
6 * obtaining a copy of this software and associated documentation files
7 * (the "Software"), to deal in the Software without restriction,
8 * including without limitation the rights to use, copy, modify, merge,
9 * publish, distribute, sublicense, and/or sell copies of the Software,
10 * and to permit persons to whom the Software is furnished to do so,
11 * subject to the following conditions:
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT
20 * HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 * DEALINGS IN THE SOFTWARE.
25 * Except as contained in this notice, the name(s) of the above
26 * copyright holders shall not be used in advertising or otherwise to
27 * promote the sale, use or other dealings in this Software without
28 * prior written authorization.
31 #import "x-selection.h"
35 #include <X11/Xatom.h>
36 #include <X11/Xutil.h>
37 #import <AppKit/NSGraphics.h>
38 #import <AppKit/NSImage.h>
39 #import <AppKit/NSBitmapImageRep.h>
42 * The basic design of the pbproxy code is as follows.
44 * When a client selects text, say from an xterm - we only copy it when the
45 * X11 Edit->Copy menu item is pressed or the shortcut activated. In this
46 * case we take the PRIMARY selection, and set it as the NSPasteboard data.
48 * When an X11 client copies something to the CLIPBOARD, pbproxy greedily grabs
49 * the data, sets it as the NSPasteboard data, and finally sets itself as
50 * owner of the CLIPBOARD.
52 * When an X11 window is activated we check to see if the NSPasteboard has
53 * changed. If the NSPasteboard has changed, then we set pbproxy as owner
54 * of the PRIMARY and CLIPBOARD and respond to requests for text and images.
56 * The behavior is now dynamic since the information above was written.
57 * The behavior is now dependent on the pbproxy_prefs below.
62 * 1. handle MULTIPLE - I need to study the ICCCM further, and find a test app.
63 * 2. Handle NSPasteboard updates immediately, not on active/inactive
64 * - Open xterm, run 'cat readme.txt | pbcopy'
69 BOOL primary_on_grab; /* This is provided as an option for people who
70 * want it and has issues that won't ever be
71 * addressed to make it *always* work.
73 BOOL clipboard_to_pasteboard;
74 BOOL pasteboard_to_primary;
75 BOOL pasteboard_to_clipboard;
76 } pbproxy_prefs = { YES, NO, YES, YES, YES };
78 @implementation x_selection
80 static struct propdata null_propdata = {
88 ErrorF("pbproxy preferences:\n"
90 "\tprimary_on_grab %u\n"
91 "\tclipboard_to_pasteboard %u\n"
92 "\tpasteboard_to_primary %u\n"
93 "\tpasteboard_to_clipboard %u\n",
95 pbproxy_prefs.primary_on_grab,
96 pbproxy_prefs.clipboard_to_pasteboard,
97 pbproxy_prefs.pasteboard_to_primary,
98 pbproxy_prefs.pasteboard_to_clipboard);
102 extern CFStringRef app_prefs_domain_cfstr;
105 prefs_get_bool(CFStringRef key, BOOL defaultValue)
109 value = CFPreferencesGetAppBooleanValue(key, app_prefs_domain_cfstr, &ok);
111 return ok ? (BOOL)value : defaultValue;
115 init_propdata(struct propdata *pdata)
117 *pdata = null_propdata;
121 free_propdata(struct propdata *pdata)
124 *pdata = null_propdata;
128 * Return True if an error occurs. Return False if pdata has data
130 * The property is only deleted when bytesleft is 0 if delete is True.
133 get_property(Window win, Atom property, struct propdata *pdata, Bool delete,
137 unsigned long numitems, bytesleft = 0;
139 /* This is used to test the growth handling. */
140 unsigned long length = 4UL;
142 unsigned long length = (100000UL + 3) / 4;
144 unsigned char *buf = NULL, *chunk = NULL;
145 size_t buflen = 0, chunkbytesize = 0;
150 if (None == property)
154 unsigned long newbuflen = 0;
155 unsigned char *newbuf = NULL;
158 ErrorF("bytesleft %lu\n", bytesleft);
161 if (Success != XGetWindowProperty(xpbproxy_dpy, win, property,
162 offset, length, delete,
164 type, &format, &numitems,
165 &bytesleft, &chunk)) {
166 DebugF("Error while getting window property.\n");
167 *pdata = null_propdata;
173 ErrorF("format %d numitems %lu bytesleft %lu\n",
174 format, numitems, bytesleft);
176 ErrorF("type %s\n", XGetAtomName(xpbproxy_dpy, *type));
179 /* Format is the number of bits. */
181 chunkbytesize = numitems;
182 else if (format == 16)
183 chunkbytesize = numitems * sizeof(short);
184 else if (format == 32)
185 chunkbytesize = numitems * sizeof(long);
188 ErrorF("chunkbytesize %zu\n", chunkbytesize);
190 newbuflen = buflen + chunkbytesize;
192 newbuf = realloc(buf, newbuflen);
194 if (NULL == newbuf) {
200 memcpy(newbuf + buflen, chunk, chunkbytesize);
204 /* offset is a multiple of 32 bits*/
205 offset += chunkbytesize / 4;
213 ErrorF("bytesleft %lu\n", bytesleft);
215 } while (bytesleft > 0);
218 pdata->length = buflen;
219 pdata->format = format;
221 return /*success*/ False;
224 /* Implementation methods */
226 /* This finds the preferred type from a TARGETS list.*/
227 - (Atom) find_preferred:(struct propdata *)pdata
231 Bool png = False, jpeg = False, utf8 = False, string = False;
235 if (pdata->format != 32) {
237 "Atom list is expected to be formatted as an array of 32bit values.\n");
241 for (i = 0, step = sizeof(long); i < pdata->length; i += step) {
242 a = (Atom) * (long *)(pdata->data + i);
244 if (a == atoms->image_png) {
247 else if (a == atoms->image_jpeg) {
250 else if (a == atoms->utf8_string) {
253 else if (a == atoms->string) {
257 char *type = XGetAtomName(xpbproxy_dpy, a);
259 DebugF("Unhandled X11 mime type: %s", type);
265 /*We prefer PNG over strings, and UTF8 over a Latin-1 string.*/
267 return atoms->image_png;
270 return atoms->image_jpeg;
273 return atoms->utf8_string;
276 return atoms->string;
278 /* This is evidently something we don't know how to handle.*/
282 /* Return True if this is an INCR-style transfer. */
283 - (Bool) is_incr_type:(XSelectionEvent *)e
287 unsigned long numitems = 0UL, bytesleft = 0UL;
288 unsigned char *chunk;
292 if (Success != XGetWindowProperty(xpbproxy_dpy, e->requestor, e->property,
293 /*offset*/ 0L, /*length*/ 4UL,
295 AnyPropertyType, &seltype, &format,
296 &numitems, &bytesleft, &chunk)) {
303 return (seltype == atoms->incr) ? True : False;
307 * This should be called after a selection has been copied,
308 * or when the selection is unfinished before a transfer completes.
310 - (void) release_pending
314 free_propdata(&pending.propdata);
315 pending.requestor = None;
316 pending.selection = None;
319 /* Return True if an error occurs during an append.*/
320 /* Return False if the append succeeds. */
321 - (Bool) append_to_pending:(struct propdata *)pdata requestor:(Window)
324 unsigned char *newdata;
329 if (requestor != pending.requestor) {
330 [self release_pending];
331 pending.requestor = requestor;
334 newlength = pending.propdata.length + pdata->length;
335 newdata = realloc(pending.propdata.data, newlength);
337 if (NULL == newdata) {
338 perror("realloc propdata");
339 [self release_pending];
343 memcpy(newdata + pending.propdata.length, pdata->data, pdata->length);
344 pending.propdata.data = newdata;
345 pending.propdata.length = newlength;
350 /* Called when X11 becomes active (i.e. has key focus) */
351 - (void) x_active:(Time)timestamp
353 static NSInteger changeCount;
359 pb = [NSPasteboard generalPasteboard];
364 countNow = [pb changeCount];
366 if (countNow != changeCount) {
367 DebugF("changed pasteboard!\n");
368 changeCount = countNow;
370 if (pbproxy_prefs.pasteboard_to_primary) {
371 XSetSelectionOwner(xpbproxy_dpy, atoms->primary,
376 if (pbproxy_prefs.pasteboard_to_clipboard) {
377 [self own_clipboard];
382 /*gstaplin: we should perhaps investigate something like this branch above...*/
383 if ([_pasteboard availableTypeFromArray: _known_types] != nil) {
384 /* Pasteboard has data we should proxy; I think it makes
385 sense to put it on both CLIPBOARD and PRIMARY */
387 XSetSelectionOwner(xpbproxy_dpy, atoms->clipboard,
388 _selection_window, timestamp);
389 XSetSelectionOwner(xpbproxy_dpy, atoms->primary,
390 _selection_window, timestamp);
395 /* Called when X11 loses key focus */
396 - (void) x_inactive:(Time)timestamp
401 /* This requests the TARGETS list from the PRIMARY selection owner. */
402 - (void) x_copy_request_targets
406 request_atom = atoms->targets;
407 XConvertSelection(xpbproxy_dpy, atoms->primary, atoms->targets,
408 atoms->primary, _selection_window, CurrentTime);
411 /* Called when the Edit/Copy item on the main X11 menubar is selected
412 * and no appkit window claims it. */
413 - (void) x_copy:(Time)timestamp
419 w = XGetSelectionOwner(xpbproxy_dpy, atoms->primary);
424 if (1 == pending_copy) {
426 * There are no other copy operations in progress, so we
427 * can proceed safely. Otherwise the copy_completed method
428 * will see that the pending_copy is > 1, and do another copy.
430 [self x_copy_request_targets];
435 /* Set pbproxy as owner of the SELECTION_MANAGER selection.
436 * This prevents tools like xclipboard from causing havoc.
437 * Returns TRUE on success
439 - (BOOL) set_clipboard_manager_status:(BOOL)value
443 Window owner = XGetSelectionOwner(xpbproxy_dpy, atoms->clipboard_manager);
446 if (owner == _selection_window)
451 "A clipboard manager using window 0x%lx already owns the clipboard selection. "
452 "pbproxy will not sync clipboard to pasteboard.\n", owner);
456 XSetSelectionOwner(xpbproxy_dpy, atoms->clipboard_manager,
459 return (_selection_window ==
460 XGetSelectionOwner(xpbproxy_dpy, atoms->clipboard_manager));
463 if (owner != _selection_window)
466 XSetSelectionOwner(xpbproxy_dpy, atoms->clipboard_manager, None,
469 XGetSelectionOwner(xpbproxy_dpy, atoms->clipboard_manager));
476 * This occurs when we previously owned a selection,
477 * and then lost it from another client.
479 - (void) clear_event:(XSelectionClearEvent *)e
484 DebugF("e->selection %s\n", XGetAtomName(xpbproxy_dpy, e->selection));
486 if (e->selection == atoms->clipboard) {
488 * We lost ownership of the CLIPBOARD.
492 if (1 == pending_clipboard) {
493 /* Claim the clipboard contents from the new owner. */
494 [self claim_clipboard];
497 else if (e->selection == atoms->clipboard_manager) {
498 if (pbproxy_prefs.clipboard_to_pasteboard) {
499 /* Another CLIPBOARD_MANAGER has set itself as owner. Disable syncing
502 ErrorF("Another clipboard manager was started! "
503 "xpbproxy is disabling syncing with clipboard.\n");
504 pbproxy_prefs.clipboard_to_pasteboard = NO;
510 * We greedily acquire the clipboard after it changes, and on startup.
512 - (void) claim_clipboard
518 if (!pbproxy_prefs.clipboard_to_pasteboard)
521 owner = XGetSelectionOwner(xpbproxy_dpy, atoms->clipboard);
524 * The owner probably died or we are just starting up pbproxy.
525 * Set pbproxy's _selection_window as the owner, and continue.
527 DebugF("No clipboard owner.\n");
528 [self copy_completed:atoms->clipboard];
531 else if (owner == _selection_window) {
532 [self copy_completed:atoms->clipboard];
536 DebugF("requesting targets\n");
538 request_atom = atoms->targets;
539 XConvertSelection(xpbproxy_dpy, atoms->clipboard, atoms->targets,
540 atoms->clipboard, _selection_window, CurrentTime);
541 XFlush(xpbproxy_dpy);
542 /* Now we will get a SelectionNotify event in the future. */
545 /* Greedily acquire the clipboard. */
546 - (void) own_clipboard
551 /* We should perhaps have a boundary limit on the number of iterations... */
553 XSetSelectionOwner(xpbproxy_dpy, atoms->clipboard, _selection_window,
555 } while (_selection_window != XGetSelectionOwner(xpbproxy_dpy,
559 - (void) init_reply:(XEvent *)reply request:(XSelectionRequestEvent *)e
561 reply->xselection.type = SelectionNotify;
562 reply->xselection.selection = e->selection;
563 reply->xselection.target = e->target;
564 reply->xselection.requestor = e->requestor;
565 reply->xselection.time = e->time;
566 reply->xselection.property = None;
569 - (void) send_reply:(XEvent *)reply
572 * We are supposed to use an empty event mask, and not propagate
573 * the event, according to the ICCCM.
575 DebugF("reply->xselection.requestor 0x%lx\n", reply->xselection.requestor);
577 XSendEvent(xpbproxy_dpy, reply->xselection.requestor, False, 0, reply);
578 XFlush(xpbproxy_dpy);
582 * This responds to a TARGETS request.
583 * The result is a list of a ATOMs that correspond to the types available
585 * For instance an application might provide a UTF8_STRING and a STRING
586 * (in Latin-1 encoding). The requestor can then make the choice based on
589 - (void) send_targets:(XSelectionRequestEvent *)e pasteboard:(NSPasteboard *)
595 [self init_reply:&reply request:e];
597 pbtypes = [pb types];
599 long list[7]; /* Don't forget to increase this if we handle more types! */
603 * I'm not sure if this is needed, but some toolkits/clients list
604 * TARGETS in response to targets.
606 list[count] = atoms->targets;
609 if ([pbtypes containsObject:NSStringPboardType]) {
610 /* We have a string type that we can convert to UTF8, or Latin-1... */
611 DebugF("NSStringPboardType\n");
612 list[count] = atoms->utf8_string;
614 list[count] = atoms->string;
616 list[count] = atoms->compound_text;
620 /* TODO add the NSPICTPboardType back again, once we have conversion
621 * functionality in send_image.
624 #pragma clang diagnostic push
625 #pragma clang diagnostic ignored "-Wdeprecated-declarations" // NSPICTPboardType
628 if ([pbtypes containsObject:NSPICTPboardType]
629 || [pbtypes containsObject:NSTIFFPboardType]) {
630 /* We can convert a TIFF to a PNG or JPEG. */
631 DebugF("NSTIFFPboardType\n");
632 list[count] = atoms->image_png;
634 list[count] = atoms->image_jpeg;
639 #pragma clang diagnostic pop
643 /* We have a list of ATOMs to send. */
644 XChangeProperty(xpbproxy_dpy, e->requestor, e->property,
646 PropModeReplace, (unsigned char *)list,
649 reply.xselection.property = e->property;
653 [self send_reply:&reply];
656 - (void) send_string:(XSelectionRequestEvent *)e utf8:(BOOL)utf8 pasteboard:(
667 [self init_reply:&reply request:e];
669 pbtypes = [pb types];
671 if (![pbtypes containsObject:NSStringPboardType]) {
672 [self send_reply:&reply];
677 DebugF("pbtypes retainCount after containsObject: %lu\n",
678 [pbtypes retainCount]);
680 DebugF("pbtypes retainCount after containsObject: %u\n",
681 [pbtypes retainCount]);
684 data = [pb stringForType:NSStringPboardType];
687 [self send_reply:&reply];
692 bytes = [data UTF8String];
694 * We don't want the UTF-8 string length here.
695 * We want the length in bytes.
697 length = strlen(bytes);
700 DebugF("UTF-8: %s\n", bytes);
702 DebugF("UTF-8 length: %lu\n", length);
704 DebugF("UTF-8 length: %u\n", length);
710 bytes = [data cStringUsingEncoding:NSISOLatin1StringEncoding];
711 /*WARNING: bytes is not NUL-terminated. */
712 length = [data lengthOfBytesUsingEncoding:NSISOLatin1StringEncoding];
715 DebugF("e->target %s\n", XGetAtomName(xpbproxy_dpy, e->target));
717 XChangeProperty(xpbproxy_dpy, e->requestor, e->property, e->target,
718 8, PropModeReplace, (unsigned char *)bytes, length);
720 reply.xselection.property = e->property;
722 [self send_reply:&reply];
725 - (void) send_compound_text:(XSelectionRequestEvent *)e pasteboard:(
733 [self init_reply:&reply request:e];
735 pbtypes = [pb types];
737 if ([pbtypes containsObject: NSStringPboardType]) {
738 NSString *data = [pb stringForType:NSStringPboardType];
741 * Cast to (void *) to avoid a const warning.
742 * AFAIK Xutf8TextListToTextProperty does not modify the input memory.
744 void *utf8 = (void *)[data UTF8String];
745 char *list[] = { utf8, NULL };
746 XTextProperty textprop;
748 textprop.value = NULL;
750 if (Success == Xutf8TextListToTextProperty(xpbproxy_dpy, list, 1,
754 if (8 != textprop.format)
756 "textprop.format is unexpectedly not 8 - it's %d instead\n",
759 XChangeProperty(xpbproxy_dpy, e->requestor, e->property,
760 atoms->compound_text, textprop.format,
761 PropModeReplace, textprop.value,
764 reply.xselection.property = e->property;
768 XFree(textprop.value);
773 [self send_reply:&reply];
776 /* Finding a test application that uses MULTIPLE has proven to be difficult. */
777 - (void) send_multiple:(XSelectionRequestEvent *)e
783 [self init_reply:&reply request:e];
785 if (None != e->property) {}
787 [self send_reply:&reply];
790 /* Return nil if an error occured. */
791 /* DO NOT retain the encdata for longer than the length of an event response.
792 * The autorelease pool will reuse/free it.
794 - (NSData *) encode_image_data:(NSData *)data type:(NSBitmapImageFileType)
797 NSBitmapImageRep *bmimage = nil;
798 NSData *encdata = nil;
799 NSDictionary *dict = nil;
801 bmimage = [[NSBitmapImageRep alloc] initWithData:data];
806 dict = [[NSDictionary alloc] init];
807 encdata = [bmimage representationUsingType:enctype properties:dict];
809 if (nil == encdata) {
811 [bmimage autorelease];
816 [bmimage autorelease];
821 /* Return YES when an error has occured when trying to send the PICT. */
822 /* The caller should send a default reponse with a property of None when an error occurs. */
823 - (BOOL) send_image_pict_reply:(XSelectionRequestEvent *)e
824 pasteboard:(NSPasteboard *)pb
825 type:(NSBitmapImageFileType)imagetype
829 NSData *data = nil, *encdata = nil;
831 const void *bytes = NULL;
833 img = [[NSImage alloc] initWithPasteboard:pb];
839 data = [img TIFFRepresentation];
843 ErrorF("unable to convert PICT to TIFF!\n");
847 encdata = [self encode_image_data:data type:imagetype];
848 if (nil == encdata) {
853 [self init_reply:&reply request:e];
855 length = [encdata length];
856 bytes = [encdata bytes];
858 XChangeProperty(xpbproxy_dpy, e->requestor, e->property, e->target,
859 8, PropModeReplace, bytes, length);
860 reply.xselection.property = e->property;
862 [self send_reply:&reply];
866 return NO; /*no error*/
869 /* Return YES if an error occured. */
870 /* The caller should send a reply with a property of None when an error occurs. */
871 - (BOOL) send_image_tiff_reply:(XSelectionRequestEvent *)e
872 pasteboard:(NSPasteboard *)pb
873 type:(NSBitmapImageFileType)imagetype
877 NSData *encdata = nil;
879 const void *bytes = NULL;
881 data = [pb dataForType:NSTIFFPboardType];
886 encdata = [self encode_image_data:data type:imagetype];
891 [self init_reply:&reply request:e];
893 length = [encdata length];
894 bytes = [encdata bytes];
896 XChangeProperty(xpbproxy_dpy, e->requestor, e->property, e->target,
897 8, PropModeReplace, bytes, length);
898 reply.xselection.property = e->property;
900 [self send_reply:&reply];
902 return NO; /*no error*/
905 - (void) send_image:(XSelectionRequestEvent *)e pasteboard:(NSPasteboard *)pb
907 NSArray *pbtypes = nil;
908 NSBitmapImageFileType imagetype = NSPNGFileType;
912 if (e->target == atoms->image_png)
913 imagetype = NSPNGFileType;
914 else if (e->target == atoms->image_jpeg)
915 imagetype = NSJPEGFileType;
918 "internal failure in xpbproxy! imagetype being sent isn't PNG or JPEG.\n");
921 pbtypes = [pb types];
924 if ([pbtypes containsObject:NSTIFFPboardType]) {
926 [self send_image_tiff_reply:e pasteboard:pb type:imagetype])
930 #pragma clang diagnostic push
931 #pragma clang diagnostic ignored "-Wdeprecated-declarations" // NSPICTPboardType
933 else if ([pbtypes containsObject:NSPICTPboardType])
935 #pragma clang diagnostic pop
939 [self send_image_pict_reply:e pasteboard:pb type:imagetype])
942 /* Fall through intentionally to the send_none: */
949 - (void)send_none:(XSelectionRequestEvent *)e
955 [self init_reply:&reply request:e];
956 [self send_reply:&reply];
959 /* Another client requested the data or targets of data available from the clipboard. */
960 - (void)request_event:(XSelectionRequestEvent *)e
966 /* TODO We should also keep track of the time of the selection, and
967 * according to the ICCCM "refuse the request" if the event timestamp
968 * is before we owned it.
969 * What should we base the time on? How can we get the current time just
970 * before an XSetSelectionOwner? Is it the server's time, or the clients?
971 * According to the XSelectionRequestEvent manual page, the Time value
972 * may be set to CurrentTime or a time, so that makes it a bit different.
973 * Perhaps we should just punt and ignore races.
976 /*TODO we need a COMPOUND_TEXT test app*/
977 /*TODO we need a MULTIPLE test app*/
979 pb = [NSPasteboard generalPasteboard];
985 if (None != e->target)
986 DebugF("e->target %s\n", XGetAtomName(xpbproxy_dpy, e->target));
988 if (e->target == atoms->targets) {
989 /* The paste requestor wants to know what TARGETS we support. */
990 [self send_targets:e pasteboard:pb];
992 else if (e->target == atoms->multiple) {
994 * This isn't finished, and may never be, unless I can find
997 [self send_multiple:e];
999 else if (e->target == atoms->utf8_string) {
1000 [self send_string:e utf8:YES pasteboard:pb];
1002 else if (e->target == atoms->string) {
1003 [self send_string:e utf8:NO pasteboard:pb];
1005 else if (e->target == atoms->compound_text) {
1006 [self send_compound_text:e pasteboard:pb];
1008 else if (e->target == atoms->multiple) {
1009 [self send_multiple:e];
1011 else if (e->target == atoms->image_png || e->target ==
1012 atoms->image_jpeg) {
1013 [self send_image:e pasteboard:pb];
1020 /* This handles the events resulting from an XConvertSelection request. */
1021 - (void) notify_event:(XSelectionEvent *)e
1024 struct propdata pdata;
1028 [self release_pending];
1030 if (None == e->property) {
1031 DebugF("e->property is None.\n");
1032 [self copy_completed:e->selection];
1033 /* Nothing is selected. */
1038 ErrorF("e->selection %s\n", XGetAtomName(xpbproxy_dpy, e->selection));
1039 ErrorF("e->property %s\n", XGetAtomName(xpbproxy_dpy, e->property));
1042 if ([self is_incr_type:e]) {
1044 * This is an INCR-style transfer, which means that we
1045 * will get the data after a series of PropertyNotify events.
1047 DebugF("is INCR\n");
1049 if (get_property(e->requestor, e->property, &pdata, /*Delete*/ True,
1052 * An error occured, so we should invoke the copy_completed:, but
1053 * not handle_selection:type:propdata:
1055 [self copy_completed:e->selection];
1059 free_propdata(&pdata);
1061 pending.requestor = e->requestor;
1062 pending.selection = e->selection;
1064 DebugF("set pending.requestor to 0x%lx\n", pending.requestor);
1067 if (get_property(e->requestor, e->property, &pdata, /*Delete*/ True,
1069 [self copy_completed:e->selection];
1073 /* We have the complete selection data.*/
1074 [self handle_selection:e->selection type:type propdata:&pdata];
1076 DebugF("handled selection with the first notify_event\n");
1080 /* This is used for INCR transfers. See the ICCCM for the details. */
1081 /* This is used to retrieve PRIMARY and CLIPBOARD selections. */
1082 - (void) property_event:(XPropertyEvent *)e
1084 struct propdata pdata;
1089 if (None != e->atom) {
1091 char *name = XGetAtomName(xpbproxy_dpy, e->atom);
1094 DebugF("e->atom %s\n", name);
1100 if (None != pending.requestor && PropertyNewValue == e->state) {
1101 DebugF("pending.requestor 0x%lx\n", pending.requestor);
1103 if (get_property(e->window, e->atom, &pdata, /*Delete*/ True,
1105 [self copy_completed:pending.selection];
1106 [self release_pending];
1110 if (0 == pdata.length) {
1112 * We completed the transfer.
1113 * handle_selection will call copy_completed: for us.
1115 [self handle_selection:pending.selection type:type propdata:&
1117 free_propdata(&pdata);
1118 pending.propdata = null_propdata;
1119 pending.requestor = None;
1120 pending.selection = None;
1123 [self append_to_pending:&pdata requestor:e->window];
1124 free_propdata(&pdata);
1129 - (void) xfixes_selection_notify:(XFixesSelectionNotifyEvent *)e
1131 if (!pbproxy_prefs.active)
1134 switch (e->subtype) {
1135 case XFixesSetSelectionOwnerNotify:
1136 if (e->selection == atoms->primary && pbproxy_prefs.primary_on_grab)
1137 [self x_copy:e->timestamp];
1140 case XFixesSelectionWindowDestroyNotify:
1141 case XFixesSelectionClientCloseNotify:
1143 ErrorF("Unhandled XFixesSelectionNotifyEvent: subtype=%d\n",
1149 - (void) handle_targets: (Atom)selection propdata:(struct propdata *)pdata
1151 /* Find a type we can handle and prefer from the list of ATOMs. */
1157 preferred = [self find_preferred:pdata];
1159 if (None == preferred) {
1161 * This isn't required by the ICCCM, but some apps apparently
1162 * don't respond to TARGETS properly.
1164 preferred = atoms->string;
1167 (void)name; /* Avoid a warning with non-debug compiles. */
1169 name = XGetAtomName(xpbproxy_dpy, preferred);
1172 DebugF("requesting %s\n", name);
1175 request_atom = preferred;
1176 XConvertSelection(xpbproxy_dpy, selection, preferred, selection,
1177 _selection_window, CurrentTime);
1180 /* This handles the image type of selection (typically in CLIPBOARD). */
1181 /* We convert to a TIFF, so that other applications can paste more easily. */
1182 - (void) handle_image: (struct propdata *)pdata pasteboard:(NSPasteboard *)pb
1186 NSData *data, *tiff;
1187 NSBitmapImageRep *bmimage;
1191 length = pdata->length;
1192 data = [[NSData alloc] initWithBytes:pdata->data length:length];
1195 DebugF("unable to create NSData object!\n");
1200 DebugF("data retainCount before NSBitmapImageRep initWithData: %lu\n",
1201 [data retainCount]);
1203 DebugF("data retainCount before NSBitmapImageRep initWithData: %u\n",
1204 [data retainCount]);
1207 bmimage = [[NSBitmapImageRep alloc] initWithData:data];
1209 if (nil == bmimage) {
1211 DebugF("unable to create NSBitmapImageRep!\n");
1216 DebugF("data retainCount after NSBitmapImageRep initWithData: %lu\n",
1217 [data retainCount]);
1219 DebugF("data retainCount after NSBitmapImageRep initWithData: %u\n",
1220 [data retainCount]);
1225 tiff = [bmimage TIFFRepresentation];
1228 @catch (NSException *e)
1230 DebugF("NSTIFFException!\n");
1232 [bmimage autorelease];
1237 DebugF("bmimage retainCount after TIFFRepresentation %lu\n",
1238 [bmimage retainCount]);
1240 DebugF("bmimage retainCount after TIFFRepresentation %u\n",
1241 [bmimage retainCount]);
1244 pbtypes = [NSArray arrayWithObjects:NSTIFFPboardType, nil];
1246 if (nil == pbtypes) {
1248 [bmimage autorelease];
1252 [pb declareTypes:pbtypes owner:nil];
1253 if (YES != [pb setData:tiff forType:NSTIFFPboardType]) {
1254 DebugF("writing pasteboard data failed!\n");
1260 DebugF("bmimage retainCount before release %lu\n", [bmimage retainCount]);
1262 DebugF("bmimage retainCount before release %u\n", [bmimage retainCount]);
1265 [bmimage autorelease];
1268 /* This handles the UTF8_STRING type of selection. */
1269 - (void) handle_utf8_string:(struct propdata *)pdata pasteboard:(NSPasteboard
1278 [[NSString alloc] initWithBytes:pdata->data length:pdata->length
1280 NSUTF8StringEncoding];
1285 pbtypes = [NSArray arrayWithObjects:NSStringPboardType, nil];
1287 if (nil == pbtypes) {
1288 [string autorelease];
1292 [pb declareTypes:pbtypes owner:nil];
1294 if (YES != [pb setString:string forType:NSStringPboardType]) {
1295 ErrorF("pasteboard setString:forType: failed!\n");
1297 [string autorelease];
1298 DebugF("done handling utf8 string\n");
1301 /* This handles the STRING type, which should be in Latin-1. */
1302 - (void) handle_string: (struct propdata *)pdata pasteboard:(NSPasteboard *)
1311 [[NSString alloc] initWithBytes:pdata->data length:pdata->length
1313 NSISOLatin1StringEncoding];
1318 pbtypes = [NSArray arrayWithObjects:NSStringPboardType, nil];
1320 if (nil == pbtypes) {
1321 [string autorelease];
1325 [pb declareTypes:pbtypes owner:nil];
1326 if (YES != [pb setString:string forType:NSStringPboardType]) {
1327 ErrorF("pasteboard setString:forType failed in handle_string!\n");
1329 [string autorelease];
1332 /* This is called when the selection is completely retrieved from another client. */
1333 /* Warning: this frees the propdata. */
1334 - (void) handle_selection:(Atom)selection type:(Atom)type propdata:(struct
1342 pb = [NSPasteboard generalPasteboard];
1345 [self copy_completed:selection];
1346 free_propdata(pdata);
1351 * Some apps it seems set the type to TARGETS instead of ATOM, such as Eterm.
1352 * These aren't ICCCM compliant apps, but we need these to work...
1354 if (request_atom == atoms->targets
1355 && (type == atoms->atom || type == atoms->targets)) {
1356 [self handle_targets:selection propdata:pdata];
1357 free_propdata(pdata);
1360 else if (type == atoms->image_png) {
1361 [self handle_image:pdata pasteboard:pb];
1363 else if (type == atoms->image_jpeg) {
1364 [self handle_image:pdata pasteboard:pb];
1366 else if (type == atoms->utf8_string) {
1367 [self handle_utf8_string:pdata pasteboard:pb];
1369 else if (type == atoms->string) {
1370 [self handle_string:pdata pasteboard:pb];
1373 free_propdata(pdata);
1375 [self copy_completed:selection];
1378 - (void) copy_completed:(Atom)selection
1383 (void)name; /* Avoid warning with non-debug compiles. */
1385 name = XGetAtomName(xpbproxy_dpy, selection);
1387 DebugF("copy_completed: %s\n", name);
1392 if (selection == atoms->primary && pending_copy > 0) {
1394 if (pending_copy > 0) {
1395 /* Copy PRIMARY again. */
1396 [self x_copy_request_targets];
1400 else if (selection == atoms->clipboard && pending_clipboard > 0) {
1401 --pending_clipboard;
1402 if (pending_clipboard > 0) {
1403 /* Copy CLIPBOARD. */
1404 [self claim_clipboard];
1408 /* We got the final data. Now set pbproxy as the owner. */
1409 [self own_clipboard];
1415 * We had 1 or more primary in progress, and the clipboard arrived
1416 * while we were busy.
1418 if (pending_clipboard > 0) {
1419 [self claim_clipboard];
1423 - (void) reload_preferences
1426 * It's uncertain how we could handle the synchronization failing, so cast to void.
1427 * The prefs_get_bool should fall back to defaults if the org.x.X11 plist doesn't exist or is invalid.
1429 (void)CFPreferencesAppSynchronize(app_prefs_domain_cfstr);
1430 #ifdef STANDALONE_XPBPROXY
1431 if (xpbproxy_is_standalone)
1432 pbproxy_prefs.active = YES;
1435 pbproxy_prefs.active = prefs_get_bool(CFSTR(
1437 pbproxy_prefs.active);
1438 pbproxy_prefs.primary_on_grab =
1439 prefs_get_bool(CFSTR(
1440 "sync_primary_on_select"),
1441 pbproxy_prefs.primary_on_grab);
1442 pbproxy_prefs.clipboard_to_pasteboard =
1443 prefs_get_bool(CFSTR(
1444 "sync_clipboard_to_pasteboard"),
1445 pbproxy_prefs.clipboard_to_pasteboard);
1446 pbproxy_prefs.pasteboard_to_primary =
1447 prefs_get_bool(CFSTR(
1448 "sync_pasteboard_to_primary"),
1449 pbproxy_prefs.pasteboard_to_primary);
1450 pbproxy_prefs.pasteboard_to_clipboard =
1451 prefs_get_bool(CFSTR(
1452 "sync_pasteboard_to_clipboard"),
1453 pbproxy_prefs.pasteboard_to_clipboard);
1455 /* This is used for debugging. */
1458 if (pbproxy_prefs.active && pbproxy_prefs.primary_on_grab &&
1459 !xpbproxy_have_xfixes) {
1461 "Disabling sync_primary_on_select functionality due to missing XFixes extension.\n");
1462 pbproxy_prefs.primary_on_grab = NO;
1465 /* Claim or release the CLIPBOARD_MANAGER atom */
1466 if (![self set_clipboard_manager_status:(pbproxy_prefs.active &&
1468 clipboard_to_pasteboard)])
1469 pbproxy_prefs.clipboard_to_pasteboard = NO;
1471 if (pbproxy_prefs.active && pbproxy_prefs.clipboard_to_pasteboard)
1472 [self claim_clipboard];
1477 return pbproxy_prefs.active;
1480 /* NSPasteboard-required methods */
1482 - (void) paste:(id)sender
1487 - (void) pasteboard:(NSPasteboard *)pb provideDataForType:(NSString *)type
1492 - (void) pasteboardChangedOwner:(NSPasteboard *)pb
1496 /* Right now we don't care with this. */
1503 unsigned long pixel;
1505 self = [super init];
1509 atoms->primary = XInternAtom(xpbproxy_dpy, "PRIMARY", False);
1510 atoms->clipboard = XInternAtom(xpbproxy_dpy, "CLIPBOARD", False);
1511 atoms->text = XInternAtom(xpbproxy_dpy, "TEXT", False);
1512 atoms->utf8_string = XInternAtom(xpbproxy_dpy, "UTF8_STRING", False);
1513 atoms->string = XInternAtom(xpbproxy_dpy, "STRING", False);
1514 atoms->targets = XInternAtom(xpbproxy_dpy, "TARGETS", False);
1515 atoms->multiple = XInternAtom(xpbproxy_dpy, "MULTIPLE", False);
1516 atoms->cstring = XInternAtom(xpbproxy_dpy, "CSTRING", False);
1517 atoms->image_png = XInternAtom(xpbproxy_dpy, "image/png", False);
1518 atoms->image_jpeg = XInternAtom(xpbproxy_dpy, "image/jpeg", False);
1519 atoms->incr = XInternAtom(xpbproxy_dpy, "INCR", False);
1520 atoms->atom = XInternAtom(xpbproxy_dpy, "ATOM", False);
1521 atoms->clipboard_manager = XInternAtom(xpbproxy_dpy, "CLIPBOARD_MANAGER",
1523 atoms->compound_text = XInternAtom(xpbproxy_dpy, "COMPOUND_TEXT", False);
1524 atoms->atom_pair = XInternAtom(xpbproxy_dpy, "ATOM_PAIR", False);
1526 pixel = BlackPixel(xpbproxy_dpy, DefaultScreen(xpbproxy_dpy));
1528 XCreateSimpleWindow(xpbproxy_dpy, DefaultRootWindow(xpbproxy_dpy),
1529 0, 0, 1, 1, 0, pixel, pixel);
1531 /* This is used to get PropertyNotify events when doing INCR transfers. */
1532 XSelectInput(xpbproxy_dpy, _selection_window, PropertyChangeMask);
1534 request_atom = None;
1536 init_propdata(&pending.propdata);
1537 pending.requestor = None;
1538 pending.selection = None;
1541 pending_clipboard = 0;
1543 if (xpbproxy_have_xfixes)
1544 XFixesSelectSelectionInput(xpbproxy_dpy, _selection_window,
1546 XFixesSetSelectionOwnerNotifyMask);
1548 [self reload_preferences];
1555 if (None != _selection_window) {
1556 XDestroyWindow(xpbproxy_dpy, _selection_window);
1557 _selection_window = None;
1560 free_propdata(&pending.propdata);