rdesktop/cliprdr.c
Erik Forsberg 2281c6b409 Added one to the length of sent data, since XGetProperty is not reporting the
null byte (although space is allocated for it as it seems).

Resend format announces if they fail, with a small delay. Ugly hack, but
it works..


git-svn-id: svn://svn.code.sf.net/p/rdesktop/code/trunk/rdesktop@388 423420c4-83ab-492f-b58f-81f9feb106b5
2003-06-06 09:26:49 +00:00

568 lines
14 KiB
C

/* -*- c-basic-offset: 8 -*-
rdesktop: A Remote Desktop Protocol client.
Protocol services - Clipboard functions
Copyright (C) Erik Forsberg <forsberg@cendio.se> 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"
extern BOOL encryption;
extern Display *display;
extern Window wnd;
extern Time last_gesturetime;
// static Time selection_timestamp;
static Atom clipboard_atom, primary_atom, targets_atom, timestamp_atom;
static Atom rdesktop_clipboard_target_atom;
static cliprdr_dataformat *server_formats = NULL;
static uint16 num_server_formats = 0;
static XSelectionEvent selection_event;
static void
cliprdr_print_server_formats(void)
{
#ifdef WITH_DEBUG_CLIPBOARD
cliprdr_dataformat *this;
uint16 i = 0;
this = server_formats;
DEBUG_CLIPBOARD(("There should be %d server formats.\n", num_server_formats));
while (NULL != this)
{
DEBUG_CLIPBOARD(("Format code %d\n", this->identifier));
i++;
this = this->next;
}
DEBUG_CLIPBOARD(("There were %d server formats.\n", i));
#endif
}
/*
static void
cliprdr_set_selection_timestamp(void)
{
XEvent xev;
DEBUG_CLIPBOARD(("Changing a property in order to get a timestamp\n"));
fflush(stdout);
XChangeProperty(display, wnd, rdesktop_clipboard_target_atom,
XA_ATOM, 32, PropModeAppend, 0, 0);
DEBUG_CLIPBOARD(("Waiting for PropertyChange on wnd\n"));
fflush(stdout);
XWindowEvent(display, wnd,
PropertyChangeMask, &xev);
DEBUG_CLIPBOARD(("Setting selection_timestamp\n"));
fflush(stdout);
selection_timestamp = xev.xproperty.time;
}
*/
static void
cliprdr_send_format_announce(void)
{
DEBUG_CLIPBOARD(("Sending format announce\n"));
STREAM s;
int number_of_formats = 1;
s = sec_init(encryption ? SEC_ENCRYPT : 0, number_of_formats*36+12+4+4);
out_uint32_le(s, number_of_formats*36+12);
out_uint32_le(s, 0x13);
out_uint16_le(s, 2);
out_uint16_le(s, 0);
out_uint32_le(s, number_of_formats*36);
// out_uint32_le(s, 0xd); // FIXME: This is a rather bogus unicode text description..
// rdp_out_unistr(s, "", 16);
// out_uint8s(s, 32);
out_uint32_le(s, 1); // FIXME: This is a rather bogus text description..
out_uint8s(s, 32);
out_uint32_le(s, 0);
s_mark_end(s);
sec_send_to_channel(s, encryption ? SEC_ENCRYPT : 0, 1005); // FIXME: Don't hardcode channel!
}
static void
cliprdr_send_empty_datapacket(void)
{
STREAM out;
out = sec_init(encryption ? SEC_ENCRYPT : 0,
20);
out_uint32_le(out, 12);
out_uint32_le(out, 0x13);
out_uint16_le(out, 5);
out_uint16_le(out, 1);
out_uint32_le(out, 0);
/* Insert null string here? */
out_uint32_le(out, 0);
s_mark_end(out);
sec_send_to_channel(out, encryption ? SEC_ENCRYPT : 0, 1005); // FIXME: Don't hardcode channel!
}
void
cliprdr_handle_SelectionNotify(XSelectionEvent *event)
{
unsigned char *data;
unsigned long nitems, bytes_left;
int res;
int format;
Atom type_return;
Atom best_target;
STREAM out;
DEBUG_CLIPBOARD(("cliprdr_handle_SelectionNotify\n"));
if (None == event->property) {
cliprdr_send_empty_datapacket();
return; /* Selection failed */
}
DEBUG_CLIPBOARD(("selection: %s, target: %s, property: %s\n",
XGetAtomName(display, event->selection),
XGetAtomName(display, event->target),
XGetAtomName(display, event->property)));
if (targets_atom == event->target) {
/* Response to TARGETS request. Let's find the target
we want and request that */
res = XGetWindowProperty(display, wnd,
rdesktop_clipboard_target_atom,
0L, 4096L, False, AnyPropertyType,
&type_return,
&format, &nitems, &bytes_left, &data);
if (Success != res)
{
DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n"));
cliprdr_send_empty_datapacket();
return;
}
if (None == type_return)
/* The owner might no support TARGETS. Just try
STRING */
best_target = XA_STRING;
else
{
/* FIXME: We should choose format here based
on what the server wanted */
best_target = XInternAtom(display, "TEXT", False);
}
XConvertSelection(display, primary_atom,
best_target,
rdesktop_clipboard_target_atom,
wnd, event->time);
}
else /* Other clipboard data */
{
res = XGetWindowProperty(display, wnd,
rdesktop_clipboard_target_atom,
0L, 4096L, False, AnyPropertyType,
&type_return,
&format, &nitems, &bytes_left, &data);
if (Success != res)
{
DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n"));
cliprdr_send_empty_datapacket();
return;
}
/* We need to handle INCR as well */
out = sec_init(encryption ? SEC_ENCRYPT : 0,
20+nitems+1);
out_uint32_le(out, 12+nitems+1);
out_uint32_le(out, 0x13);
out_uint16_le(out, 5);
out_uint16_le(out, 1);
out_uint32_le(out, nitems+1);
out_uint8p(out, data, nitems+1);
/* Insert null string here? */
out_uint32_le(out, 0);
s_mark_end(out);
sec_send_to_channel(out, encryption ? SEC_ENCRYPT : 0, 1005); // FIXME: Don't hardcode channel!
cliprdr_send_format_announce();
}
}
void
cliprdr_handle_SelectionClear(void)
{
DEBUG_CLIPBOARD(("cliprdr_handle_SelectionClear\n"));
cliprdr_send_format_announce();
}
static void
cliprdr_request_clipboard_data(uint32 formatcode)
{
STREAM s;
s = sec_init(encryption ? SEC_ENCRYPT : 0, 24);
out_uint32_le(s, 16);
out_uint32_le(s, 0x13);
out_uint16_le(s, 4);
out_uint16_le(s, 0);
out_uint32_le(s, 4); // Remaining length
out_uint32_le(s, formatcode);
out_uint32_le(s, 0); // Unknown. Garbage pad?
s_mark_end(s);
sec_send_to_channel(s, encryption ? SEC_ENCRYPT : 0, 1005); // FIXME: Don't hardcode channel!
}
void
cliprdr_handle_SelectionRequest(XSelectionRequestEvent *xevent)
{
Atom *targets;
int res;
XSelectionEvent xev;
DEBUG_CLIPBOARD(("cliprdr_handle_SelectionRequest\n"));
DEBUG_CLIPBOARD(("Requestor window id 0x%x ",
(unsigned)xevent->requestor));
if (clipboard_atom == xevent->selection) {
DEBUG_CLIPBOARD(("wants CLIPBOARD\n"));
}
if (primary_atom == xevent->selection) {
DEBUG_CLIPBOARD(("wants PRIMARY\n"));
}
DEBUG_CLIPBOARD(("Target is %s (0x%x), property is %s (0x%x)\n",
XGetAtomName(display, xevent->target),
(unsigned)xevent->target,
XGetAtomName(display, xevent->property),
(unsigned)xevent->property));
xev.type = SelectionNotify;
xev.serial = 0;
xev.send_event = True;
xev.requestor = xevent->requestor;
xev.selection = xevent->selection;
xev.target = xevent->target;
xev.property = xevent->property;
xev.time = xevent->time;
if (targets_atom == xevent->target)
{
DEBUG_CLIPBOARD(("TARGETS requested, sending list..\n"));
targets = xmalloc(4*sizeof(Atom));
targets[0] = xevent->target;
targets[1] = XInternAtom(display, "TEXT", True);
targets[2] = XInternAtom(display, "UTF8_STRING", True);
targets[3] = XInternAtom(display, "TIMESTAMP", True);
res = XChangeProperty(display,
xevent->requestor,
xevent->property,
XA_ATOM,
32,
PropModeAppend,
(unsigned char *)targets,
3);
res = XSendEvent(display,
xevent->requestor,
False,
NoEventMask,
(XEvent *)&xev);
return;
} else if (timestamp_atom == xevent->target)
{
DEBUG_CLIPBOARD(("TIMESTAMP requested... sending 0x%x\n",
(unsigned)last_gesturetime));
res = XChangeProperty(display,
xevent->requestor,
xevent->property,
XA_INTEGER,
32,
PropModeAppend,
(unsigned char *)&last_gesturetime,
1);
res = XSendEvent(display,
xevent->requestor,
False,
NoEventMask,
(XEvent *)&xev);
} else /* Some other target */
{
cliprdr_request_clipboard_data(CF_TEXT);
memcpy(&selection_event, &xev, sizeof(xev));
/* Return and wait for data, handled by
cliprdr_handle_server_data */
}
}
static void
cliprdr_ack_format_list(void)
{
STREAM s;
s = sec_init(encryption ? SEC_ENCRYPT : 0, 20);
out_uint32_le(s, 12);
out_uint32_le(s, 0x13);
out_uint16_le(s, 3);
out_uint16_le(s, 1);
out_uint32_le(s, 0);
out_uint32_le(s, 0x0000c0da);
s_mark_end(s);
sec_send_to_channel(s, encryption ? SEC_ENCRYPT : 0, 1005); // FIXME: Don't hardcode channel!
}
static void
cliprdr_register_server_formats(STREAM s)
{
uint32 remaining_length, pad;
uint16 num_formats;
cliprdr_dataformat *this, *next;
DEBUG_CLIPBOARD(("cliprdr_register_server_formats\n"));
in_uint32_le(s, remaining_length);
num_formats = remaining_length / 36;
if (NULL != server_formats) {
this = server_formats;
next = this->next;
while (NULL != next) {
xfree(this);
this = NULL;
this = next;
next = this->next;
}
}
this = xmalloc(sizeof(cliprdr_dataformat));
this->next = NULL;
server_formats = this;
num_server_formats = num_formats;
while (1 < num_formats) {
in_uint32_le(s, this->identifier);
in_uint8a(s, this->textual_description, 32);
DEBUG_CLIPBOARD(("Stored format description with numeric id %d\n",
this->identifier));
this-> next = xmalloc(sizeof(cliprdr_dataformat));
this = this->next;
num_formats--;
}
in_uint32_le(s, this->identifier);
DEBUG_CLIPBOARD(("Stored format description with numeric id %d\n", this->identifier));
in_uint8a(s, this->textual_description, 32);
this -> next = NULL;
in_uint32_le(s, pad);
cliprdr_print_server_formats();
}
static void
cliprdr_select_X_clipboards(void)
{
XSetSelectionOwner(display, primary_atom, wnd, last_gesturetime);
if (wnd != XGetSelectionOwner(display, primary_atom))
{
warning("Failed to aquire ownership of PRIMARY clipboard\n");
}
XSetSelectionOwner(display, clipboard_atom, wnd, last_gesturetime);
if (wnd != XGetSelectionOwner(display, clipboard_atom))
{
warning("Failed to aquire ownership of CLIPBOARD clipboard\n");
}
}
static void
cliprdr_handle_first_handshake(STREAM s)
{
uint32 remaining_length, pad;
in_uint32_le(s, remaining_length);
in_uint32_le(s, pad);
DEBUG_CLIPBOARD(("Remaining length in first handshake frm server is %d, pad is %d\n",
remaining_length, pad));
cliprdr_send_format_announce();
}
void cliprdr_handle_server_data(uint32 length, STREAM s)
{
uint32 remaining_length;
char *data;
in_uint32_le(s, remaining_length);
data = s->p;
XChangeProperty(display,
selection_event.requestor,
selection_event.property,
XInternAtom(display, "STRING", False),
8,
PropModeAppend,
data,
remaining_length);
XSendEvent(display,
selection_event.requestor,
False,
NoEventMask,
(XEvent *)&selection_event);
}
void cliprdr_handle_server_data_request(STREAM s)
{
Window selectionowner;
uint32 remaining_length;
uint32 wanted_formatcode, pad;
in_uint32_le(s, remaining_length);
in_uint32_le(s, wanted_formatcode);
in_uint32_le(s, pad);
/* FIXME: Check that we support this formatcode */
DEBUG_CLIPBOARD(("Request from server for format %d\n",
wanted_formatcode));
selectionowner = XGetSelectionOwner(display, primary_atom);
if (None != selectionowner)
{
/* FIXME: Perhaps we should check if we are the owner? */
XConvertSelection(display, primary_atom,
targets_atom,
rdesktop_clipboard_target_atom,
wnd, CurrentTime);
/* The rest of the transfer is handled in
cliprdr_handle_SelectionNotify */
} else
{
DEBUG_CLIPBOARD(("There were no owner for PRIMARY, sending empty string\n")); // FIXME: Should we always send an empty string?
cliprdr_send_empty_datapacket();
}
}
void cliprdr_callback(STREAM s)
{
static int failed_clipboard_acks = 0;
struct timeval timeval;
uint32 length, flags;
uint16 ptype0, ptype1;
DEBUG_CLIPBOARD(("cliprdr_callback called, clipboard data:\n"));
#ifdef WITH_DEBUG_CLIPBOARD
hexdump(s->p, s->end - s->p);
#endif
in_uint32_le(s, length);
in_uint32_le(s, flags);
DEBUG_CLIPBOARD(("length is %d, flags are %d\n", length, flags));
if (flags & 0x03 || flags & 0x01) /* Single-write op or first-packet-of-several op */
{
in_uint16_le(s, ptype0);
in_uint16_le(s, ptype1);
DEBUG_CLIPBOARD(("ptype0 is %d, ptype1 is %d\n", ptype0, ptype1));
if (1 == ptype0 && 0 == ptype1) {
cliprdr_handle_first_handshake(s);
return;
} else if (3 == ptype0 && 1 == ptype1)
{
// Acknowledgment on our format announce. Do we care? Not right now.
// There is a strange pad in this packet that we might need some time,
// but probably not.
DEBUG_CLIPBOARD(("Received format announce ACK\n"));
failed_clipboard_acks = 0;
return;
} else if (3 == ptype0 && 2 == ptype1)
{
DEBUG_CLIPBOARD(("Received failed clipboard format announce ACK, retrying\n"));
/* This is a fairly portable way to sleep 1/10 of
a second.. */
timeval.tv_sec = 0;
timeval.tv_usec = 100;
select(0, NULL, NULL, NULL, &timeval);
if (failed_clipboard_acks < 3)
{
cliprdr_send_format_announce();
/* Make sure we don't get stuck in this loop */
failed_clipboard_acks++;
}
else
{
warning("Reached maximum number of clipboard format announce attempts. Pasting in Windows probably won't work well now.\n");
}
} else if (2 == ptype0 && 0 == ptype1)
{
cliprdr_register_server_formats(s);
cliprdr_select_X_clipboards();
cliprdr_ack_format_list();
return;
} else if (5 == ptype0 && 1 == ptype1)
{
cliprdr_handle_server_data(length, s);
} else if (4 == ptype0 && 0 == ptype1)
{
cliprdr_handle_server_data_request(s);
}
}
}
void cliprdr_init(void)
{
primary_atom = XInternAtom(display, "PRIMARY", False);
clipboard_atom = XInternAtom(display, "CLIPBOARD", False);
targets_atom = XInternAtom(display, "TARGETS", False);
timestamp_atom = XInternAtom(display, "TIMESTAMP", False);
rdesktop_clipboard_target_atom = XInternAtom(display, "_RDESKTOP_CLIPBOARD_TARGET", False);
}