1542 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
			
		
		
	
	
			1542 lines
		
	
	
		
			38 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 (FILE *fp) {
 | 
						|
    fprintf(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   
 | 
						|
	printf("bytesleft %lu\n", bytesleft);
 | 
						|
#endif
 | 
						|
 | 
						|
	if (Success != XGetWindowProperty (xpbproxy_dpy, win, property,
 | 
						|
					   offset, length, delete, 
 | 
						|
					   AnyPropertyType,
 | 
						|
					   type, &format, &numitems, 
 | 
						|
					   &bytesleft, &chunk)) 
 | 
						|
	{
 | 
						|
	    DB ("Error while getting window property.\n");
 | 
						|
	    *pdata = null_propdata;
 | 
						|
	    free (buf);
 | 
						|
	    return True;
 | 
						|
	}
 | 
						|
	
 | 
						|
#ifdef TEST
 | 
						|
	printf("format %d numitems %lu bytesleft %lu\n",
 | 
						|
	       format, numitems, bytesleft);
 | 
						|
	
 | 
						|
	printf("type %s\n", XGetAtomName (xpbproxy_dpy, *type));
 | 
						|
#endif
 | 
						|
	
 | 
						|
	/* Format is the number of bits. */
 | 
						|
	chunkbytesize = numitems * (format / 8);
 | 
						|
 | 
						|
#ifdef TEST
 | 
						|
	printf("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
 | 
						|
	printf("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)
 | 
						|
    {
 | 
						|
	fprintf(stderr, "Atom list is expected to be formatted as an array of 32bit values.\n");
 | 
						|
	return None;
 | 
						|
    }
 | 
						|
 | 
						|
    for (i = 0, step = pdata->format >> 3; i < pdata->length; i += step)
 | 
						|
    {
 | 
						|
	a = (Atom)*(uint32_t *)(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)
 | 
						|
	    {
 | 
						|
		DB("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)
 | 
						|
    {
 | 
						|
        DB ("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) {
 | 
						|
            fprintf (stderr, "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 ();
 | 
						|
        
 | 
						|
    DB ("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.
 | 
						|
             */
 | 
						|
            fprintf(stderr, "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.
 | 
						|
         */
 | 
						|
        DB ("No clipboard owner.\n");
 | 
						|
        [self copy_completed:atoms->clipboard];
 | 
						|
        return;
 | 
						|
    } else if (owner == _selection_window) {
 | 
						|
        [self copy_completed:atoms->clipboard];
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    
 | 
						|
    DB ("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.
 | 
						|
     */
 | 
						|
    DB ("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... */
 | 
						|
	    DB ("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.
 | 
						|
	 */
 | 
						|
 | 
						|
	if ([pbtypes containsObject:NSPICTPboardType] 
 | 
						|
	    || [pbtypes containsObject:NSTIFFPboardType]) 
 | 
						|
	{
 | 
						|
	    /* We can convert a TIFF to a PNG or JPEG. */
 | 
						|
	    DB ("NSTIFFPboardType\n");
 | 
						|
	    list[count] = atoms->image_png;
 | 
						|
	    ++count;
 | 
						|
	    list[count] = atoms->image_jpeg;
 | 
						|
	    ++count;
 | 
						|
	} 
 | 
						|
 | 
						|
	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;
 | 
						|
    }
 | 
						|
 | 
						|
    DB ("pbtypes retainCount after containsObject: %u\n", [pbtypes retainCount]);
 | 
						|
 | 
						|
    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) {
 | 
						|
	    DB ("UTF-8: %s\n", bytes);
 | 
						|
	    DB ("UTF-8 length: %u\n", length); 
 | 
						|
	}
 | 
						|
    } 
 | 
						|
    else 
 | 
						|
    {
 | 
						|
	DB ("Latin-1\n");
 | 
						|
	bytes = [data cStringUsingEncoding:NSISOLatin1StringEncoding];
 | 
						|
	/*WARNING: bytes is not NUL-terminated. */
 | 
						|
	length = [data lengthOfBytesUsingEncoding:NSISOLatin1StringEncoding];
 | 
						|
    }
 | 
						|
 | 
						|
    DB ("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)
 | 
						|
		    DB ("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];
 | 
						|
	fprintf(stderr, "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 
 | 
						|
    {
 | 
						|
	fprintf(stderr, "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;
 | 
						|
	} 
 | 
						|
     	else if ([pbtypes containsObject:NSPICTPboardType])
 | 
						|
	{
 | 
						|
	    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)
 | 
						|
	DB ("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) {
 | 
						|
	DB ("e->property is None.\n");
 | 
						|
	[self copy_completed:e->selection];
 | 
						|
	/* Nothing is selected. */
 | 
						|
	return;
 | 
						|
    }
 | 
						|
 | 
						|
#if 0
 | 
						|
    printf ("e->selection %s\n", XGetAtomName (xpbproxy_dpy, e->selection));
 | 
						|
    printf ("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.
 | 
						|
	 */
 | 
						|
	DB ("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;
 | 
						|
 | 
						|
	DB ("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];
 | 
						|
	
 | 
						|
	DB ("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) 
 | 
						|
	{
 | 
						|
	    DB ("e->atom %s\n", name);
 | 
						|
	    XFree(name);
 | 
						|
	}
 | 
						|
#endif
 | 
						|
    }
 | 
						|
 | 
						|
    if (None != pending.requestor && PropertyNewValue == e->state) 
 | 
						|
    {
 | 
						|
	DB ("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:
 | 
						|
            fprintf(stderr, "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)
 | 
						|
    {
 | 
						|
	DB ("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)
 | 
						|
    {
 | 
						|
	DB ("unable to create NSData object!\n");
 | 
						|
	return;
 | 
						|
    }
 | 
						|
 | 
						|
    DB ("data retainCount before NSBitmapImageRep initWithData: %u\n",
 | 
						|
	[data retainCount]);
 | 
						|
 | 
						|
    bmimage = [[NSBitmapImageRep alloc] initWithData:data];
 | 
						|
 | 
						|
    if (nil == bmimage)
 | 
						|
    {
 | 
						|
	[data autorelease];
 | 
						|
	DB ("unable to create NSBitmapImageRep!\n");
 | 
						|
	return;
 | 
						|
    }
 | 
						|
 | 
						|
    DB ("data retainCount after NSBitmapImageRep initWithData: %u\n", 
 | 
						|
	[data retainCount]);
 | 
						|
 | 
						|
    @try 
 | 
						|
    {
 | 
						|
	tiff = [bmimage TIFFRepresentation];
 | 
						|
    }
 | 
						|
 | 
						|
    @catch (NSException *e) 
 | 
						|
    {
 | 
						|
	DB ("NSTIFFException!\n");
 | 
						|
	[data autorelease];
 | 
						|
	[bmimage autorelease];
 | 
						|
	return;
 | 
						|
    }
 | 
						|
    
 | 
						|
    DB ("bmimage retainCount after TIFFRepresentation %u\n", [bmimage retainCount]);
 | 
						|
 | 
						|
    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])
 | 
						|
    {
 | 
						|
	DB ("writing pasteboard data failed!\n");
 | 
						|
    }
 | 
						|
 | 
						|
    [data autorelease];
 | 
						|
 | 
						|
    DB ("bmimage retainCount before release %u\n", [bmimage retainCount]);
 | 
						|
    [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]) {
 | 
						|
	fprintf(stderr, "pasteboard setString:forType: failed!\n");
 | 
						|
    }
 | 
						|
    [string autorelease];
 | 
						|
    DB ("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]) {
 | 
						|
	fprintf(stderr, "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)
 | 
						|
    {
 | 
						|
	DB ("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(stdout);
 | 
						|
 | 
						|
    if(pbproxy_prefs.active && pbproxy_prefs.primary_on_grab && !xpbproxy_have_xfixes) {
 | 
						|
        fprintf(stderr, "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
 |