1527 lines
42 KiB
Objective-C
1527 lines
42 KiB
Objective-C
/* x-selection.m -- proxies between NSPasteboard and X11 selections
|
|
|
|
Copyright (c) 2002, 2008 Apple Computer, Inc. All rights reserved.
|
|
|
|
Permission is hereby granted, free of charge, to any person
|
|
obtaining a copy of this software and associated documentation files
|
|
(the "Software"), to deal in the Software without restriction,
|
|
including without limitation the rights to use, copy, modify, merge,
|
|
publish, distribute, sublicense, and/or sell copies of the Software,
|
|
and to permit persons to whom the Software is furnished to do so,
|
|
subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be
|
|
included in all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
NONINFRINGEMENT. IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT
|
|
HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
DEALINGS IN THE SOFTWARE.
|
|
|
|
Except as contained in this notice, the name(s) of the above
|
|
copyright holders shall not be used in advertising or otherwise to
|
|
promote the sale, use or other dealings in this Software without
|
|
prior written authorization.
|
|
*/
|
|
|
|
#import "x-selection.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <X11/Xatom.h>
|
|
#include <X11/Xutil.h>
|
|
#import <AppKit/NSGraphics.h>
|
|
#import <AppKit/NSImage.h>
|
|
#import <AppKit/NSBitmapImageRep.h>
|
|
|
|
/*
|
|
* The basic design of the pbproxy code is as follows.
|
|
*
|
|
* When a client selects text, say from an xterm - we only copy it when the
|
|
* X11 Edit->Copy menu item is pressed or the shortcut activated. In this
|
|
* case we take the PRIMARY selection, and set it as the NSPasteboard data.
|
|
*
|
|
* When an X11 client copies something to the CLIPBOARD, pbproxy greedily grabs
|
|
* the data, sets it as the NSPasteboard data, and finally sets itself as
|
|
* owner of the CLIPBOARD.
|
|
*
|
|
* When an X11 window is activated we check to see if the NSPasteboard has
|
|
* changed. If the NSPasteboard has changed, then we set pbproxy as owner
|
|
* of the PRIMARY and CLIPBOARD and respond to requests for text and images.
|
|
*
|
|
* The behavior is now dynamic since the information above was written.
|
|
* The behavior is now dependent on the pbproxy_prefs below.
|
|
*/
|
|
|
|
/*
|
|
* TODO:
|
|
* 1. handle MULTIPLE - I need to study the ICCCM further, and find a test app.
|
|
* 2. Handle NSPasteboard updates immediately, not on active/inactive
|
|
* - Open xterm, run 'cat readme.txt | pbcopy'
|
|
*/
|
|
|
|
static struct {
|
|
BOOL active;
|
|
BOOL primary_on_grab; /* This is provided as an option for people who
|
|
* want it and has issues that won't ever be
|
|
* addressed to make it *always* work.
|
|
*/
|
|
BOOL clipboard_to_pasteboard;
|
|
BOOL pasteboard_to_primary;
|
|
BOOL pasteboard_to_clipboard;
|
|
} pbproxy_prefs = {
|
|
YES, NO, YES, YES, YES};
|
|
|
|
@implementation x_selection
|
|
static struct propdata null_propdata = { NULL, 0, 0 };
|
|
|
|
#ifdef DEBUG
|
|
static void
|
|
dump_prefs()
|
|
{
|
|
ErrorF(fp,
|
|
"pbproxy preferences:\n"
|
|
"\tactive %u\n"
|
|
"\tprimary_on_grab %u\n"
|
|
"\tclipboard_to_pasteboard %u\n"
|
|
"\tpasteboard_to_primary %u\n"
|
|
"\tpasteboard_to_clipboard %u\n",
|
|
pbproxy_prefs.active,
|
|
pbproxy_prefs.primary_on_grab,
|
|
pbproxy_prefs.clipboard_to_pasteboard,
|
|
pbproxy_prefs.pasteboard_to_primary,
|
|
pbproxy_prefs.pasteboard_to_clipboard);
|
|
}
|
|
#endif
|
|
|
|
extern CFStringRef app_prefs_domain_cfstr;
|
|
|
|
static BOOL
|
|
prefs_get_bool(CFStringRef key, BOOL defaultValue)
|
|
{
|
|
Boolean value, ok;
|
|
|
|
value = CFPreferencesGetAppBooleanValue(key, app_prefs_domain_cfstr, &ok);
|
|
|
|
return ok ? (BOOL) value : defaultValue;
|
|
}
|
|
|
|
static void
|
|
init_propdata(struct propdata *pdata)
|
|
{
|
|
*pdata = null_propdata;
|
|
}
|
|
|
|
static void
|
|
free_propdata(struct propdata *pdata)
|
|
{
|
|
free(pdata->data);
|
|
*pdata = null_propdata;
|
|
}
|
|
|
|
/*
|
|
* Return True if an error occurs. Return False if pdata has data
|
|
* and we finished.
|
|
* The property is only deleted when bytesleft is 0 if delete is True.
|
|
*/
|
|
static Bool
|
|
get_property(Window win, Atom property, struct propdata *pdata, Bool delete,
|
|
Atom *type)
|
|
{
|
|
long offset = 0;
|
|
unsigned long numitems, bytesleft = 0;
|
|
|
|
#ifdef TEST
|
|
/* This is used to test the growth handling. */
|
|
unsigned long length = 4UL;
|
|
#else
|
|
unsigned long length = (100000UL + 3) / 4;
|
|
#endif
|
|
unsigned char *buf = NULL, *chunk = NULL;
|
|
size_t buflen = 0, chunkbytesize = 0;
|
|
int format;
|
|
|
|
TRACE();
|
|
|
|
if (None == property)
|
|
return True;
|
|
|
|
do {
|
|
unsigned long newbuflen = 0;
|
|
unsigned char *newbuf = NULL;
|
|
|
|
#ifdef TEST
|
|
ErrorF("bytesleft %lu\n", bytesleft);
|
|
#endif
|
|
|
|
if (Success != XGetWindowProperty(xpbproxy_dpy, win, property,
|
|
offset, length, delete,
|
|
AnyPropertyType,
|
|
type, &format, &numitems,
|
|
&bytesleft, &chunk)) {
|
|
DebugF("Error while getting window property.\n");
|
|
*pdata = null_propdata;
|
|
free(buf);
|
|
return True;
|
|
}
|
|
|
|
#ifdef TEST
|
|
ErrorF("format %d numitems %lu bytesleft %lu\n",
|
|
format, numitems, bytesleft);
|
|
|
|
ErrorF("type %s\n", XGetAtomName(xpbproxy_dpy, *type));
|
|
#endif
|
|
|
|
/* Format is the number of bits. */
|
|
if (format == 8)
|
|
chunkbytesize = numitems;
|
|
else if (format == 16)
|
|
chunkbytesize = numitems * sizeof(short);
|
|
else if (format == 32)
|
|
chunkbytesize = numitems * sizeof(long);
|
|
|
|
#ifdef TEST
|
|
ErrorF("chunkbytesize %zu\n", chunkbytesize);
|
|
#endif
|
|
newbuflen = buflen + chunkbytesize;
|
|
if (newbuflen > 0) {
|
|
newbuf = realloc(buf, newbuflen);
|
|
|
|
if (NULL == newbuf) {
|
|
XFree(chunk);
|
|
free(buf);
|
|
return True;
|
|
}
|
|
|
|
memcpy(newbuf + buflen, chunk, chunkbytesize);
|
|
XFree(chunk);
|
|
buf = newbuf;
|
|
buflen = newbuflen;
|
|
/* offset is a multiple of 32 bits */
|
|
offset += chunkbytesize / 4;
|
|
}
|
|
else {
|
|
if (chunk)
|
|
XFree(chunk);
|
|
}
|
|
|
|
#ifdef TEST
|
|
ErrorF("bytesleft %lu\n", bytesleft);
|
|
#endif
|
|
} while (bytesleft > 0);
|
|
|
|
pdata->data = buf;
|
|
pdata->length = buflen;
|
|
pdata->format = format;
|
|
|
|
return /*success */ False;
|
|
}
|
|
|
|
/* Implementation methods */
|
|
|
|
/* This finds the preferred type from a TARGETS list.*/
|
|
-(Atom) find_preferred:(struct propdata *) pdata {
|
|
Atom a = None;
|
|
size_t i, step;
|
|
Bool png = False, jpeg = False, utf8 = False, string = False;
|
|
|
|
TRACE();
|
|
|
|
if (pdata->format != 32) {
|
|
ErrorF
|
|
("Atom list is expected to be formatted as an array of 32bit values.\n");
|
|
return None;
|
|
}
|
|
|
|
for (i = 0, step = sizeof(long); i < pdata->length; i += step) {
|
|
a = (Atom) *(long *) (pdata->data + i);
|
|
|
|
if (a == atoms->image_png) {
|
|
png = True;
|
|
}
|
|
else if (a == atoms->image_jpeg) {
|
|
jpeg = True;
|
|
}
|
|
else if (a == atoms->utf8_string) {
|
|
utf8 = True;
|
|
}
|
|
else if (a == atoms->string) {
|
|
string = True;
|
|
}
|
|
else {
|
|
char *type = XGetAtomName(xpbproxy_dpy, a);
|
|
|
|
if (type) {
|
|
DebugF("Unhandled X11 mime type: %s", type);
|
|
XFree(type);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*We prefer PNG over strings, and UTF8 over a Latin-1 string. */
|
|
if (png)
|
|
return atoms->image_png;
|
|
|
|
if (jpeg)
|
|
return atoms->image_jpeg;
|
|
|
|
if (utf8)
|
|
return atoms->utf8_string;
|
|
|
|
if (string)
|
|
return atoms->string;
|
|
|
|
/* This is evidently something we don't know how to handle. */
|
|
return None;
|
|
}
|
|
|
|
/* Return True if this is an INCR-style transfer. */
|
|
-(Bool) is_incr_type:(XSelectionEvent *) e {
|
|
Atom seltype;
|
|
int format;
|
|
unsigned long numitems = 0UL, bytesleft = 0UL;
|
|
unsigned char *chunk;
|
|
|
|
TRACE();
|
|
|
|
if (Success != XGetWindowProperty(xpbproxy_dpy, e->requestor, e->property,
|
|
/*offset */ 0L, /*length */ 4UL,
|
|
/*Delete */ False,
|
|
AnyPropertyType, &seltype, &format,
|
|
&numitems, &bytesleft, &chunk)) {
|
|
return False;
|
|
}
|
|
|
|
if (chunk)
|
|
XFree(chunk);
|
|
|
|
return (seltype == atoms->incr) ? True : False;
|
|
}
|
|
|
|
/*
|
|
* This should be called after a selection has been copied,
|
|
* or when the selection is unfinished before a transfer completes.
|
|
*/
|
|
-(void) release_pending {
|
|
TRACE();
|
|
|
|
free_propdata(&pending.propdata);
|
|
pending.requestor = None;
|
|
pending.selection = None;
|
|
}
|
|
|
|
/* Return True if an error occurs during an append.*/
|
|
/* Return False if the append succeeds. */
|
|
-(Bool) append_to_pending:(struct propdata *)
|
|
pdata requestor:(Window) requestor
|
|
{
|
|
unsigned char *newdata;
|
|
size_t newlength;
|
|
|
|
TRACE();
|
|
|
|
if (requestor != pending.requestor) {
|
|
[self release_pending];
|
|
pending.requestor = requestor;
|
|
}
|
|
|
|
newlength = pending.propdata.length + pdata->length;
|
|
newdata = realloc(pending.propdata.data, newlength);
|
|
|
|
if (NULL == newdata) {
|
|
perror("realloc propdata");
|
|
[self release_pending];
|
|
return True;
|
|
}
|
|
|
|
memcpy(newdata + pending.propdata.length, pdata->data, pdata->length);
|
|
pending.propdata.data = newdata;
|
|
pending.propdata.length = newlength;
|
|
|
|
return False;
|
|
}
|
|
|
|
/* Called when X11 becomes active (i.e. has key focus) */
|
|
-(void) x_active:(Time) timestamp {
|
|
static NSInteger changeCount;
|
|
NSInteger countNow;
|
|
NSPasteboard *pb;
|
|
|
|
TRACE();
|
|
|
|
pb =[NSPasteboard generalPasteboard];
|
|
|
|
if (nil == pb)
|
|
return;
|
|
|
|
countNow =[pb changeCount];
|
|
|
|
if (countNow != changeCount) {
|
|
DebugF("changed pasteboard!\n");
|
|
changeCount = countNow;
|
|
|
|
if (pbproxy_prefs.pasteboard_to_primary) {
|
|
XSetSelectionOwner(xpbproxy_dpy, atoms->primary, _selection_window,
|
|
CurrentTime);
|
|
}
|
|
|
|
if (pbproxy_prefs.pasteboard_to_clipboard) {
|
|
[self own_clipboard];
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
/*gstaplin: we should perhaps investigate something like this branch above... */
|
|
if ([_pasteboard availableTypeFromArray:_known_types] != nil) {
|
|
/* Pasteboard has data we should proxy; I think it makes
|
|
sense to put it on both CLIPBOARD and PRIMARY */
|
|
|
|
XSetSelectionOwner(xpbproxy_dpy, atoms->clipboard,
|
|
_selection_window, timestamp);
|
|
XSetSelectionOwner(xpbproxy_dpy, atoms->primary,
|
|
_selection_window, timestamp);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Called when X11 loses key focus */
|
|
-(void) x_inactive:(Time) timestamp {
|
|
TRACE();
|
|
}
|
|
|
|
/* This requests the TARGETS list from the PRIMARY selection owner. */
|
|
-(void) x_copy_request_targets {
|
|
TRACE();
|
|
|
|
request_atom = atoms->targets;
|
|
XConvertSelection(xpbproxy_dpy, atoms->primary, atoms->targets,
|
|
atoms->primary, _selection_window, CurrentTime);
|
|
}
|
|
|
|
/* Called when the Edit/Copy item on the main X11 menubar is selected
|
|
* and no appkit window claims it. */
|
|
-(void) x_copy:(Time) timestamp {
|
|
Window w;
|
|
|
|
TRACE();
|
|
|
|
w = XGetSelectionOwner(xpbproxy_dpy, atoms->primary);
|
|
|
|
if (None != w) {
|
|
++pending_copy;
|
|
|
|
if (1 == pending_copy) {
|
|
/*
|
|
* There are no other copy operations in progress, so we
|
|
* can proceed safely. Otherwise the copy_completed method
|
|
* will see that the pending_copy is > 1, and do another copy.
|
|
*/
|
|
[self x_copy_request_targets];
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Set pbproxy as owner of the SELECTION_MANAGER selection.
|
|
* This prevents tools like xclipboard from causing havoc.
|
|
* Returns TRUE on success
|
|
*/
|
|
-(BOOL) set_clipboard_manager_status:(BOOL) value {
|
|
TRACE();
|
|
|
|
Window owner = XGetSelectionOwner(xpbproxy_dpy, atoms->clipboard_manager);
|
|
|
|
if (value) {
|
|
if (owner == _selection_window)
|
|
return TRUE;
|
|
|
|
if (owner != None) {
|
|
ErrorF
|
|
("A clipboard manager using window 0x%lx already owns the clipboard selection. "
|
|
"pbproxy will not sync clipboard to pasteboard.\n", owner);
|
|
return FALSE;
|
|
}
|
|
|
|
XSetSelectionOwner(xpbproxy_dpy, atoms->clipboard_manager,
|
|
_selection_window, CurrentTime);
|
|
return (_selection_window ==
|
|
XGetSelectionOwner(xpbproxy_dpy, atoms->clipboard_manager));
|
|
}
|
|
else {
|
|
if (owner != _selection_window)
|
|
return TRUE;
|
|
|
|
XSetSelectionOwner(xpbproxy_dpy, atoms->clipboard_manager, None,
|
|
CurrentTime);
|
|
return (None ==
|
|
XGetSelectionOwner(xpbproxy_dpy, atoms->clipboard_manager));
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* This occurs when we previously owned a selection,
|
|
* and then lost it from another client.
|
|
*/
|
|
-(void) clear_event:(XSelectionClearEvent *) e {
|
|
|
|
TRACE();
|
|
|
|
DebugF("e->selection %s\n", XGetAtomName(xpbproxy_dpy, e->selection));
|
|
|
|
if (e->selection == atoms->clipboard) {
|
|
/*
|
|
* We lost ownership of the CLIPBOARD.
|
|
*/
|
|
++pending_clipboard;
|
|
|
|
if (1 == pending_clipboard) {
|
|
/* Claim the clipboard contents from the new owner. */
|
|
[self claim_clipboard];
|
|
}
|
|
}
|
|
else if (e->selection == atoms->clipboard_manager) {
|
|
if (pbproxy_prefs.clipboard_to_pasteboard) {
|
|
/* Another CLIPBOARD_MANAGER has set itself as owner. Disable syncing
|
|
* to avoid a race.
|
|
*/
|
|
ErrorF("Another clipboard manager was started! "
|
|
"xpbproxy is disabling syncing with clipboard.\n");
|
|
pbproxy_prefs.clipboard_to_pasteboard = NO;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We greedily acquire the clipboard after it changes, and on startup.
|
|
*/
|
|
-(void) claim_clipboard {
|
|
Window owner;
|
|
|
|
TRACE();
|
|
|
|
if (!pbproxy_prefs.clipboard_to_pasteboard)
|
|
return;
|
|
|
|
owner = XGetSelectionOwner(xpbproxy_dpy, atoms->clipboard);
|
|
if (None == owner) {
|
|
/*
|
|
* The owner probably died or we are just starting up pbproxy.
|
|
* Set pbproxy's _selection_window as the owner, and continue.
|
|
*/
|
|
DebugF("No clipboard owner.\n");
|
|
[self copy_completed:atoms->clipboard];
|
|
return;
|
|
}
|
|
else if (owner == _selection_window) {
|
|
[self copy_completed:atoms->clipboard];
|
|
return;
|
|
}
|
|
|
|
DebugF("requesting targets\n");
|
|
|
|
request_atom = atoms->targets;
|
|
XConvertSelection(xpbproxy_dpy, atoms->clipboard, atoms->targets,
|
|
atoms->clipboard, _selection_window, CurrentTime);
|
|
XFlush(xpbproxy_dpy);
|
|
/* Now we will get a SelectionNotify event in the future. */
|
|
}
|
|
|
|
/* Greedily acquire the clipboard. */
|
|
-(void) own_clipboard {
|
|
|
|
TRACE();
|
|
|
|
/* We should perhaps have a boundary limit on the number of iterations... */
|
|
do {
|
|
XSetSelectionOwner(xpbproxy_dpy, atoms->clipboard, _selection_window,
|
|
CurrentTime);
|
|
} while (_selection_window != XGetSelectionOwner(xpbproxy_dpy,
|
|
atoms->clipboard));
|
|
}
|
|
|
|
-(void) init_reply:(XEvent *)
|
|
reply request:(XSelectionRequestEvent *) e
|
|
{
|
|
reply->xselection.type = SelectionNotify;
|
|
reply->xselection.selection = e->selection;
|
|
reply->xselection.target = e->target;
|
|
reply->xselection.requestor = e->requestor;
|
|
reply->xselection.time = e->time;
|
|
reply->xselection.property = None;
|
|
}
|
|
|
|
-(void) send_reply:(XEvent *) reply {
|
|
/*
|
|
* We are supposed to use an empty event mask, and not propagate
|
|
* the event, according to the ICCCM.
|
|
*/
|
|
DebugF("reply->xselection.requestor 0x%lx\n", reply->xselection.requestor);
|
|
|
|
XSendEvent(xpbproxy_dpy, reply->xselection.requestor, False, 0, reply);
|
|
XFlush(xpbproxy_dpy);
|
|
}
|
|
|
|
/*
|
|
* This responds to a TARGETS request.
|
|
* The result is a list of a ATOMs that correspond to the types available
|
|
* for a selection.
|
|
* For instance an application might provide a UTF8_STRING and a STRING
|
|
* (in Latin-1 encoding). The requestor can then make the choice based on
|
|
* the list.
|
|
*/
|
|
-(void) send_targets:(XSelectionRequestEvent *)
|
|
e pasteboard:(NSPasteboard *) pb
|
|
{
|
|
XEvent reply;
|
|
NSArray *pbtypes;
|
|
|
|
[self init_reply: &reply request:e];
|
|
|
|
pbtypes =[pb types];
|
|
if (pbtypes) {
|
|
long list[7]; /* Don't forget to increase this if we handle more types! */
|
|
long count = 0;
|
|
|
|
/*
|
|
* I'm not sure if this is needed, but some toolkits/clients list
|
|
* TARGETS in response to targets.
|
|
*/
|
|
list[count] = atoms->targets;
|
|
++count;
|
|
|
|
if ([pbtypes containsObject:NSStringPboardType]) {
|
|
/* We have a string type that we can convert to UTF8, or Latin-1... */
|
|
DebugF("NSStringPboardType\n");
|
|
list[count] = atoms->utf8_string;
|
|
++count;
|
|
list[count] = atoms->string;
|
|
++count;
|
|
list[count] = atoms->compound_text;
|
|
++count;
|
|
}
|
|
|
|
/* TODO add the NSPICTPboardType back again, once we have conversion
|
|
* functionality in send_image.
|
|
*/
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations" // NSPICTPboardType
|
|
#endif
|
|
|
|
if ([pbtypes containsObject:NSPICTPboardType]
|
|
||[pbtypes containsObject:NSTIFFPboardType]) {
|
|
/* We can convert a TIFF to a PNG or JPEG. */
|
|
DebugF("NSTIFFPboardType\n");
|
|
list[count] = atoms->image_png;
|
|
++count;
|
|
list[count] = atoms->image_jpeg;
|
|
++count;
|
|
}
|
|
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic pop
|
|
#endif
|
|
|
|
if (count) {
|
|
/* We have a list of ATOMs to send. */
|
|
XChangeProperty(xpbproxy_dpy, e->requestor, e->property,
|
|
atoms->atom, 32, PropModeReplace,
|
|
(unsigned char *) list, count);
|
|
|
|
reply.xselection.property = e->property;
|
|
}
|
|
}
|
|
|
|
[self send_reply:&reply];
|
|
}
|
|
|
|
-(void) send_string:(XSelectionRequestEvent *)
|
|
e utf8:(BOOL)
|
|
utf8 pasteboard:(NSPasteboard *) pb
|
|
{
|
|
XEvent reply;
|
|
NSArray *pbtypes;
|
|
NSString *data;
|
|
const char *bytes;
|
|
NSUInteger length;
|
|
|
|
TRACE();
|
|
|
|
[self init_reply: &reply request:e];
|
|
|
|
pbtypes =[pb types];
|
|
|
|
if (![pbtypes containsObject:NSStringPboardType]) {
|
|
[self send_reply:&reply];
|
|
return;
|
|
}
|
|
|
|
#ifdef __LP64__
|
|
DebugF("pbtypes retainCount after containsObject: %lu\n",
|
|
[pbtypes retainCount]);
|
|
#else
|
|
DebugF("pbtypes retainCount after containsObject: %u\n",
|
|
[pbtypes retainCount]);
|
|
#endif
|
|
|
|
data =[pb stringForType:NSStringPboardType];
|
|
|
|
if (nil == data) {
|
|
[self send_reply:&reply];
|
|
return;
|
|
}
|
|
|
|
if (utf8) {
|
|
bytes =[data UTF8String];
|
|
/*
|
|
* We don't want the UTF-8 string length here.
|
|
* We want the length in bytes.
|
|
*/
|
|
length = strlen(bytes);
|
|
|
|
if (length < 50) {
|
|
DebugF("UTF-8: %s\n", bytes);
|
|
#ifdef __LP64__
|
|
DebugF("UTF-8 length: %lu\n", length);
|
|
#else
|
|
DebugF("UTF-8 length: %u\n", length);
|
|
#endif
|
|
}
|
|
}
|
|
else {
|
|
DebugF("Latin-1\n");
|
|
bytes =[data cStringUsingEncoding:NSISOLatin1StringEncoding];
|
|
/*WARNING: bytes is not NUL-terminated. */
|
|
length =[data lengthOfBytesUsingEncoding:NSISOLatin1StringEncoding];
|
|
}
|
|
|
|
DebugF("e->target %s\n", XGetAtomName(xpbproxy_dpy, e->target));
|
|
|
|
XChangeProperty(xpbproxy_dpy, e->requestor, e->property, e->target,
|
|
8, PropModeReplace, (unsigned char *) bytes, length);
|
|
|
|
reply.xselection.property = e->property;
|
|
|
|
[self send_reply:&reply];
|
|
}
|
|
|
|
-(void) send_compound_text:(XSelectionRequestEvent *)
|
|
e pasteboard:(NSPasteboard *) pb
|
|
{
|
|
XEvent reply;
|
|
NSArray *pbtypes;
|
|
|
|
TRACE();
|
|
|
|
[self init_reply: &reply request:e];
|
|
|
|
pbtypes =[pb types];
|
|
|
|
if ([pbtypes containsObject:NSStringPboardType]) {
|
|
NSString *data =[pb stringForType:NSStringPboardType];
|
|
|
|
if (nil != data) {
|
|
/*
|
|
* Cast to (void *) to avoid a const warning.
|
|
* AFAIK Xutf8TextListToTextProperty does not modify the input memory.
|
|
*/
|
|
void *utf8 = (void *)[data UTF8String];
|
|
char *list[] = { utf8, NULL };
|
|
XTextProperty textprop;
|
|
|
|
textprop.value = NULL;
|
|
|
|
if (Success == Xutf8TextListToTextProperty(xpbproxy_dpy, list, 1,
|
|
XCompoundTextStyle,
|
|
&textprop)) {
|
|
|
|
if (8 != textprop.format)
|
|
DebugF
|
|
("textprop.format is unexpectedly not 8 - it's %d instead\n",
|
|
textprop.format);
|
|
|
|
XChangeProperty(xpbproxy_dpy, e->requestor, e->property,
|
|
atoms->compound_text, textprop.format,
|
|
PropModeReplace, textprop.value,
|
|
textprop.nitems);
|
|
|
|
reply.xselection.property = e->property;
|
|
}
|
|
|
|
if (textprop.value)
|
|
XFree(textprop.value);
|
|
|
|
}
|
|
}
|
|
|
|
[self send_reply:&reply];
|
|
}
|
|
|
|
/* Finding a test application that uses MULTIPLE has proven to be difficult. */
|
|
-(void) send_multiple:(XSelectionRequestEvent *) e {
|
|
XEvent reply;
|
|
|
|
TRACE();
|
|
|
|
[self init_reply: &reply request:e];
|
|
|
|
if (None != e->property) {
|
|
|
|
}
|
|
|
|
[self send_reply:&reply];
|
|
}
|
|
|
|
/* Return nil if an error occured. */
|
|
/* DO NOT retain the encdata for longer than the length of an event response.
|
|
* The autorelease pool will reuse/free it.
|
|
*/
|
|
-(NSData *) encode_image_data:(NSData *)
|
|
data type:(NSBitmapImageFileType) enctype
|
|
{
|
|
NSBitmapImageRep *bmimage = nil;
|
|
NSData *encdata = nil;
|
|
NSDictionary *dict = nil;
|
|
|
|
bmimage =[[NSBitmapImageRep alloc] initWithData:data];
|
|
|
|
if (nil == bmimage)
|
|
return nil;
|
|
|
|
dict =[[NSDictionary alloc] init];
|
|
encdata =[bmimage representationUsingType: enctype properties:dict];
|
|
|
|
if (nil == encdata) {
|
|
[dict autorelease];
|
|
[bmimage autorelease];
|
|
return nil;
|
|
}
|
|
|
|
[dict autorelease];
|
|
[bmimage autorelease];
|
|
|
|
return encdata;
|
|
}
|
|
|
|
/* Return YES when an error has occured when trying to send the PICT. */
|
|
/* The caller should send a default reponse with a property of None when an error occurs. */
|
|
-(BOOL) send_image_pict_reply: (XSelectionRequestEvent *) e pasteboard: (NSPasteboard *) pb type:(NSBitmapImageFileType) imagetype
|
|
{
|
|
XEvent reply;
|
|
NSImage *img = nil;
|
|
NSData *data = nil, *encdata = nil;
|
|
NSUInteger length;
|
|
const void *bytes = NULL;
|
|
|
|
img =[[NSImage alloc] initWithPasteboard:pb];
|
|
|
|
if (nil == img) {
|
|
return YES;
|
|
}
|
|
|
|
data =[img TIFFRepresentation];
|
|
|
|
if (nil == data) {
|
|
[img autorelease];
|
|
ErrorF("unable to convert PICT to TIFF!\n");
|
|
return YES;
|
|
}
|
|
|
|
encdata =[self encode_image_data: data type:imagetype];
|
|
if (nil == encdata) {
|
|
[img autorelease];
|
|
return YES;
|
|
}
|
|
|
|
[self init_reply: &reply request:e];
|
|
|
|
length =[encdata length];
|
|
bytes =[encdata bytes];
|
|
|
|
XChangeProperty(xpbproxy_dpy, e->requestor, e->property, e->target,
|
|
8, PropModeReplace, bytes, length);
|
|
reply.xselection.property = e->property;
|
|
|
|
[self send_reply:&reply];
|
|
|
|
[img autorelease];
|
|
|
|
return NO; /*no error */
|
|
}
|
|
|
|
/* Return YES if an error occured. */
|
|
/* The caller should send a reply with a property of None when an error occurs. */
|
|
-(BOOL) send_image_tiff_reply: (XSelectionRequestEvent *) e pasteboard: (NSPasteboard *) pb type:(NSBitmapImageFileType) imagetype
|
|
{
|
|
XEvent reply;
|
|
NSData *data = nil;
|
|
NSData *encdata = nil;
|
|
NSUInteger length;
|
|
const void *bytes = NULL;
|
|
|
|
data =[pb dataForType:NSTIFFPboardType];
|
|
|
|
if (nil == data)
|
|
return YES;
|
|
|
|
encdata =[self encode_image_data: data type:imagetype];
|
|
|
|
if (nil == encdata)
|
|
return YES;
|
|
|
|
[self init_reply: &reply request:e];
|
|
|
|
length =[encdata length];
|
|
bytes =[encdata bytes];
|
|
|
|
XChangeProperty(xpbproxy_dpy, e->requestor, e->property, e->target,
|
|
8, PropModeReplace, bytes, length);
|
|
reply.xselection.property = e->property;
|
|
|
|
[self send_reply:&reply];
|
|
|
|
return NO; /*no error */
|
|
}
|
|
|
|
-(void) send_image:(XSelectionRequestEvent *)
|
|
e pasteboard:(NSPasteboard *) pb
|
|
{
|
|
NSArray *pbtypes = nil;
|
|
NSBitmapImageFileType imagetype = NSPNGFileType;
|
|
|
|
TRACE();
|
|
|
|
if (e->target == atoms->image_png)
|
|
imagetype = NSPNGFileType;
|
|
else if (e->target == atoms->image_jpeg)
|
|
imagetype = NSJPEGFileType;
|
|
else {
|
|
ErrorF
|
|
("internal failure in xpbproxy! imagetype being sent isn't PNG or JPEG.\n");
|
|
}
|
|
|
|
pbtypes =[pb types];
|
|
|
|
if (pbtypes) {
|
|
if ([pbtypes containsObject:NSTIFFPboardType]) {
|
|
if (NO ==[self send_image_tiff_reply: e pasteboard: pb type:imagetype])
|
|
return;
|
|
}
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations" // NSPICTPboardType
|
|
#endif
|
|
else if ([pbtypes containsObject:NSPICTPboardType])
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic pop
|
|
#endif
|
|
{
|
|
if (NO ==[self send_image_pict_reply: e pasteboard: pb type:imagetype])
|
|
return;
|
|
|
|
/* Fall through intentionally to the send_none: */
|
|
}
|
|
}
|
|
|
|
[self send_none:e];
|
|
}
|
|
|
|
-(void) send_none:(XSelectionRequestEvent *) e {
|
|
XEvent reply;
|
|
|
|
TRACE();
|
|
|
|
[self init_reply: &reply request:e];
|
|
[self send_reply:&reply];
|
|
}
|
|
|
|
/* Another client requested the data or targets of data available from the clipboard. */
|
|
-(void) request_event:(XSelectionRequestEvent *) e {
|
|
NSPasteboard *pb;
|
|
|
|
TRACE();
|
|
|
|
/* TODO We should also keep track of the time of the selection, and
|
|
* according to the ICCCM "refuse the request" if the event timestamp
|
|
* is before we owned it.
|
|
* What should we base the time on? How can we get the current time just
|
|
* before an XSetSelectionOwner? Is it the server's time, or the clients?
|
|
* According to the XSelectionRequestEvent manual page, the Time value
|
|
* may be set to CurrentTime or a time, so that makes it a bit different.
|
|
* Perhaps we should just punt and ignore races.
|
|
*/
|
|
|
|
/*TODO we need a COMPOUND_TEXT test app */
|
|
/*TODO we need a MULTIPLE test app */
|
|
|
|
pb =[NSPasteboard generalPasteboard];
|
|
if (nil == pb) {
|
|
[self send_none:e];
|
|
return;
|
|
}
|
|
|
|
if (None != e->target)
|
|
DebugF("e->target %s\n", XGetAtomName(xpbproxy_dpy, e->target));
|
|
|
|
if (e->target == atoms->targets) {
|
|
/* The paste requestor wants to know what TARGETS we support. */
|
|
[self send_targets: e pasteboard:pb];
|
|
}
|
|
else if (e->target == atoms->multiple) {
|
|
/*
|
|
* This isn't finished, and may never be, unless I can find
|
|
* a good test app.
|
|
*/
|
|
[self send_multiple:e];
|
|
}
|
|
else if (e->target == atoms->utf8_string) {
|
|
[self send_string: e utf8: YES pasteboard:pb];
|
|
}
|
|
else if (e->target == atoms->string) {
|
|
[self send_string: e utf8: NO pasteboard:pb];
|
|
}
|
|
else if (e->target == atoms->compound_text) {
|
|
[self send_compound_text: e pasteboard:pb];
|
|
}
|
|
else if (e->target == atoms->multiple) {
|
|
[self send_multiple:e];
|
|
}
|
|
else if (e->target == atoms->image_png || e->target == atoms->image_jpeg) {
|
|
[self send_image: e pasteboard:pb];
|
|
}
|
|
else {
|
|
[self send_none:e];
|
|
}
|
|
}
|
|
|
|
/* This handles the events resulting from an XConvertSelection request. */
|
|
-(void) notify_event:(XSelectionEvent *) e {
|
|
Atom type;
|
|
struct propdata pdata;
|
|
|
|
TRACE();
|
|
|
|
[self release_pending];
|
|
|
|
if (None == e->property) {
|
|
DebugF("e->property is None.\n");
|
|
[self copy_completed:e->selection];
|
|
/* Nothing is selected. */
|
|
return;
|
|
}
|
|
|
|
#if 0
|
|
ErrorF("e->selection %s\n", XGetAtomName(xpbproxy_dpy, e->selection));
|
|
ErrorF("e->property %s\n", XGetAtomName(xpbproxy_dpy, e->property));
|
|
#endif
|
|
|
|
if ([self is_incr_type:e]) {
|
|
/*
|
|
* This is an INCR-style transfer, which means that we
|
|
* will get the data after a series of PropertyNotify events.
|
|
*/
|
|
DebugF("is INCR\n");
|
|
|
|
if (get_property
|
|
(e->requestor, e->property, &pdata, /*Delete */ True, &type)) {
|
|
/*
|
|
* An error occured, so we should invoke the copy_completed:, but
|
|
* not handle_selection:type:propdata:
|
|
*/
|
|
[self copy_completed:e->selection];
|
|
return;
|
|
}
|
|
|
|
free_propdata(&pdata);
|
|
|
|
pending.requestor = e->requestor;
|
|
pending.selection = e->selection;
|
|
|
|
DebugF("set pending.requestor to 0x%lx\n", pending.requestor);
|
|
}
|
|
else {
|
|
if (get_property
|
|
(e->requestor, e->property, &pdata, /*Delete */ True, &type)) {
|
|
[self copy_completed:e->selection];
|
|
return;
|
|
}
|
|
|
|
/* We have the complete selection data. */
|
|
[self handle_selection: e->selection type: type propdata:&pdata];
|
|
|
|
DebugF("handled selection with the first notify_event\n");
|
|
}
|
|
}
|
|
|
|
/* This is used for INCR transfers. See the ICCCM for the details. */
|
|
/* This is used to retrieve PRIMARY and CLIPBOARD selections. */
|
|
-(void) property_event:(XPropertyEvent *) e {
|
|
struct propdata pdata;
|
|
Atom type;
|
|
|
|
TRACE();
|
|
|
|
if (None != e->atom) {
|
|
#ifdef DEBUG
|
|
char *name = XGetAtomName(xpbproxy_dpy, e->atom);
|
|
|
|
if (name) {
|
|
DebugF("e->atom %s\n", name);
|
|
XFree(name);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (None != pending.requestor && PropertyNewValue == e->state) {
|
|
DebugF("pending.requestor 0x%lx\n", pending.requestor);
|
|
|
|
if (get_property(e->window, e->atom, &pdata, /*Delete */ True, &type)) {
|
|
[self copy_completed:pending.selection];
|
|
[self release_pending];
|
|
return;
|
|
}
|
|
|
|
if (0 == pdata.length) {
|
|
/*
|
|
* We completed the transfer.
|
|
* handle_selection will call copy_completed: for us.
|
|
*/
|
|
[self handle_selection: pending.selection type: type propdata:&pending.
|
|
propdata];
|
|
free_propdata(&pdata);
|
|
pending.propdata = null_propdata;
|
|
pending.requestor = None;
|
|
pending.selection = None;
|
|
}
|
|
else {
|
|
[self append_to_pending: &pdata requestor:e->window];
|
|
free_propdata(&pdata);
|
|
}
|
|
}
|
|
}
|
|
|
|
-(void) xfixes_selection_notify:(XFixesSelectionNotifyEvent *) e {
|
|
if (!pbproxy_prefs.active)
|
|
return;
|
|
|
|
switch (e->subtype) {
|
|
case XFixesSetSelectionOwnerNotify:
|
|
if (e->selection == atoms->primary && pbproxy_prefs.primary_on_grab)
|
|
[self x_copy:e->timestamp];
|
|
break;
|
|
|
|
case XFixesSelectionWindowDestroyNotify:
|
|
case XFixesSelectionClientCloseNotify:
|
|
default:
|
|
ErrorF("Unhandled XFixesSelectionNotifyEvent: subtype=%d\n",
|
|
e->subtype);
|
|
break;
|
|
}
|
|
}
|
|
|
|
-(void) handle_targets:(Atom)
|
|
selection propdata:(struct propdata *) pdata
|
|
{
|
|
/* Find a type we can handle and prefer from the list of ATOMs. */
|
|
Atom preferred;
|
|
char *name;
|
|
|
|
TRACE();
|
|
|
|
preferred =[self find_preferred:pdata];
|
|
|
|
if (None == preferred) {
|
|
/*
|
|
* This isn't required by the ICCCM, but some apps apparently
|
|
* don't respond to TARGETS properly.
|
|
*/
|
|
preferred = atoms->string;
|
|
}
|
|
|
|
(void) name; /* Avoid a warning with non-debug compiles. */
|
|
#ifdef DEBUG
|
|
name = XGetAtomName(xpbproxy_dpy, preferred);
|
|
|
|
if (name) {
|
|
DebugF("requesting %s\n", name);
|
|
}
|
|
#endif
|
|
request_atom = preferred;
|
|
XConvertSelection(xpbproxy_dpy, selection, preferred, selection,
|
|
_selection_window, CurrentTime);
|
|
}
|
|
|
|
/* This handles the image type of selection (typically in CLIPBOARD). */
|
|
/* We convert to a TIFF, so that other applications can paste more easily. */
|
|
-(void) handle_image:(struct propdata *)
|
|
pdata pasteboard:(NSPasteboard *) pb
|
|
{
|
|
NSArray *pbtypes;
|
|
NSUInteger length;
|
|
NSData *data, *tiff;
|
|
NSBitmapImageRep *bmimage;
|
|
|
|
TRACE();
|
|
|
|
length = pdata->length;
|
|
data =[[NSData alloc] initWithBytes: pdata->data length:length];
|
|
|
|
if (nil == data) {
|
|
DebugF("unable to create NSData object!\n");
|
|
return;
|
|
}
|
|
|
|
#ifdef __LP64__
|
|
DebugF("data retainCount before NSBitmapImageRep initWithData: %lu\n",
|
|
[data retainCount]);
|
|
#else
|
|
DebugF("data retainCount before NSBitmapImageRep initWithData: %u\n",
|
|
[data retainCount]);
|
|
#endif
|
|
|
|
bmimage =[[NSBitmapImageRep alloc] initWithData:data];
|
|
|
|
if (nil == bmimage) {
|
|
[data autorelease];
|
|
DebugF("unable to create NSBitmapImageRep!\n");
|
|
return;
|
|
}
|
|
|
|
#ifdef __LP64__
|
|
DebugF("data retainCount after NSBitmapImageRep initWithData: %lu\n",
|
|
[data retainCount]);
|
|
#else
|
|
DebugF("data retainCount after NSBitmapImageRep initWithData: %u\n",
|
|
[data retainCount]);
|
|
#endif
|
|
|
|
@try {
|
|
tiff =[bmimage TIFFRepresentation];
|
|
}
|
|
|
|
@catch(NSException * e) {
|
|
DebugF("NSTIFFException!\n");
|
|
[data autorelease];
|
|
[bmimage autorelease];
|
|
return;
|
|
}
|
|
|
|
#ifdef __LP64__
|
|
DebugF("bmimage retainCount after TIFFRepresentation %lu\n",
|
|
[bmimage retainCount]);
|
|
#else
|
|
DebugF("bmimage retainCount after TIFFRepresentation %u\n",
|
|
[bmimage retainCount]);
|
|
#endif
|
|
|
|
pbtypes =[NSArray arrayWithObjects:NSTIFFPboardType, nil];
|
|
|
|
if (nil == pbtypes) {
|
|
[data autorelease];
|
|
[bmimage autorelease];
|
|
return;
|
|
}
|
|
|
|
[pb declareTypes: pbtypes owner:nil];
|
|
if (YES !=[pb setData: tiff forType:NSTIFFPboardType]) {
|
|
DebugF("writing pasteboard data failed!\n");
|
|
}
|
|
|
|
[data autorelease];
|
|
|
|
#ifdef __LP64__
|
|
DebugF("bmimage retainCount before release %lu\n",[bmimage retainCount]);
|
|
#else
|
|
DebugF("bmimage retainCount before release %u\n",[bmimage retainCount]);
|
|
#endif
|
|
|
|
[bmimage autorelease];
|
|
}
|
|
|
|
/* This handles the UTF8_STRING type of selection. */
|
|
-(void) handle_utf8_string:(struct propdata *)
|
|
pdata pasteboard:(NSPasteboard *) pb
|
|
{
|
|
NSString *string;
|
|
NSArray *pbtypes;
|
|
|
|
TRACE();
|
|
|
|
string =[[NSString alloc] initWithBytes: pdata->data length: pdata->length encoding:NSUTF8StringEncoding];
|
|
|
|
if (nil == string)
|
|
return;
|
|
|
|
pbtypes =[NSArray arrayWithObjects:NSStringPboardType, nil];
|
|
|
|
if (nil == pbtypes) {
|
|
[string autorelease];
|
|
return;
|
|
}
|
|
|
|
[pb declareTypes: pbtypes owner:nil];
|
|
|
|
if (YES !=[pb setString: string forType:NSStringPboardType]) {
|
|
ErrorF("pasteboard setString:forType: failed!\n");
|
|
}
|
|
[string autorelease];
|
|
DebugF("done handling utf8 string\n");
|
|
}
|
|
|
|
/* This handles the STRING type, which should be in Latin-1. */
|
|
-(void) handle_string:(struct propdata *)
|
|
pdata pasteboard:(NSPasteboard *) pb
|
|
{
|
|
NSString *string;
|
|
NSArray *pbtypes;
|
|
|
|
TRACE();
|
|
|
|
string =[[NSString alloc] initWithBytes: pdata->data length: pdata->length encoding:NSISOLatin1StringEncoding];
|
|
|
|
if (nil == string)
|
|
return;
|
|
|
|
pbtypes =[NSArray arrayWithObjects:NSStringPboardType, nil];
|
|
|
|
if (nil == pbtypes) {
|
|
[string autorelease];
|
|
return;
|
|
}
|
|
|
|
[pb declareTypes: pbtypes owner:nil];
|
|
if (YES !=[pb setString: string forType:NSStringPboardType]) {
|
|
ErrorF("pasteboard setString:forType failed in handle_string!\n");
|
|
}
|
|
[string autorelease];
|
|
}
|
|
|
|
/* This is called when the selection is completely retrieved from another client. */
|
|
/* Warning: this frees the propdata. */
|
|
-(void) handle_selection:(Atom)
|
|
selection type:(Atom)
|
|
type propdata:(struct propdata *) pdata
|
|
{
|
|
NSPasteboard *pb;
|
|
|
|
TRACE();
|
|
|
|
pb =[NSPasteboard generalPasteboard];
|
|
|
|
if (nil == pb) {
|
|
[self copy_completed:selection];
|
|
free_propdata(pdata);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Some apps it seems set the type to TARGETS instead of ATOM, such as Eterm.
|
|
* These aren't ICCCM compliant apps, but we need these to work...
|
|
*/
|
|
if (request_atom == atoms->targets
|
|
&& (type == atoms->atom || type == atoms->targets)) {
|
|
[self handle_targets: selection propdata:pdata];
|
|
free_propdata(pdata);
|
|
return;
|
|
}
|
|
else if (type == atoms->image_png) {
|
|
[self handle_image: pdata pasteboard:pb];
|
|
}
|
|
else if (type == atoms->image_jpeg) {
|
|
[self handle_image: pdata pasteboard:pb];
|
|
}
|
|
else if (type == atoms->utf8_string) {
|
|
[self handle_utf8_string: pdata pasteboard:pb];
|
|
}
|
|
else if (type == atoms->string) {
|
|
[self handle_string: pdata pasteboard:pb];
|
|
}
|
|
|
|
free_propdata(pdata);
|
|
|
|
[self copy_completed:selection];
|
|
}
|
|
|
|
-(void) copy_completed:(Atom) selection {
|
|
TRACE();
|
|
char *name;
|
|
|
|
(void) name; /* Avoid warning with non-debug compiles. */
|
|
#ifdef DEBUG
|
|
name = XGetAtomName(xpbproxy_dpy, selection);
|
|
if (name) {
|
|
DebugF("copy_completed: %s\n", name);
|
|
XFree(name);
|
|
}
|
|
#endif
|
|
|
|
if (selection == atoms->primary && pending_copy > 0) {
|
|
--pending_copy;
|
|
if (pending_copy > 0) {
|
|
/* Copy PRIMARY again. */
|
|
[self x_copy_request_targets];
|
|
return;
|
|
}
|
|
}
|
|
else if (selection == atoms->clipboard && pending_clipboard > 0) {
|
|
--pending_clipboard;
|
|
if (pending_clipboard > 0) {
|
|
/* Copy CLIPBOARD. */
|
|
[self claim_clipboard];
|
|
return;
|
|
}
|
|
else {
|
|
/* We got the final data. Now set pbproxy as the owner. */
|
|
[self own_clipboard];
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We had 1 or more primary in progress, and the clipboard arrived
|
|
* while we were busy.
|
|
*/
|
|
if (pending_clipboard > 0) {
|
|
[self claim_clipboard];
|
|
}
|
|
}
|
|
|
|
-(void) reload_preferences {
|
|
/*
|
|
* It's uncertain how we could handle the synchronization failing, so cast to void.
|
|
* The prefs_get_bool should fall back to defaults if the org.x.X11 plist doesn't exist or is invalid.
|
|
*/
|
|
(void) CFPreferencesAppSynchronize(app_prefs_domain_cfstr);
|
|
#ifdef STANDALONE_XPBPROXY
|
|
if (xpbproxy_is_standalone)
|
|
pbproxy_prefs.active = YES;
|
|
else
|
|
#endif
|
|
pbproxy_prefs.active =
|
|
prefs_get_bool(CFSTR("sync_pasteboard"), pbproxy_prefs.active);
|
|
pbproxy_prefs.primary_on_grab =
|
|
prefs_get_bool(CFSTR("sync_primary_on_select"),
|
|
pbproxy_prefs.primary_on_grab);
|
|
pbproxy_prefs.clipboard_to_pasteboard =
|
|
prefs_get_bool(CFSTR("sync_clipboard_to_pasteboard"),
|
|
pbproxy_prefs.clipboard_to_pasteboard);
|
|
pbproxy_prefs.pasteboard_to_primary =
|
|
prefs_get_bool(CFSTR("sync_pasteboard_to_primary"),
|
|
pbproxy_prefs.pasteboard_to_primary);
|
|
pbproxy_prefs.pasteboard_to_clipboard =
|
|
prefs_get_bool(CFSTR("sync_pasteboard_to_clipboard"),
|
|
pbproxy_prefs.pasteboard_to_clipboard);
|
|
|
|
/* This is used for debugging. */
|
|
//dump_prefs();
|
|
|
|
if (pbproxy_prefs.active && pbproxy_prefs.primary_on_grab &&
|
|
!xpbproxy_have_xfixes) {
|
|
ErrorF
|
|
("Disabling sync_primary_on_select functionality due to missing XFixes extension.\n");
|
|
pbproxy_prefs.primary_on_grab = NO;
|
|
}
|
|
|
|
/* Claim or release the CLIPBOARD_MANAGER atom */
|
|
if (![self set_clipboard_manager_status:(pbproxy_prefs.active &&
|
|
pbproxy_prefs.
|
|
clipboard_to_pasteboard)])
|
|
pbproxy_prefs.clipboard_to_pasteboard = NO;
|
|
|
|
if (pbproxy_prefs.active && pbproxy_prefs.clipboard_to_pasteboard)
|
|
[self claim_clipboard];
|
|
}
|
|
|
|
-(BOOL) is_active {
|
|
return pbproxy_prefs.active;
|
|
}
|
|
|
|
/* NSPasteboard-required methods */
|
|
|
|
-(void) paste:(id) sender {
|
|
TRACE();
|
|
}
|
|
|
|
-(void) pasteboard:(NSPasteboard *)
|
|
pb provideDataForType:(NSString *) type
|
|
{
|
|
TRACE();
|
|
}
|
|
|
|
-(void) pasteboardChangedOwner:(NSPasteboard *) pb {
|
|
TRACE();
|
|
|
|
/* Right now we don't care with this. */
|
|
}
|
|
|
|
/* Allocation */
|
|
|
|
-init {
|
|
unsigned long pixel;
|
|
|
|
self =[super init];
|
|
if (self == nil)
|
|
return nil;
|
|
|
|
atoms->primary = XInternAtom(xpbproxy_dpy, "PRIMARY", False);
|
|
atoms->clipboard = XInternAtom(xpbproxy_dpy, "CLIPBOARD", False);
|
|
atoms->text = XInternAtom(xpbproxy_dpy, "TEXT", False);
|
|
atoms->utf8_string = XInternAtom(xpbproxy_dpy, "UTF8_STRING", False);
|
|
atoms->string = XInternAtom(xpbproxy_dpy, "STRING", False);
|
|
atoms->targets = XInternAtom(xpbproxy_dpy, "TARGETS", False);
|
|
atoms->multiple = XInternAtom(xpbproxy_dpy, "MULTIPLE", False);
|
|
atoms->cstring = XInternAtom(xpbproxy_dpy, "CSTRING", False);
|
|
atoms->image_png = XInternAtom(xpbproxy_dpy, "image/png", False);
|
|
atoms->image_jpeg = XInternAtom(xpbproxy_dpy, "image/jpeg", False);
|
|
atoms->incr = XInternAtom(xpbproxy_dpy, "INCR", False);
|
|
atoms->atom = XInternAtom(xpbproxy_dpy, "ATOM", False);
|
|
atoms->clipboard_manager =
|
|
XInternAtom(xpbproxy_dpy, "CLIPBOARD_MANAGER", False);
|
|
atoms->compound_text = XInternAtom(xpbproxy_dpy, "COMPOUND_TEXT", False);
|
|
atoms->atom_pair = XInternAtom(xpbproxy_dpy, "ATOM_PAIR", False);
|
|
|
|
pixel = BlackPixel(xpbproxy_dpy, DefaultScreen(xpbproxy_dpy));
|
|
_selection_window =
|
|
XCreateSimpleWindow(xpbproxy_dpy, DefaultRootWindow(xpbproxy_dpy), 0, 0,
|
|
1, 1, 0, pixel, pixel);
|
|
|
|
/* This is used to get PropertyNotify events when doing INCR transfers. */
|
|
XSelectInput(xpbproxy_dpy, _selection_window, PropertyChangeMask);
|
|
|
|
request_atom = None;
|
|
|
|
init_propdata(&pending.propdata);
|
|
pending.requestor = None;
|
|
pending.selection = None;
|
|
|
|
pending_copy = 0;
|
|
pending_clipboard = 0;
|
|
|
|
if (xpbproxy_have_xfixes)
|
|
XFixesSelectSelectionInput(xpbproxy_dpy, _selection_window,
|
|
atoms->primary,
|
|
XFixesSetSelectionOwnerNotifyMask);
|
|
|
|
[self reload_preferences];
|
|
|
|
return self;
|
|
}
|
|
|
|
-(void) dealloc {
|
|
if (None != _selection_window) {
|
|
XDestroyWindow(xpbproxy_dpy, _selection_window);
|
|
_selection_window = None;
|
|
}
|
|
|
|
free_propdata(&pending.propdata);
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
@end
|