xref: /OK3568_Linux_fs/external/xserver/hw/xquartz/pbproxy/x-selection.m (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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