492 lines
11 KiB
Objective-C
492 lines
11 KiB
Objective-C
/* x-selection.m -- proxies between NSPasteboard and X11 selections
|
||
$Id: x-selection.m,v 1.9 2006-07-07 18:24:28 jharper Exp $
|
||
|
||
Copyright (c) 2002 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 <X11/Xatom.h>
|
||
|
||
#include <unistd.h>
|
||
|
||
@implementation x_selection
|
||
|
||
static unsigned long *
|
||
read_prop_32 (Window id, Atom prop, int *nitems_ret)
|
||
{
|
||
int r, format;
|
||
Atom type;
|
||
unsigned long nitems, bytes_after;
|
||
unsigned char *data;
|
||
|
||
r = XGetWindowProperty (x_dpy, id, prop, 0, 0,
|
||
False, AnyPropertyType, &type, &format,
|
||
&nitems, &bytes_after, &data);
|
||
|
||
if (r == Success && bytes_after != 0)
|
||
{
|
||
XFree (data);
|
||
r = XGetWindowProperty (x_dpy, id, prop, 0,
|
||
(bytes_after / 4) + 1, False,
|
||
AnyPropertyType, &type, &format,
|
||
&nitems, &bytes_after, &data);
|
||
}
|
||
|
||
if (r != Success)
|
||
return NULL;
|
||
|
||
if (format != 32)
|
||
{
|
||
XFree (data);
|
||
return NULL;
|
||
}
|
||
|
||
*nitems_ret = nitems;
|
||
return (unsigned long *) data;
|
||
}
|
||
|
||
float
|
||
get_time (void)
|
||
{
|
||
extern void Microseconds ();
|
||
UnsignedWide usec;
|
||
long long ll;
|
||
|
||
Microseconds (&usec);
|
||
ll = ((long long) usec.hi << 32) | usec.lo;
|
||
|
||
return ll / 1e6;
|
||
}
|
||
|
||
static Bool
|
||
IfEventWithTimeout (Display *dpy, XEvent *e, int timeout,
|
||
Bool (*pred) (Display *, XEvent *, XPointer),
|
||
XPointer arg)
|
||
{
|
||
float start = get_time ();
|
||
fd_set fds;
|
||
struct timeval tv;
|
||
|
||
do {
|
||
if (XCheckIfEvent (x_dpy, e, pred, arg))
|
||
return True;
|
||
|
||
FD_ZERO (&fds);
|
||
FD_SET (ConnectionNumber (x_dpy), &fds);
|
||
tv.tv_usec = 0;
|
||
tv.tv_sec = timeout;
|
||
|
||
if (select (FD_SETSIZE, &fds, NULL, NULL, &tv) != 1)
|
||
break;
|
||
|
||
} while (start + timeout > get_time ());
|
||
|
||
return False;
|
||
}
|
||
|
||
/* Called when X11 becomes active (i.e. has key focus) */
|
||
- (void) x_active:(Time)timestamp
|
||
{
|
||
TRACE ();
|
||
|
||
if ([_pasteboard changeCount] != _my_last_change)
|
||
{
|
||
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 (x_dpy, x_atom_clipboard,
|
||
_selection_window, timestamp);
|
||
XSetSelectionOwner (x_dpy, XA_PRIMARY,
|
||
_selection_window, timestamp);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Called when X11 loses key focus */
|
||
- (void) x_inactive:(Time)timestamp
|
||
{
|
||
Window w;
|
||
|
||
TRACE ();
|
||
|
||
if (_proxied_selection == XA_PRIMARY)
|
||
return;
|
||
|
||
w = XGetSelectionOwner (x_dpy, x_atom_clipboard);
|
||
|
||
if (w != None && w != _selection_window)
|
||
{
|
||
/* An X client has the selection, proxy it to the pasteboard */
|
||
|
||
_my_last_change = [_pasteboard declareTypes:_known_types owner:self];
|
||
_proxied_selection = x_atom_clipboard;
|
||
}
|
||
}
|
||
|
||
/* 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;
|
||
|
||
/* Lazily copies the PRIMARY selection to the pasteboard. */
|
||
|
||
w = XGetSelectionOwner (x_dpy, XA_PRIMARY);
|
||
|
||
if (w != None && w != _selection_window)
|
||
{
|
||
XSetSelectionOwner (x_dpy, x_atom_clipboard,
|
||
_selection_window, timestamp);
|
||
_my_last_change = [_pasteboard declareTypes:_known_types owner:self];
|
||
_proxied_selection = XA_PRIMARY;
|
||
}
|
||
else
|
||
{
|
||
XBell (x_dpy, 0);
|
||
}
|
||
}
|
||
|
||
|
||
/* X events */
|
||
|
||
- (void) clear_event:(XSelectionClearEvent *)e
|
||
{
|
||
TRACE ();
|
||
|
||
/* Right now we don't care about this. */
|
||
}
|
||
|
||
static Atom
|
||
convert_1 (XSelectionRequestEvent *e, NSString *data, Atom target, Atom prop)
|
||
{
|
||
Atom ret = None;
|
||
|
||
if (data == nil)
|
||
return ret;
|
||
|
||
if (target == x_atom_text)
|
||
target = x_atom_utf8_string;
|
||
|
||
if (target == XA_STRING
|
||
|| target == x_atom_cstring
|
||
|| target == x_atom_utf8_string)
|
||
{
|
||
const char *bytes;
|
||
|
||
if (target == XA_STRING)
|
||
bytes = [data lossyCString];
|
||
else
|
||
bytes = [data UTF8String];
|
||
|
||
if (bytes != NULL)
|
||
{
|
||
XChangeProperty (x_dpy, e->requestor, prop, target,
|
||
8, PropModeReplace, (unsigned char *) bytes,
|
||
strlen (bytes));
|
||
ret = prop;
|
||
}
|
||
}
|
||
/* FIXME: handle COMPOUND_TEXT target */
|
||
|
||
return ret;
|
||
}
|
||
|
||
- (void) request_event:(XSelectionRequestEvent *)e
|
||
{
|
||
/* Someone's asking us for the data on the pasteboard */
|
||
|
||
XEvent reply;
|
||
NSString *data;
|
||
Atom target;
|
||
|
||
TRACE ();
|
||
|
||
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;
|
||
|
||
target = e->target;
|
||
|
||
if (target == x_atom_targets)
|
||
{
|
||
long data[2];
|
||
|
||
data[0] = x_atom_utf8_string;
|
||
data[1] = XA_STRING;
|
||
|
||
XChangeProperty (x_dpy, e->requestor, e->property, target,
|
||
8, PropModeReplace, (unsigned char *) &data,
|
||
sizeof (data));
|
||
reply.xselection.property = e->property;
|
||
}
|
||
else if (target == x_atom_multiple)
|
||
{
|
||
if (e->property != None)
|
||
{
|
||
int i, nitems;
|
||
unsigned long *atoms;
|
||
|
||
atoms = read_prop_32 (e->requestor, e->property, &nitems);
|
||
|
||
if (atoms != NULL)
|
||
{
|
||
data = [_pasteboard stringForType:NSStringPboardType];
|
||
|
||
for (i = 0; i < nitems; i += 2)
|
||
{
|
||
Atom target = atoms[i], prop = atoms[i+1];
|
||
|
||
atoms[i+1] = convert_1 (e, data, target, prop);
|
||
}
|
||
|
||
XChangeProperty (x_dpy, e->requestor, e->property, target,
|
||
32, PropModeReplace, (unsigned char *) atoms,
|
||
nitems);
|
||
XFree (atoms);
|
||
}
|
||
}
|
||
}
|
||
|
||
data = [_pasteboard stringForType:NSStringPboardType];
|
||
if (data != nil)
|
||
{
|
||
reply.xselection.property = convert_1 (e, data, target, e->property);
|
||
}
|
||
|
||
XSendEvent (x_dpy, e->requestor, False, 0, &reply);
|
||
}
|
||
|
||
- (void) notify_event:(XSelectionEvent *)e
|
||
{
|
||
/* Someone sent us data we're waiting for. */
|
||
|
||
Atom type;
|
||
int format, r, offset;
|
||
unsigned long nitems, bytes_after;
|
||
unsigned char *data, *buf;
|
||
NSString *string;
|
||
|
||
TRACE ();
|
||
|
||
if (e->target == x_atom_targets)
|
||
{
|
||
/* Was trying to fetch the TARGETS property; it lists the
|
||
formats supported by the selection owner. */
|
||
|
||
unsigned long *atoms;
|
||
int natoms;
|
||
int i, utf8_i = -1, string_i = -1;
|
||
|
||
if (e->property != None
|
||
&& (atoms = read_prop_32 (e->requestor,
|
||
e->property, &natoms)) != NULL)
|
||
{
|
||
for (i = 0; i < natoms; i++)
|
||
{
|
||
if (atoms[i] == XA_STRING)
|
||
string_i = i;
|
||
else if (atoms[i] == x_atom_utf8_string)
|
||
utf8_i = i;
|
||
}
|
||
XFree (atoms);
|
||
}
|
||
|
||
/* May as well try as STRING if nothing else, it can only
|
||
fail, and it will help broken clients who don't support
|
||
the TARGETS selection.. */
|
||
|
||
if (utf8_i >= 0)
|
||
type = x_atom_utf8_string;
|
||
else
|
||
type = XA_STRING;
|
||
|
||
XConvertSelection (x_dpy, e->selection, type,
|
||
e->selection, e->requestor, e->time);
|
||
_pending_notify = YES;
|
||
return;
|
||
}
|
||
|
||
if (e->property == None)
|
||
return; /* FIXME: notify pasteboard? */
|
||
|
||
/* Should be the data. Find out how big it is and what format it's in. */
|
||
|
||
r = XGetWindowProperty (x_dpy, e->requestor, e->property,
|
||
0, 0, False, AnyPropertyType, &type,
|
||
&format, &nitems, &bytes_after, &data);
|
||
if (r != Success)
|
||
return;
|
||
|
||
XFree (data);
|
||
if (type == None || format != 8)
|
||
return;
|
||
|
||
bytes_after += nitems;
|
||
|
||
/* Read it into a buffer. */
|
||
|
||
buf = malloc (bytes_after + 1);
|
||
if (buf == NULL)
|
||
return;
|
||
|
||
for (offset = 0; bytes_after > 0; offset += nitems)
|
||
{
|
||
r = XGetWindowProperty (x_dpy, e->requestor, e->property,
|
||
offset / 4, (bytes_after / 4) + 1,
|
||
False, AnyPropertyType, &type,
|
||
&format, &nitems, &bytes_after, &data);
|
||
if (r != Success)
|
||
{
|
||
free (buf);
|
||
return;
|
||
}
|
||
|
||
memcpy (buf + offset, data, nitems);
|
||
XFree (data);
|
||
}
|
||
buf[offset] = 0;
|
||
XDeleteProperty (x_dpy, e->requestor, e->property);
|
||
|
||
/* Convert to an NSString and write to the pasteboard. */
|
||
|
||
if (type == XA_STRING)
|
||
string = [NSString stringWithCString:(char *) buf];
|
||
else /* if (type == x_atom_utf8_string) */
|
||
string = [NSString stringWithUTF8String:(char *) buf];
|
||
|
||
free (buf);
|
||
|
||
[_pasteboard setString:string forType:NSStringPboardType];
|
||
}
|
||
|
||
|
||
/* NSPasteboard-required methods */
|
||
|
||
static Bool
|
||
selnotify_pred (Display *dpy, XEvent *e, XPointer arg)
|
||
{
|
||
return e->type == SelectionNotify;
|
||
}
|
||
|
||
- (void) pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type
|
||
{
|
||
XEvent e;
|
||
Atom request;
|
||
|
||
TRACE ();
|
||
|
||
/* Don't ask for the data yet, first find out which formats
|
||
the selection owner supports. */
|
||
|
||
request = x_atom_targets;
|
||
|
||
again:
|
||
XConvertSelection (x_dpy, _proxied_selection, request,
|
||
_proxied_selection, _selection_window, CurrentTime);
|
||
|
||
_pending_notify = YES;
|
||
|
||
/* Seems like we need to be synchronous here.. Actually, this really
|
||
sucks, since it means we could get deadlocked if people don't
|
||
respond to our request. So we need to implement our own timeout
|
||
code.. */
|
||
|
||
while (_pending_notify
|
||
&& IfEventWithTimeout (x_dpy, &e, 1, selnotify_pred, NULL))
|
||
{
|
||
_pending_notify = NO;
|
||
[self notify_event:&e.xselection];
|
||
}
|
||
|
||
if (_pending_notify && request == x_atom_targets)
|
||
{
|
||
/* App didn't respond to request for TARGETS selection. Let's
|
||
try the STRING selection as a last resort.. Helps broken
|
||
applications (e.g. nedit, see #3199867) */
|
||
|
||
request = XA_STRING;
|
||
goto again;
|
||
}
|
||
|
||
_pending_notify = NO;
|
||
}
|
||
|
||
- (void) pasteboardChangedOwner:(NSPasteboard *)sender
|
||
{
|
||
TRACE ();
|
||
|
||
/* Right now we don't care with this. */
|
||
}
|
||
|
||
|
||
/* Allocation */
|
||
|
||
- init
|
||
{
|
||
unsigned long pixel;
|
||
|
||
self = [super init];
|
||
if (self == nil)
|
||
return nil;
|
||
|
||
_pasteboard = [[NSPasteboard generalPasteboard] retain];
|
||
|
||
_known_types = [[NSArray arrayWithObject:NSStringPboardType] retain];
|
||
|
||
pixel = BlackPixel (x_dpy, DefaultScreen (x_dpy));
|
||
_selection_window = XCreateSimpleWindow (x_dpy, DefaultRootWindow (x_dpy),
|
||
0, 0, 1, 1, 0, pixel, pixel);
|
||
|
||
return self;
|
||
}
|
||
|
||
- (void) dealloc
|
||
{
|
||
[_pasteboard releaseGlobally];
|
||
[_pasteboard release];
|
||
_pasteboard = nil;
|
||
|
||
[_known_types release];
|
||
_known_types = nil;
|
||
|
||
if (_selection_window != 0)
|
||
{
|
||
XDestroyWindow (x_dpy, _selection_window);
|
||
_selection_window = 0;
|
||
}
|
||
|
||
[super dealloc];
|
||
}
|
||
|
||
@end
|