rdesktop/xclip.c

483 lines
13 KiB
C
Raw Normal View History

/* -*- c-basic-offset: 8 -*-
rdesktop: A Remote Desktop Protocol client.
Protocol services - Clipboard functions
Copyright (C) Erik Forsberg <forsberg@cendio.se> 2003
Copyright (C) Matthew Chapman 2003
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include "rdesktop.h"
#define NUM_TARGETS 6
extern Display *g_display;
extern Window g_wnd;
extern Time g_last_gesturetime;
static Atom clipboard_atom, primary_atom, targets_atom, timestamp_atom;
static Atom rdesktop_clipboard_target_atom, rdesktop_clipboard_formats_atom, incr_atom;
static XSelectionRequestEvent selection_request;
static Atom targets[NUM_TARGETS];
static int have_primary = 0;
static int rdesktop_is_selection_owner = 0;
static int g_waiting_for_INCR = 0;
static uint8 *g_clip_buffer = 0;
static uint32 g_clip_buflen = 0;
/* Replace CR-LF to LF (well, rather removing all CR:s) This is done
in-place. The length is updated. Handles embedded nulls */
static void
crlf2lf(uint8 * data, uint32 * length)
{
uint8 *dst, *src;
src = dst = data;
while (src < data + *length)
{
if (*src != '\x0d')
*dst++ = *src;
src++;
}
*length = dst - data;
}
/* Translate LF to CR-LF. To do this, we must allocate more memory.
The length is updated. */
static uint8 *
lf2crlf(uint8 * data, uint32 * length)
{
uint8 *result, *p, *o;
/* Worst case: Every char is LF */
result = xmalloc(*length * 2);
p = data;
o = result;
while (p < data + *length)
{
if (*p == '\x0a')
*o++ = '\x0d';
*o++ = *p++;
}
*length = o - result;
/* Convenience */
*o++ = '\0';
return result;
}
static void
xclip_provide_selection(XSelectionRequestEvent * req, Atom type, unsigned int format, uint8 * data,
uint32 length)
{
XEvent xev;
XChangeProperty(g_display, req->requestor, req->property,
type, format, PropModeReplace, data, length);
xev.xselection.type = SelectionNotify;
xev.xselection.serial = 0;
xev.xselection.send_event = True;
xev.xselection.requestor = req->requestor;
xev.xselection.selection = req->selection;
xev.xselection.target = req->target;
xev.xselection.property = req->property;
xev.xselection.time = req->time;
XSendEvent(g_display, req->requestor, False, NoEventMask, &xev);
}
void
xclip_handle_SelectionNotify(XSelectionEvent * event)
{
unsigned long nitems, bytes_left;
XWindowAttributes wa;
Atom type, best_target, text_target;
Atom *supported_targets;
int res, i, format;
uint8 *data;
if (event->property == None)
goto fail;
DEBUG_CLIPBOARD(("xclip_handle_SelectionNotify: selection=%s, target=%s, property=%s\n",
XGetAtomName(g_display, event->selection),
XGetAtomName(g_display, event->target),
XGetAtomName(g_display, event->property)));
if (event->property == None)
goto fail;
res = XGetWindowProperty(g_display, g_wnd, rdesktop_clipboard_target_atom,
0, XMaxRequestSize(g_display), True, AnyPropertyType,
&type, &format, &nitems, &bytes_left, &data);
if (res != Success)
{
DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n"));
goto fail;
}
/* Negotiate target format */
if (event->target == targets_atom)
{
/* FIXME: We should choose format here based on what the server wanted */
best_target = XA_STRING;
if (type != None)
{
supported_targets = (Atom *) data;
text_target = XInternAtom(g_display, "TEXT", False);
for (i = 0; i < nitems; i++)
{
DEBUG_CLIPBOARD(("Target %d: %s\n", i,
XGetAtomName(g_display, supported_targets[i])));
if (supported_targets[i] == text_target)
{
DEBUG_CLIPBOARD(("Other party supports TEXT, choosing that as best_target\n"));
best_target = text_target;
break;
}
}
XFree(data);
}
XConvertSelection(g_display, primary_atom, best_target,
rdesktop_clipboard_target_atom, g_wnd, event->time);
return;
}
if (type == incr_atom)
{
DEBUG_CLIPBOARD(("Received INCR.\n"));
XGetWindowAttributes(g_display, g_wnd, &wa);
if ((wa.your_event_mask | PropertyChangeMask) != wa.your_event_mask)
{
XSelectInput(g_display, g_wnd, (wa.your_event_mask | PropertyChangeMask));
}
XDeleteProperty(g_display, g_wnd, type);
XFree(data);
g_waiting_for_INCR = 1;
if ((XGetWindowProperty(g_display, g_wnd, rdesktop_clipboard_target_atom, 0,
4096L, True, AnyPropertyType,
&type, &format, &nitems, &bytes_left, &data) != Success))
{
DEBUG_CLIPBOARD(("XGetWindowProperty failed.\n"));
goto fail;
}
else
{
uint8 *translated_data;
uint32 length = nitems;
translated_data = lf2crlf(data, &length);
g_clip_buffer = (uint8 *) xmalloc(length);
strncpy((char*)g_clip_buffer, (char*)translated_data, length);
xfree(translated_data);
g_clip_buflen = length;
XFree(data);
return;
}
}
/* Translate linebreaks, but only if not getting data from
other rdesktop instance */
if (event->target != rdesktop_clipboard_formats_atom)
{
uint8 *translated_data;
uint32 length = nitems;
DEBUG_CLIPBOARD(("Translating linebreaks before sending data\n"));
translated_data = lf2crlf(data, &length);
cliprdr_send_data(translated_data, length + 1);
xfree(translated_data); /* Not the same thing as XFree! */
}
else
{
cliprdr_send_data(data, nitems + 1);
}
XFree(data);
if (!rdesktop_is_selection_owner)
cliprdr_send_text_format_announce();
return;
fail:
cliprdr_send_data(NULL, 0);
}
void
xclip_handle_SelectionRequest(XSelectionRequestEvent * event)
{
unsigned long nitems, bytes_left;
unsigned char *prop_return;
uint32 *wanted_format;
int format, res;
Atom type;
DEBUG_CLIPBOARD(("xclip_handle_SelectionRequest: selection=%s, target=%s, property=%s\n",
XGetAtomName(g_display, event->selection),
XGetAtomName(g_display, event->target),
XGetAtomName(g_display, event->property)));
if (event->target == targets_atom)
{
xclip_provide_selection(event, XA_ATOM, 32, (uint8 *) & targets, NUM_TARGETS);
return;
}
else if (event->target == timestamp_atom)
{
xclip_provide_selection(event, XA_INTEGER, 32, (uint8 *) & g_last_gesturetime, 1);
return;
}
else if (event->target == rdesktop_clipboard_formats_atom)
{
res = XGetWindowProperty(g_display, event->requestor,
rdesktop_clipboard_target_atom, 0, 1, True, XA_INTEGER,
&type, &format, &nitems, &bytes_left, &prop_return);
wanted_format = (uint32 *) prop_return;
format = (res == Success) ? *wanted_format : CF_TEXT;
/* FIXME: Need to free returned data? */
}
else
{
format = CF_TEXT;
}
cliprdr_send_data_request(format);
selection_request = *event;
/* wait for data */
}
void
xclip_handle_SelectionClear(void)
{
DEBUG_CLIPBOARD(("xclip_handle_SelectionClear\n"));
have_primary = 0;
XDeleteProperty(g_display, DefaultRootWindow(g_display), rdesktop_clipboard_formats_atom);
cliprdr_send_text_format_announce();
}
void
xclip_handle_PropertyNotify(XPropertyEvent * event)
{
unsigned long nitems, bytes_left;
int format, res;
XWindowAttributes wa;
uint8 *data;
Atom type;
if (event->state == PropertyNewValue && g_waiting_for_INCR)
{
DEBUG_CLIPBOARD(("x_clip_handle_PropertyNotify: g_waiting_for_INCR != 0\n"));
if ((XGetWindowProperty(g_display, g_wnd, rdesktop_clipboard_target_atom, 0,
4096L, True, AnyPropertyType,
&type, &format, &nitems, &bytes_left, &data) != Success))
{
XFree(data);
return;
}
if (nitems == 0)
{
XGetWindowAttributes(g_display, g_wnd, &wa);
XSelectInput(g_display, g_wnd, (wa.your_event_mask ^ PropertyChangeMask));
XFree(data);
g_waiting_for_INCR = 0;
if (g_clip_buflen > 0)
{
cliprdr_send_data(g_clip_buffer, g_clip_buflen + 1);
if (!rdesktop_is_selection_owner)
cliprdr_send_text_format_announce();
xfree(g_clip_buffer);
g_clip_buffer = 0;
g_clip_buflen = 0;
}
}
else
{
uint8 *translated_data;
uint32 length = nitems;
DEBUG_CLIPBOARD(("Translating linebreaks before sending data\n"));
translated_data = lf2crlf(data, &length);
uint8 *tmp = xmalloc(length + g_clip_buflen);
strncpy((char*)tmp, (char*)g_clip_buffer, g_clip_buflen);
xfree(g_clip_buffer);
strncpy((char*)(tmp + g_clip_buflen), (char*)translated_data, length);
xfree(translated_data);
g_clip_buffer = tmp;
g_clip_buflen += length;
XFree(data);
return;
}
}
if (event->atom != rdesktop_clipboard_formats_atom)
return;
if (have_primary) /* from us */
return;
if (event->state == PropertyNewValue)
{
res = XGetWindowProperty(g_display, DefaultRootWindow(g_display),
rdesktop_clipboard_formats_atom, 0,
XMaxRequestSize(g_display), False, XA_STRING, &type,
&format, &nitems, &bytes_left, &data);
if ((res == Success) && (nitems > 0))
{
cliprdr_send_native_format_announce(data, nitems);
rdesktop_is_selection_owner = 1;
return;
}
}
/* PropertyDelete, or XGetWindowProperty failed */
cliprdr_send_text_format_announce();
rdesktop_is_selection_owner = 0;
}
void
ui_clip_format_announce(uint8 * data, uint32 length)
{
XSetSelectionOwner(g_display, primary_atom, g_wnd, g_last_gesturetime);
if (XGetSelectionOwner(g_display, primary_atom) != g_wnd)
{
warning("Failed to aquire ownership of PRIMARY clipboard\n");
return;
}
have_primary = 1;
XChangeProperty(g_display, DefaultRootWindow(g_display),
rdesktop_clipboard_formats_atom, XA_STRING, 8, PropModeReplace, data,
length);
XSetSelectionOwner(g_display, clipboard_atom, g_wnd, g_last_gesturetime);
if (XGetSelectionOwner(g_display, clipboard_atom) != g_wnd)
warning("Failed to aquire ownership of CLIPBOARD clipboard\n");
}
void
ui_clip_handle_data(uint8 * data, uint32 length)
{
if (selection_request.target != rdesktop_clipboard_formats_atom)
{
uint8 *firstnull;
/* translate linebreaks */
crlf2lf(data, &length);
/* Only send data up to null byte, if any */
firstnull = (uint8 *) strchr((char *) data, '\0');
if (firstnull)
{
length = firstnull - data + 1;
}
}
xclip_provide_selection(&selection_request, XA_STRING, 8, data, length - 1);
}
void
ui_clip_request_data(uint32 format)
{
Window selectionowner;
DEBUG_CLIPBOARD(("Request from server for format %d\n", format));
if (rdesktop_is_selection_owner)
{
XChangeProperty(g_display, g_wnd, rdesktop_clipboard_target_atom,
XA_INTEGER, 32, PropModeReplace, (unsigned char *) &format, 1);
XConvertSelection(g_display, primary_atom, rdesktop_clipboard_formats_atom,
rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
return;
}
selectionowner = XGetSelectionOwner(g_display, primary_atom);
if (selectionowner != None)
{
XConvertSelection(g_display, primary_atom, targets_atom,
rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
return;
}
/* No PRIMARY, try CLIPBOARD */
selectionowner = XGetSelectionOwner(g_display, clipboard_atom);
if (selectionowner != None)
{
XConvertSelection(g_display, clipboard_atom, targets_atom,
rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
return;
}
/* No data available */
cliprdr_send_data(NULL, 0);
}
void
ui_clip_sync(void)
{
cliprdr_send_text_format_announce();
}
void
xclip_init(void)
{
if (!cliprdr_init())
return;
primary_atom = XInternAtom(g_display, "PRIMARY", False);
clipboard_atom = XInternAtom(g_display, "CLIPBOARD", False);
targets_atom = XInternAtom(g_display, "TARGETS", False);
timestamp_atom = XInternAtom(g_display, "TIMESTAMP", False);
rdesktop_clipboard_target_atom =
XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_TARGET", False);
incr_atom = XInternAtom(g_display, "INCR", False);
targets[0] = targets_atom;
targets[1] = XInternAtom(g_display, "TEXT", False);
targets[2] = XInternAtom(g_display, "STRING", False);
targets[3] = XInternAtom(g_display, "text/unicode", False);
targets[4] = XInternAtom(g_display, "TIMESTAMP", False);
targets[5] = XA_STRING;
/* rdesktop sets _RDESKTOP_CLIPBOARD_FORMATS on the root window when acquiring the clipboard.
Other interested rdesktops can use this to notify their server of the available formats. */
rdesktop_clipboard_formats_atom =
XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_FORMATS", False);
XSelectInput(g_display, DefaultRootWindow(g_display), PropertyChangeMask);
}