diff --git a/channels/cliprdr/cliprdr_format.c b/channels/cliprdr/cliprdr_format.c index 9902982a0..3f349fbaf 100644 --- a/channels/cliprdr/cliprdr_format.c +++ b/channels/cliprdr/cliprdr_format.c @@ -38,44 +38,52 @@ void cliprdr_process_format_list_event(cliprdrPlugin* cliprdr, RDP_CB_FORMAT_LIST_EVENT* cb_event) { - STREAM* data_out; + STREAM* s; int i; - data_out = cliprdr_packet_new(CB_FORMAT_LIST, 0, 36 * cb_event->num_formats); - - for (i = 0; i < cb_event->num_formats; i++) + if (cb_event->raw_format_data) { - stream_write_uint32(data_out, cb_event->formats[i]); - switch (cb_event->formats[i]) + s = cliprdr_packet_new(CB_FORMAT_LIST, 0, cb_event->raw_format_data_size); + stream_write(s, cb_event->raw_format_data, cb_event->raw_format_data_size); + } + else + { + s = cliprdr_packet_new(CB_FORMAT_LIST, 0, 36 * cb_event->num_formats); + + for (i = 0; i < cb_event->num_formats; i++) { - case CB_FORMAT_HTML: - memcpy(stream_get_tail(data_out), CFSTR_HTML, sizeof(CFSTR_HTML)); - break; - case CB_FORMAT_PNG: - memcpy(stream_get_tail(data_out), CFSTR_PNG, sizeof(CFSTR_PNG)); - break; - case CB_FORMAT_JPEG: - memcpy(stream_get_tail(data_out), CFSTR_JPEG, sizeof(CFSTR_JPEG)); - break; - case CB_FORMAT_GIF: - memcpy(stream_get_tail(data_out), CFSTR_GIF, sizeof(CFSTR_GIF)); - break; + stream_write_uint32(s, cb_event->formats[i]); + switch (cb_event->formats[i]) + { + case CB_FORMAT_HTML: + memcpy(stream_get_tail(s), CFSTR_HTML, sizeof(CFSTR_HTML)); + break; + case CB_FORMAT_PNG: + memcpy(stream_get_tail(s), CFSTR_PNG, sizeof(CFSTR_PNG)); + break; + case CB_FORMAT_JPEG: + memcpy(stream_get_tail(s), CFSTR_JPEG, sizeof(CFSTR_JPEG)); + break; + case CB_FORMAT_GIF: + memcpy(stream_get_tail(s), CFSTR_GIF, sizeof(CFSTR_GIF)); + break; + } + stream_seek(s, 32); } - stream_seek(data_out, 32); } - cliprdr_packet_send(cliprdr, data_out); + cliprdr_packet_send(cliprdr, s); } static void cliprdr_send_format_list_response(cliprdrPlugin* cliprdr) { - STREAM* data_out; + STREAM* s; - data_out = cliprdr_packet_new(CB_FORMAT_LIST_RESPONSE, CB_RESPONSE_OK, 0); - cliprdr_packet_send(cliprdr, data_out); + s = cliprdr_packet_new(CB_FORMAT_LIST_RESPONSE, CB_RESPONSE_OK, 0); + cliprdr_packet_send(cliprdr, s); } -void cliprdr_process_format_list(cliprdrPlugin* cliprdr, STREAM* data_in, uint32 dataLen) +void cliprdr_process_format_list(cliprdrPlugin* cliprdr, STREAM* s, uint32 dataLen) { RDP_CB_FORMAT_LIST_EVENT* cb_event; uint32 format; @@ -85,14 +93,23 @@ void cliprdr_process_format_list(cliprdrPlugin* cliprdr, STREAM* data_in, uint32 cb_event = (RDP_CB_FORMAT_LIST_EVENT*)freerdp_event_new(RDP_EVENT_CLASS_CLIPRDR, RDP_EVENT_TYPE_CB_FORMAT_LIST, NULL, NULL); + num_formats = dataLen / 36; cb_event->formats = (uint32*)xmalloc(sizeof(uint32) * num_formats); cb_event->num_formats = 0; + if (dataLen > 0) + { + cb_event->raw_format_data = (uint8*) xmalloc(dataLen); + memcpy(cb_event->raw_format_data, stream_get_tail(s), dataLen); + cb_event->raw_format_data_size = dataLen; + } + if (num_formats * 36 != dataLen) DEBUG_WARN("dataLen %d not devided by 36!", dataLen); + for (i = 0; i < num_formats; i++) { - stream_read_uint32(data_in, format); + stream_read_uint32(s, format); supported = 1; switch (format) { @@ -102,22 +119,22 @@ void cliprdr_process_format_list(cliprdrPlugin* cliprdr, STREAM* data_in, uint32 break; default: - if (memcmp(stream_get_tail(data_in), CFSTR_HTML, sizeof(CFSTR_HTML)) == 0) + if (memcmp(stream_get_tail(s), CFSTR_HTML, sizeof(CFSTR_HTML)) == 0) { format = CB_FORMAT_HTML; break; } - if (memcmp(stream_get_tail(data_in), CFSTR_PNG, sizeof(CFSTR_PNG)) == 0) + if (memcmp(stream_get_tail(s), CFSTR_PNG, sizeof(CFSTR_PNG)) == 0) { format = CB_FORMAT_PNG; break; } - if (memcmp(stream_get_tail(data_in), CFSTR_JPEG, sizeof(CFSTR_JPEG)) == 0) + if (memcmp(stream_get_tail(s), CFSTR_JPEG, sizeof(CFSTR_JPEG)) == 0) { format = CB_FORMAT_JPEG; break; } - if (memcmp(stream_get_tail(data_in), CFSTR_GIF, sizeof(CFSTR_GIF)) == 0) + if (memcmp(stream_get_tail(s), CFSTR_GIF, sizeof(CFSTR_GIF)) == 0) { format = CB_FORMAT_GIF; break; @@ -125,7 +142,7 @@ void cliprdr_process_format_list(cliprdrPlugin* cliprdr, STREAM* data_in, uint32 supported = 0; break; } - stream_seek(data_in, 32); + stream_seek(s, 32); if (supported) cb_event->formats[cb_event->num_formats++] = format; @@ -135,42 +152,65 @@ void cliprdr_process_format_list(cliprdrPlugin* cliprdr, STREAM* data_in, uint32 cliprdr_send_format_list_response(cliprdr); } -void cliprdr_process_format_data_request(cliprdrPlugin* cliprdr, STREAM* data_in) +void cliprdr_process_format_list_response(cliprdrPlugin* cliprdr, uint16 msgFlags) +{ + RDP_EVENT* event; + + if ((msgFlags & CB_RESPONSE_FAIL) != 0) + { + event = freerdp_event_new(RDP_EVENT_CLASS_CLIPRDR, RDP_EVENT_TYPE_CB_SYNC, NULL, NULL); + svc_plugin_send_event((rdpSvcPlugin*)cliprdr, event); + } +} + +void cliprdr_process_format_data_request(cliprdrPlugin* cliprdr, STREAM* s) { RDP_CB_DATA_REQUEST_EVENT* cb_event; cb_event = (RDP_CB_DATA_REQUEST_EVENT*)freerdp_event_new(RDP_EVENT_CLASS_CLIPRDR, RDP_EVENT_TYPE_CB_DATA_REQUEST, NULL, NULL); - stream_read_uint32(data_in, cb_event->format); + stream_read_uint32(s, cb_event->format); svc_plugin_send_event((rdpSvcPlugin*)cliprdr, (RDP_EVENT*)cb_event); } void cliprdr_process_format_data_response_event(cliprdrPlugin* cliprdr, RDP_CB_DATA_RESPONSE_EVENT* cb_event) { - STREAM* data_out; + STREAM* s; - data_out = cliprdr_packet_new(CB_FORMAT_DATA_RESPONSE, CB_RESPONSE_OK, cb_event->size); - stream_write(data_out, cb_event->data, cb_event->size); - cliprdr_packet_send(cliprdr, data_out); + if (cb_event->size > 0) + { + s = cliprdr_packet_new(CB_FORMAT_DATA_RESPONSE, CB_RESPONSE_OK, cb_event->size); + stream_write(s, cb_event->data, cb_event->size); + } + else + { + s = cliprdr_packet_new(CB_FORMAT_DATA_RESPONSE, CB_RESPONSE_FAIL, 0); + } + cliprdr_packet_send(cliprdr, s); } void cliprdr_process_format_data_request_event(cliprdrPlugin* cliprdr, RDP_CB_DATA_REQUEST_EVENT* cb_event) { - STREAM* data_out; + STREAM* s; - data_out = cliprdr_packet_new(CB_FORMAT_DATA_REQUEST, 0, 4); - stream_write_uint32(data_out, cb_event->format); - cliprdr_packet_send(cliprdr, data_out); + s = cliprdr_packet_new(CB_FORMAT_DATA_REQUEST, 0, 4); + stream_write_uint32(s, cb_event->format); + cliprdr_packet_send(cliprdr, s); } -void cliprdr_process_format_data_response(cliprdrPlugin* cliprdr, STREAM* data_in, uint32 dataLen) +void cliprdr_process_format_data_response(cliprdrPlugin* cliprdr, STREAM* s, uint32 dataLen) { RDP_CB_DATA_RESPONSE_EVENT* cb_event; cb_event = (RDP_CB_DATA_RESPONSE_EVENT*)freerdp_event_new(RDP_EVENT_CLASS_CLIPRDR, RDP_EVENT_TYPE_CB_DATA_RESPONSE, NULL, NULL); - cb_event->size = dataLen; - cb_event->data = (uint8*)xmalloc(dataLen); - memcpy(cb_event->data, stream_get_tail(data_in), dataLen); + + if (dataLen > 0) + { + cb_event->size = dataLen; + cb_event->data = (uint8*)xmalloc(dataLen); + memcpy(cb_event->data, stream_get_tail(s), dataLen); + } + svc_plugin_send_event((rdpSvcPlugin*)cliprdr, (RDP_EVENT*)cb_event); } diff --git a/channels/cliprdr/cliprdr_format.h b/channels/cliprdr/cliprdr_format.h index 7a7440706..280aa2bc6 100644 --- a/channels/cliprdr/cliprdr_format.h +++ b/channels/cliprdr/cliprdr_format.h @@ -23,6 +23,7 @@ void cliprdr_process_format_list_event(cliprdrPlugin* cliprdr, RDP_CB_FORMAT_LIST_EVENT* cb_event); void cliprdr_process_format_list(cliprdrPlugin* cliprdr, STREAM* data_in, uint32 dataLen); +void cliprdr_process_format_list_response(cliprdrPlugin* cliprdr, uint16 msgFlags); void cliprdr_process_format_data_request(cliprdrPlugin* cliprdr, STREAM* data_in); void cliprdr_process_format_data_response_event(cliprdrPlugin* cliprdr, RDP_CB_DATA_RESPONSE_EVENT* cb_event); diff --git a/channels/cliprdr/cliprdr_main.c b/channels/cliprdr/cliprdr_main.c index bf8744780..b9aa15efa 100644 --- a/channels/cliprdr/cliprdr_main.c +++ b/channels/cliprdr/cliprdr_main.c @@ -33,29 +33,29 @@ STREAM* cliprdr_packet_new(uint16 msgType, uint16 msgFlags, uint32 dataLen) { - STREAM* data_out; + STREAM* s; - data_out = stream_new(dataLen + 8); - stream_write_uint16(data_out, msgType); - stream_write_uint16(data_out, msgFlags); + s = stream_new(dataLen + 8); + stream_write_uint16(s, msgType); + stream_write_uint16(s, msgFlags); /* Write actual length after the entire packet has been constructed. */ - stream_seek(data_out, 4); + stream_seek(s, 4); - return data_out; + return s; } -void cliprdr_packet_send(cliprdrPlugin* cliprdr, STREAM* data_out) +void cliprdr_packet_send(cliprdrPlugin* cliprdr, STREAM* s) { int pos; uint32 dataLen; - pos = stream_get_pos(data_out); + pos = stream_get_pos(s); dataLen = pos - 8; - stream_set_pos(data_out, 4); - stream_write_uint32(data_out, dataLen); - stream_set_pos(data_out, pos); + stream_set_pos(s, 4); + stream_write_uint32(s, dataLen); + stream_set_pos(s, pos); - svc_plugin_send((rdpSvcPlugin*)cliprdr, data_out); + svc_plugin_send((rdpSvcPlugin*)cliprdr, s); } static void cliprdr_process_connect(rdpSvcPlugin* plugin) @@ -63,28 +63,28 @@ static void cliprdr_process_connect(rdpSvcPlugin* plugin) DEBUG_SVC("connecting"); } -static void cliprdr_process_clip_caps(cliprdrPlugin* cliprdr, STREAM* data_in) +static void cliprdr_process_clip_caps(cliprdrPlugin* cliprdr, STREAM* s) { uint16 cCapabilitiesSets; - stream_read_uint16(data_in, cCapabilitiesSets); + stream_read_uint16(s, cCapabilitiesSets); DEBUG_SVC("cCapabilitiesSets %d", cCapabilitiesSets); } static void cliprdr_send_clip_caps(cliprdrPlugin* cliprdr) { - STREAM* data_out; + STREAM* s; - data_out = cliprdr_packet_new(CB_CLIP_CAPS, 0, 4 + CB_CAPSTYPE_GENERAL_LEN); + s = cliprdr_packet_new(CB_CLIP_CAPS, 0, 4 + CB_CAPSTYPE_GENERAL_LEN); - stream_write_uint16(data_out, 1); /* cCapabilitiesSets */ - stream_write_uint16(data_out, 0); /* pad1 */ - stream_write_uint16(data_out, CB_CAPSTYPE_GENERAL); /* capabilitySetType */ - stream_write_uint16(data_out, CB_CAPSTYPE_GENERAL_LEN); /* lengthCapability */ - stream_write_uint32(data_out, CB_CAPS_VERSION_2); /* version */ - stream_write_uint32(data_out, 0); /* generalFlags */ + stream_write_uint16(s, 1); /* cCapabilitiesSets */ + stream_write_uint16(s, 0); /* pad1 */ + stream_write_uint16(s, CB_CAPSTYPE_GENERAL); /* capabilitySetType */ + stream_write_uint16(s, CB_CAPSTYPE_GENERAL_LEN); /* lengthCapability */ + stream_write_uint32(s, CB_CAPS_VERSION_2); /* version */ + stream_write_uint32(s, 0); /* generalFlags */ - cliprdr_packet_send(cliprdr, data_out); + cliprdr_packet_send(cliprdr, s); } static void cliprdr_process_monitor_ready(cliprdrPlugin* cliprdr) @@ -97,23 +97,23 @@ static void cliprdr_process_monitor_ready(cliprdrPlugin* cliprdr) svc_plugin_send_event((rdpSvcPlugin*)cliprdr, event); } -static void cliprdr_process_receive(rdpSvcPlugin* plugin, STREAM* data_in) +static void cliprdr_process_receive(rdpSvcPlugin* plugin, STREAM* s) { cliprdrPlugin* cliprdr = (cliprdrPlugin*)plugin; uint16 msgType; uint16 msgFlags; uint32 dataLen; - stream_read_uint16(data_in, msgType); - stream_read_uint16(data_in, msgFlags); - stream_read_uint32(data_in, dataLen); + stream_read_uint16(s, msgType); + stream_read_uint16(s, msgFlags); + stream_read_uint32(s, dataLen); DEBUG_SVC("msgType %d msgFlags %d dataLen %d", msgType, msgFlags, dataLen); switch (msgType) { case CB_CLIP_CAPS: - cliprdr_process_clip_caps(cliprdr, data_in); + cliprdr_process_clip_caps(cliprdr, s); break; case CB_MONITOR_READY: @@ -121,18 +121,19 @@ static void cliprdr_process_receive(rdpSvcPlugin* plugin, STREAM* data_in) break; case CB_FORMAT_LIST: - cliprdr_process_format_list(cliprdr, data_in, dataLen); + cliprdr_process_format_list(cliprdr, s, dataLen); break; case CB_FORMAT_LIST_RESPONSE: + cliprdr_process_format_list_response(cliprdr, msgFlags); break; case CB_FORMAT_DATA_REQUEST: - cliprdr_process_format_data_request(cliprdr, data_in); + cliprdr_process_format_data_request(cliprdr, s); break; case CB_FORMAT_DATA_RESPONSE: - cliprdr_process_format_data_response(cliprdr, data_in, dataLen); + cliprdr_process_format_data_response(cliprdr, s, dataLen); break; default: @@ -140,7 +141,7 @@ static void cliprdr_process_receive(rdpSvcPlugin* plugin, STREAM* data_in) break; } - stream_free(data_in); + stream_free(s); } static void cliprdr_process_event(rdpSvcPlugin* plugin, RDP_EVENT* event) diff --git a/client/X11/CMakeLists.txt b/client/X11/CMakeLists.txt index 835c089bf..7d273708e 100644 --- a/client/X11/CMakeLists.txt +++ b/client/X11/CMakeLists.txt @@ -27,6 +27,8 @@ add_executable(xfreerdp xf_rail.h xf_tsmf.c xf_tsmf.h + xf_cliprdr.c + xf_cliprdr.h xf_event.c xf_event.h xf_keyboard.c diff --git a/client/X11/xf_cliprdr.c b/client/X11/xf_cliprdr.c new file mode 100644 index 000000000..cb830519f --- /dev/null +++ b/client/X11/xf_cliprdr.c @@ -0,0 +1,1225 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * X11 Clipboard Redirection + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "xf_cliprdr.h" + +typedef struct clipboard_format_mapping clipboardFormatMapping; +struct clipboard_format_mapping +{ + Atom target_format; + uint32 format_id; +}; + +typedef struct clipboard_context clipboardContext; +struct clipboard_context +{ + rdpChanMan* chanman; + Window root_window; + Atom clipboard_atom; + Atom property_atom; + Atom identity_atom; + + clipboardFormatMapping format_mappings[20]; + int num_format_mappings; + + /* server->client data */ + uint32* formats; + int num_formats; + Atom targets[20]; + int num_targets; + uint8* data; + uint32 data_format; + uint32 data_alt_format; + int data_length; + XEvent* respond; + + /* client->server data */ + Window owner; + int request_index; + boolean sync; + + /* INCR mechanism */ + Atom incr_atom; + boolean incr_starts; + uint8* incr_data; + int incr_data_length; +}; + +void xf_cliprdr_init(xfInfo* xfi, rdpChanMan* chanman) +{ + int n; + uint32 id; + clipboardContext* cb; + + cb = xnew(clipboardContext); + xfi->clipboard_context = cb; + + cb->chanman = chanman; + cb->request_index = -1; + + cb->root_window = DefaultRootWindow(xfi->display); + cb->clipboard_atom = XInternAtom(xfi->display, "CLIPBOARD", False); + if (cb->clipboard_atom == None) + { + DEBUG_WARN("unable to get CLIPBOARD atom"); + } + + cb->property_atom = XInternAtom(xfi->display, "_FREERDP_CLIPRDR", False); + cb->identity_atom = XInternAtom(xfi->display, "_FREERDP_CLIPRDR_ID", False); + id = 1; + XChangeProperty(xfi->display, xfi->window->handle, cb->identity_atom, XA_INTEGER, + 32, PropModeReplace, (unsigned char *)&id, 1); + + XSelectInput(xfi->display, cb->root_window, PropertyChangeMask); + + n = 0; + cb->format_mappings[n].target_format = XInternAtom(xfi->display, "_FREERDP_RAW", False); + cb->format_mappings[n].format_id = CB_FORMAT_RAW; + + n++; + cb->format_mappings[n].target_format = XInternAtom(xfi->display, "UTF8_STRING", False); + cb->format_mappings[n].format_id = CB_FORMAT_UNICODETEXT; + + n++; + cb->format_mappings[n].target_format = XA_STRING; + cb->format_mappings[n].format_id = CB_FORMAT_TEXT; + + n++; + cb->format_mappings[n].target_format = XInternAtom(xfi->display, "image/png", False); + cb->format_mappings[n].format_id = CB_FORMAT_PNG; + + n++; + cb->format_mappings[n].target_format = XInternAtom(xfi->display, "image/jpeg", False); + cb->format_mappings[n].format_id = CB_FORMAT_JPEG; + + n++; + cb->format_mappings[n].target_format = XInternAtom(xfi->display, "image/gif", False); + cb->format_mappings[n].format_id = CB_FORMAT_GIF; + + n++; + cb->format_mappings[n].target_format = XInternAtom(xfi->display, "image/bmp", False); + cb->format_mappings[n].format_id = CB_FORMAT_DIB; + + n++; + cb->format_mappings[n].target_format = XInternAtom(xfi->display, "text/html", False); + cb->format_mappings[n].format_id = CB_FORMAT_HTML; + + cb->num_format_mappings = n + 1; + cb->targets[0] = XInternAtom(xfi->display, "TIMESTAMP", False); + cb->targets[1] = XInternAtom(xfi->display, "TARGETS", False); + cb->num_targets = 2; + + cb->incr_atom = XInternAtom(xfi->display, "INCR", False); +} + +void xf_cliprdr_uninit(xfInfo* xfi) +{ + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + if (cb) + { + xfree(cb->formats); + xfree(cb->data); + xfree(cb->respond); + xfree(cb->incr_data); + xfree(cb); + xfi->clipboard_context = NULL; + } +} + +static uint8* lf2crlf(uint8* data, int* size) +{ + uint8* outbuf; + uint8* out; + uint8* in_end; + uint8* in; + uint8 c; + int out_size; + + out_size = (*size) * 2 + 1; + outbuf = (uint8*) xzalloc(out_size); + out = outbuf; + in = data; + in_end = data + (*size); + while (in < in_end) + { + c = *in++; + if (c == '\n') + { + *out++ = '\r'; + *out++ = '\n'; + } + else + { + *out++ = c; + } + } + *out++ = 0; + *size = out - outbuf; + return outbuf; +} + +static void crlf2lf(uint8* data, int* size) +{ + uint8* out; + uint8* in; + uint8* in_end; + uint8 c; + + out = data; + in = data; + in_end = data + (*size); + while (in < in_end) + { + c = *in++; + if (c != '\r') + { + *out++ = c; + } + } + *size = out - data; +} + +static void be2le(uint8* data, int size) +{ + uint8 c; + + while (size >= 2) + { + c = data[0]; + data[0] = data[1]; + data[1] = c; + + data += 2; + size -= 2; + } +} + +static boolean xf_cliprdr_is_self_owned(xfInfo* xfi) +{ + Atom type; + uint32 id = 0; + uint32* pid = NULL; + int format, result = 0; + unsigned long len, bytes_left; + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + cb->owner = XGetSelectionOwner(xfi->display, cb->clipboard_atom); + if (cb->owner != None) + { + result = XGetWindowProperty(xfi->display, cb->owner, + cb->identity_atom, 0, 4, 0, XA_INTEGER, + &type, &format, &len, &bytes_left, (unsigned char**) &pid); + } + if (pid) + { + id = *pid; + XFree(pid); + } + if (cb->owner == None || cb->owner == xfi->window->handle) + { + return False; + } + if (result != Success) + { + return False; + } + return (id ? True : False); +} + +static int xf_cliprdr_select_format_by_id(clipboardContext* cb, uint32 format_id) +{ + int i; + + for (i = 0; i < cb->num_format_mappings; i++) + { + if (cb->format_mappings[i].format_id == format_id) + { + return i; + } + } + return -1; +} + +static int xf_cliprdr_select_format_by_atom(clipboardContext* cb, Atom target) +{ + int i; + int j; + + if (cb->formats == NULL) + return -1; + + for (i = 0; i < cb->num_format_mappings; i++) + { + if (cb->format_mappings[i].target_format != target) + continue; + + if (cb->format_mappings[i].format_id == CB_FORMAT_RAW) + return i; + + for (j = 0; j < cb->num_formats; j++) + { + if (cb->format_mappings[i].format_id == cb->formats[j]) + return i; + } + } + return -1; +} + +static void xf_cliprdr_send_raw_format_list(xfInfo* xfi) +{ + Atom type; + uint8* format_data; + int format, result; + unsigned long len, bytes_left; + RDP_CB_FORMAT_LIST_EVENT* event; + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + result = XGetWindowProperty(xfi->display, cb->root_window, + cb->property_atom, 0, 3600, 0, XA_STRING, + &type, &format, &len, &bytes_left, (unsigned char**) &format_data); + if (result != Success) + { + DEBUG_WARN("XGetWindowProperty failed"); + return; + } + DEBUG_X11("format=%d len=%d bytes_left=%d", format, (int)len, (int)bytes_left); + + event = (RDP_CB_FORMAT_LIST_EVENT*) freerdp_event_new(RDP_EVENT_CLASS_CLIPRDR, + RDP_EVENT_TYPE_CB_FORMAT_LIST, NULL, NULL); + + event->raw_format_data = (uint8*) xmalloc(len); + memcpy(event->raw_format_data, format_data, len); + event->raw_format_data_size = len; + XFree(format_data); + + freerdp_chanman_send_event(cb->chanman, (RDP_EVENT*) event); +} + +static void xf_cliprdr_send_null_format_list(xfInfo* xfi) +{ + RDP_CB_FORMAT_LIST_EVENT* event; + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + event = (RDP_CB_FORMAT_LIST_EVENT*) freerdp_event_new(RDP_EVENT_CLASS_CLIPRDR, + RDP_EVENT_TYPE_CB_FORMAT_LIST, NULL, NULL); + + event->num_formats = 0; + + freerdp_chanman_send_event(cb->chanman, (RDP_EVENT*) event); +} + +static void xf_cliprdr_send_supported_format_list(xfInfo* xfi) +{ + int i; + RDP_CB_FORMAT_LIST_EVENT* event; + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + DEBUG_X11(""); + + event = (RDP_CB_FORMAT_LIST_EVENT*) freerdp_event_new(RDP_EVENT_CLASS_CLIPRDR, + RDP_EVENT_TYPE_CB_FORMAT_LIST, NULL, NULL); + + event->formats = (uint32*) xmalloc(sizeof(uint32) * cb->num_format_mappings); + event->num_formats = cb->num_format_mappings; + for (i = 0; i < cb->num_format_mappings; i++) + { + event->formats[i] = cb->format_mappings[i].format_id; + } + + freerdp_chanman_send_event(cb->chanman, (RDP_EVENT*) event); +} + +static void xf_cliprdr_send_format_list(xfInfo* xfi) +{ + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + if (xf_cliprdr_is_self_owned(xfi)) + { + xf_cliprdr_send_raw_format_list(xfi); + } + else if (cb->owner == None) + { + xf_cliprdr_send_null_format_list(xfi); + } + else if (cb->owner != xfi->window->handle) + { + /* Request the owner for TARGETS, and wait for SelectionNotify event */ + XConvertSelection(xfi->display, cb->clipboard_atom, + cb->targets[1], cb->property_atom, + xfi->window->handle, CurrentTime); + } +} + +static void xf_cliprdr_send_data_request(xfInfo* xfi, uint32 format) +{ + RDP_CB_DATA_REQUEST_EVENT* event; + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + event = (RDP_CB_DATA_REQUEST_EVENT*) freerdp_event_new(RDP_EVENT_CLASS_CLIPRDR, + RDP_EVENT_TYPE_CB_DATA_REQUEST, NULL, NULL); + event->format = format; + + freerdp_chanman_send_event(cb->chanman, (RDP_EVENT*) event); +} + +static void xf_cliprdr_send_data_response(xfInfo* xfi, uint8* data, int size) +{ + RDP_CB_DATA_RESPONSE_EVENT* event; + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + event = (RDP_CB_DATA_RESPONSE_EVENT*) freerdp_event_new(RDP_EVENT_CLASS_CLIPRDR, + RDP_EVENT_TYPE_CB_DATA_RESPONSE, NULL, NULL); + event->data = data; + event->size = size; + + freerdp_chanman_send_event(cb->chanman, (RDP_EVENT*) event); +} + +static void xf_cliprdr_send_null_data_response(xfInfo* xfi) +{ + xf_cliprdr_send_data_response(xfi, NULL, 0); +} + +static void xf_cliprdr_process_cb_sync_event(xfInfo* xfi) +{ + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + xf_cliprdr_send_format_list(xfi); + cb->sync = True; +} + +static void xf_cliprdr_process_cb_data_request_event(xfInfo* xfi, RDP_CB_DATA_REQUEST_EVENT* event) +{ + int i; + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + DEBUG_X11("format %d", event->format); + + if (xf_cliprdr_is_self_owned(xfi)) + { + /* CB_FORMAT_RAW */ + i = 0; + XChangeProperty(xfi->display, xfi->window->handle, cb->property_atom, + XA_INTEGER, 32, PropModeReplace, + (unsigned char*) &event->format, 1); + } + else + { + i = xf_cliprdr_select_format_by_id(cb, event->format); + } + if (i < 0) + { + DEBUG_X11("unsupported format requested"); + xf_cliprdr_send_null_data_response(xfi); + } + else + { + cb->request_index = i; + + DEBUG_X11("target=%d", (int)cb->format_mappings[i].target_format); + + XConvertSelection(xfi->display, cb->clipboard_atom, + cb->format_mappings[i].target_format, cb->property_atom, + xfi->window->handle, CurrentTime); + XFlush(xfi->display); + /* After this point, we expect a SelectionNotify event from the clipboard owner. */ + } +} + +static void xf_cliprdr_get_requested_targets(xfInfo* xfi) +{ + int num; + int i, j; + Atom atom; + int format; + uint8* data = NULL; + unsigned long len, bytes_left; + RDP_CB_FORMAT_LIST_EVENT* event; + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + XGetWindowProperty(xfi->display, xfi->window->handle, cb->property_atom, + 0, 200, 0, XA_ATOM, + &atom, &format, &len, &bytes_left, &data); + + DEBUG_X11("type=%d format=%d len=%d bytes_left=%d", + (int)atom, format, (int)len, (int)bytes_left); + + if (len > 0) + { + event = (RDP_CB_FORMAT_LIST_EVENT*) freerdp_event_new(RDP_EVENT_CLASS_CLIPRDR, + RDP_EVENT_TYPE_CB_FORMAT_LIST, NULL, NULL); + + event->formats = (uint32*) xmalloc(sizeof(uint32) * cb->num_format_mappings); + num = 0; + for (i = 0; i < len; i++) + { + atom = ((Atom*)data)[i]; + DEBUG_X11("atom %d", (int)atom); + for (j = 0; j < cb->num_format_mappings; j++) + { + if (cb->format_mappings[j].target_format == atom) + { + DEBUG_X11("found format %d for atom %d", + cb->format_mappings[j].format_id, (int)atom); + event->formats[num++] = cb->format_mappings[j].format_id; + break; + } + } + } + event->num_formats = num; + XFree(data); + + freerdp_chanman_send_event(cb->chanman, (RDP_EVENT*) event); + } + else + { + if (data) + { + XFree(data); + } + xf_cliprdr_send_null_format_list(xfi); + } +} + +static uint8* xf_cliprdr_process_requested_raw(uint8* data, int* size) +{ + uint8* outbuf; + + outbuf = (uint8*) xmalloc(*size); + memcpy(outbuf, data, *size); + return outbuf; +} + +static uint8* xf_cliprdr_process_requested_unicodetext(uint8* data, int* size) +{ + uint8* inbuf; + uint8* outbuf; + size_t out_size; + UNICONV* uniconv; + + inbuf = lf2crlf(data, size); + + uniconv = freerdp_uniconv_new(); + outbuf = (uint8*) freerdp_uniconv_out(uniconv, (char*) inbuf, &out_size); + freerdp_uniconv_free(uniconv); + + xfree(inbuf); + + *size = (int)out_size + 2; + + return outbuf; +} + +static uint8* xf_cliprdr_process_requested_text(uint8* data, int* size) +{ + uint8* outbuf; + + outbuf = lf2crlf(data, size); + + return outbuf; +} + +static uint8* xf_cliprdr_process_requested_dib(uint8* data, int* size) +{ + uint8* outbuf; + + /* length should be at least BMP header (14) + sizeof(BITMAPINFOHEADER) */ + if (*size < 54) + { + DEBUG_X11("bmp length %d too short", *size); + return NULL; + } + *size -= 14; + outbuf = (uint8*) xzalloc(*size); + memcpy(outbuf, data + 14, *size); + + return outbuf; +} + +static uint8* xf_cliprdr_process_requested_html(uint8* data, int* size) +{ + uint8* inbuf; + uint8* in; + uint8* outbuf; + char num[11]; + UNICONV* uniconv; + + inbuf = NULL; + if (*size > 2) + { + if ((unsigned char)data[0] == 0xfe && (unsigned char)data[1] == 0xff) + { + be2le(data, *size); + } + + if ((unsigned char)data[0] == 0xff && (unsigned char)data[1] == 0xfe) + { + uniconv = freerdp_uniconv_new(); + inbuf = (uint8*) freerdp_uniconv_in(uniconv, data + 2, *size - 2); + freerdp_uniconv_free(uniconv); + } + } + if (inbuf == NULL) + { + inbuf = xzalloc(*size + 1); + memcpy(inbuf, data, *size); + } + + outbuf = (uint8*) xzalloc(*size + 200); + strcpy((char*) outbuf, + "Version:0.9\r\n" + "StartHTML:0000000000\r\n" + "EndHTML:0000000000\r\n" + "StartFragment:0000000000\r\n" + "EndFragment:0000000000\r\n"); + in = (uint8*) strstr((char*) inbuf, ""); + } + strcat((char*) outbuf, ""); + /* StartFragment */ + snprintf(num, sizeof(num), "%010lu", (unsigned long) strlen((char*) outbuf)); + memcpy(outbuf + 69, num, 10); + strcat((char*) outbuf, (char*) inbuf); + /* EndFragment */ + snprintf(num, sizeof(num), "%010lu", (unsigned long) strlen((char*) outbuf)); + memcpy(outbuf + 93, num, 10); + strcat((char*) outbuf, ""); + if (in == NULL) + { + strcat((char*) outbuf, ""); + } + /* EndHTML */ + snprintf(num, sizeof(num), "%010lu", (unsigned long) strlen((char*) outbuf)); + memcpy(outbuf + 43, num, 10); + + *size = strlen((char*) outbuf) + 1; + xfree(inbuf); + + return outbuf; +} + +static void xf_cliprdr_process_requested_data(xfInfo* xfi, boolean has_data, uint8* data, int size) +{ + uint8* outbuf; + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + if (cb->incr_starts && has_data) + return; + + if (!has_data || data == NULL) + { + xf_cliprdr_send_null_data_response(xfi); + return; + } + + switch (cb->format_mappings[cb->request_index].format_id) + { + case CB_FORMAT_RAW: + case CB_FORMAT_PNG: + case CB_FORMAT_JPEG: + case CB_FORMAT_GIF: + outbuf = xf_cliprdr_process_requested_raw(data, &size); + break; + + case CB_FORMAT_UNICODETEXT: + outbuf = xf_cliprdr_process_requested_unicodetext(data, &size); + break; + + case CB_FORMAT_TEXT: + outbuf = xf_cliprdr_process_requested_text(data, &size); + break; + + case CB_FORMAT_DIB: + outbuf = xf_cliprdr_process_requested_dib(data, &size); + break; + + case CB_FORMAT_HTML: + outbuf = xf_cliprdr_process_requested_html(data, &size); + break; + + default: + outbuf = NULL; + break; + } + + if (outbuf) + { + xf_cliprdr_send_data_response(xfi, outbuf, size); + } + else + { + xf_cliprdr_send_null_data_response(xfi); + } + + /* Resend the format list, otherwise the server won't request again for the next paste */ + xf_cliprdr_send_format_list(xfi); +} + +static boolean xf_cliprdr_get_requested_data(xfInfo* xfi, Atom target) +{ + Atom type; + int format; + uint8* data = NULL; + boolean has_data = False; + unsigned long len, bytes_left, dummy; + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + if (cb->request_index < 0 || + cb->format_mappings[cb->request_index].target_format != target) + { + DEBUG_X11("invalid target"); + xf_cliprdr_send_null_data_response(xfi); + return False; + } + + XGetWindowProperty(xfi->display, xfi->window->handle, + cb->property_atom, 0, 0, 0, target, + &type, &format, &len, &bytes_left, &data); + DEBUG_X11("type=%d format=%d bytes=%d request_index=%d", + (int)type, format, (int)bytes_left, cb->request_index); + if (data) + { + XFree(data); + data = NULL; + } + if (bytes_left <= 0 && !cb->incr_starts) + { + DEBUG_X11("no data"); + } + else if (type == cb->incr_atom) + { + DEBUG_X11("INCR started"); + cb->incr_starts = True; + if (cb->incr_data) + { + xfree(cb->incr_data); + cb->incr_data = NULL; + } + cb->incr_data_length = 0; + /* Data will be followed in PropertyNotify event */ + has_data = True; + } + else + { + if (bytes_left <= 0) + { + /* INCR finish */ + data = cb->incr_data; + cb->incr_data = NULL; + bytes_left = cb->incr_data_length; + cb->incr_data_length = 0; + cb->incr_starts = 0; + DEBUG_X11("INCR finished"); + has_data = True; + } + else if (XGetWindowProperty(xfi->display, xfi->window->handle, + cb->property_atom, 0, bytes_left, 0, target, + &type, &format, &len, &dummy, &data) == Success) + { + if (cb->incr_starts) + { + bytes_left = len * format / 8; + DEBUG_X11("%d bytes", (int)bytes_left); + cb->incr_data = (uint8*) xrealloc(cb->incr_data, cb->incr_data_length + bytes_left); + memcpy(cb->incr_data + cb->incr_data_length, data, bytes_left); + cb->incr_data_length += bytes_left; + XFree(data); + data = NULL; + } + has_data = True; + } + else + { + DEBUG_X11("XGetWindowProperty failed"); + } + } + XDeleteProperty(xfi->display, xfi->window->handle, cb->property_atom); + + xf_cliprdr_process_requested_data(xfi, has_data, data, (int)bytes_left); + + if (data) + XFree(data); + + return True; +} + +static void xf_cliprdr_append_target(clipboardContext* cb, Atom target) +{ + int i; + + if (cb->num_targets >= sizeof(cb->targets) / sizeof(Atom)) + return; + + for (i = 0; i < cb->num_targets; i++) + { + if (cb->targets[i] == target) + return; + } + cb->targets[cb->num_targets++] = target; +} + +static void xf_cliprdr_provide_targets(xfInfo* xfi, XEvent* respond) +{ + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + if (respond->xselection.property != None) + { + XChangeProperty(xfi->display, + respond->xselection.requestor, + respond->xselection.property, + XA_ATOM, 32, PropModeReplace, + (uint8*) cb->targets, cb->num_targets); + } +} + +static void xf_cliprdr_provide_data(xfInfo* xfi, XEvent* respond) +{ + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + if (respond->xselection.property != None) + { + XChangeProperty(xfi->display, + respond->xselection.requestor, + respond->xselection.property, + respond->xselection.target, 8, PropModeReplace, + (uint8*) cb->data, cb->data_length); + } +} + +static void xf_cliprdr_process_cb_format_list_event(xfInfo* xfi, RDP_CB_FORMAT_LIST_EVENT* event) +{ + int i, j; + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + if (cb->data) + { + xfree(cb->data); + cb->data = NULL; + } + if (cb->formats) + { + xfree(cb->formats); + } + + cb->formats = event->formats; + cb->num_formats = event->num_formats; + event->formats = NULL; + event->num_formats = 0; + + cb->num_targets = 2; + for (i = 0; i < cb->num_formats; i++) + { + for (j = 0; j < cb->num_format_mappings; j++) + { + if (cb->formats[i] == cb->format_mappings[j].format_id) + { + DEBUG_X11("announce format#%d : %d", i, cb->formats[i]); + xf_cliprdr_append_target(cb, cb->format_mappings[j].target_format); + } + } + } + XSetSelectionOwner(xfi->display, cb->clipboard_atom, xfi->window->handle, CurrentTime); + if (event->raw_format_data) + { + XChangeProperty(xfi->display, cb->root_window, cb->property_atom, + XA_STRING, 8, PropModeReplace, + event->raw_format_data, event->raw_format_data_size); + } + XFlush(xfi->display); +} + +static void xf_cliprdr_process_text(clipboardContext* cb, uint8* data, int size) +{ + cb->data = (uint8*) xmalloc(size); + memcpy(cb->data, data, size); + cb->data_length = size; + crlf2lf(cb->data, &cb->data_length); +} + +static void xf_cliprdr_process_unicodetext(clipboardContext* cb, uint8* data, int size) +{ + UNICONV* uniconv; + + uniconv = freerdp_uniconv_new(); + cb->data = (uint8*) freerdp_uniconv_in(uniconv, data, size); + freerdp_uniconv_free(uniconv); + cb->data_length = strlen((char*) cb->data); + crlf2lf(cb->data, &cb->data_length); +} + +static void xf_cliprdr_process_dib(clipboardContext* cb, uint8* data, int size) +{ + STREAM* s; + uint16 bpp; + uint32 offset; + uint32 ncolors; + + /* size should be at least sizeof(BITMAPINFOHEADER) */ + if (size < 40) + { + DEBUG_X11("dib size %d too short", size); + return; + } + + s = stream_new(0); + stream_attach(s, data, size); + stream_seek(s, 14); + stream_read_uint16(s, bpp); + stream_read_uint32(s, ncolors); + offset = 14 + 40 + (bpp <= 8 ? (ncolors == 0 ? (1 << bpp) : ncolors) * 4 : 0); + stream_detach(s); + stream_free(s); + + DEBUG_X11("offset=%d bpp=%d ncolors=%d", + offset, bpp, ncolors); + + s = stream_new(14 + size); + stream_write_uint8(s, 'B'); + stream_write_uint8(s, 'M'); + stream_write_uint32(s, 14 + size); + stream_write_uint32(s, 0); + stream_write_uint32(s, offset); + stream_write(s, data, size); + + cb->data = stream_get_head(s); + cb->data_length = stream_get_length(s); + stream_detach(s); + stream_free(s); +} + +static void xf_cliprdr_process_html(clipboardContext* cb, uint8* data, int size) +{ + char* start_str; + char* end_str; + int start; + int end; + + start_str = strstr((char*) data, "StartHTML:"); + end_str = strstr((char*) data, "EndHTML:"); + if (start_str == NULL || end_str == NULL) + { + DEBUG_X11("invalid HTML clipboard format"); + return; + } + start = atoi(start_str + 10); + end = atoi(end_str + 8); + if (start > size || end > size || start >= end) + { + DEBUG_X11("invalid HTML offset"); + return; + } + + cb->data = (uint8*) xmalloc(size - start + 1); + memcpy(cb->data, data + start, end - start); + cb->data_length = end - start; + crlf2lf(cb->data, &cb->data_length); +} + +static void xf_cliprdr_process_cb_data_response_event(xfInfo* xfi, RDP_CB_DATA_RESPONSE_EVENT* event) +{ + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + DEBUG_X11("size=%d", event->size); + + if (cb->respond == NULL) + { + DEBUG_X11("unexpected data"); + return; + } + + if (event->size == 0) + { + cb->respond->xselection.property = None; + } + else + { + if (cb->data) + { + xfree(cb->data); + cb->data = NULL; + } + switch (cb->data_format) + { + case CB_FORMAT_RAW: + case CB_FORMAT_PNG: + case CB_FORMAT_JPEG: + case CB_FORMAT_GIF: + cb->data = event->data; + cb->data_length = event->size; + event->data = NULL; + event->size = 0; + break; + + case CB_FORMAT_TEXT: + xf_cliprdr_process_text(cb, event->data, event->size); + break; + + case CB_FORMAT_UNICODETEXT: + xf_cliprdr_process_unicodetext(cb, event->data, event->size); + break; + + case CB_FORMAT_DIB: + xf_cliprdr_process_dib(cb, event->data, event->size); + break; + + case CB_FORMAT_HTML: + xf_cliprdr_process_html(cb, event->data, event->size); + break; + + default: + cb->respond->xselection.property = None; + break; + } + xf_cliprdr_provide_data(xfi, cb->respond); + } + + XSendEvent(xfi->display, cb->respond->xselection.requestor, + 0, 0, cb->respond); + XFlush(xfi->display); + xfree(cb->respond); + cb->respond = NULL; +} + +void xf_process_cliprdr_event(xfInfo* xfi, RDP_EVENT* event) +{ + switch (event->event_type) + { + case RDP_EVENT_TYPE_CB_SYNC: + xf_cliprdr_process_cb_sync_event(xfi); + break; + + case RDP_EVENT_TYPE_CB_FORMAT_LIST: + xf_cliprdr_process_cb_format_list_event(xfi, (RDP_CB_FORMAT_LIST_EVENT*) event); + break; + + case RDP_EVENT_TYPE_CB_DATA_REQUEST: + xf_cliprdr_process_cb_data_request_event(xfi, (RDP_CB_DATA_REQUEST_EVENT*) event); + break; + + case RDP_EVENT_TYPE_CB_DATA_RESPONSE: + xf_cliprdr_process_cb_data_response_event(xfi, (RDP_CB_DATA_RESPONSE_EVENT*) event); + break; + + default: + DEBUG_X11("unknown event type %d", event->event_type); + break; + } +} + +boolean xf_cliprdr_process_selection_notify(xfInfo* xfi, XEvent* xevent) +{ + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + if (xevent->xselection.target == cb->targets[1]) + { + if (xevent->xselection.property == None) + { + DEBUG_X11("owner not support TARGETS. sending all format."); + xf_cliprdr_send_supported_format_list(xfi); + } + else + { + xf_cliprdr_get_requested_targets(xfi); + } + + return True; + } + else + { + return xf_cliprdr_get_requested_data(xfi, xevent->xselection.target); + } +} + +boolean xf_cliprdr_process_selection_request(xfInfo* xfi, XEvent* xevent) +{ + int i; + int fmt; + Atom type; + uint32 format; + XEvent* respond; + uint32 alt_format; + uint8* data = NULL; + boolean delay_respond; + unsigned long len, bytes_left; + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + DEBUG_X11("target=%d", (int)xevent->xselectionrequest.target); + + if (xevent->xselectionrequest.owner != xfi->window->handle) + { + DEBUG_X11("not owner"); + return False; + } + + delay_respond = False; + respond = xnew(XEvent); + respond->xselection.property = None; + respond->xselection.type = SelectionNotify; + respond->xselection.display = xevent->xselectionrequest.display; + respond->xselection.requestor = xevent->xselectionrequest.requestor; + respond->xselection.selection = xevent->xselectionrequest.selection; + respond->xselection.target = xevent->xselectionrequest.target; + respond->xselection.time = xevent->xselectionrequest.time; + if (xevent->xselectionrequest.target == cb->targets[0]) /* TIMESTAMP */ + { + /* TODO */ + } + else if (xevent->xselectionrequest.target == cb->targets[1]) /* TARGETS */ + { + /* Someone else requests our available formats */ + respond->xselection.property = xevent->xselectionrequest.property; + xf_cliprdr_provide_targets(xfi, respond); + } + else + { + i = xf_cliprdr_select_format_by_atom(cb, xevent->xselectionrequest.target); + if (i >= 0 && xevent->xselectionrequest.requestor != xfi->window->handle) + { + format = cb->format_mappings[i].format_id; + alt_format = format; + if (format == CB_FORMAT_RAW) + { + if (XGetWindowProperty(xfi->display, xevent->xselectionrequest.requestor, + cb->property_atom, 0, 4, 0, XA_INTEGER, + &type, &fmt, &len, &bytes_left, &data) != Success) + { + DEBUG_X11("XGetWindowProperty failed"); + } + if (data) + { + memcpy(&alt_format, data, 4); + XFree(data); + } + } + DEBUG_X11("provide format 0x%04x alt_format 0x%04x", format, alt_format); + if (cb->data != 0 && + format == cb->data_format && + alt_format == cb->data_alt_format) + { + /* Cached clipboard data available. Send it now */ + respond->xselection.property = xevent->xselectionrequest.property; + xf_cliprdr_provide_data(xfi, respond); + } + else if (cb->respond) + { + DEBUG_X11("duplicated request"); + } + else + { + /** + * Send clipboard data request to the server. + * Response will be postponed after receiving the data + */ + if (cb->data) + { + xfree(cb->data); + cb->data = NULL; + } + respond->xselection.property = xevent->xselectionrequest.property; + cb->respond = respond; + cb->data_format = format; + cb->data_alt_format = alt_format; + delay_respond = True; + + xf_cliprdr_send_data_request(xfi, alt_format); + } + } + } + + if (!delay_respond) + { + XSendEvent(xfi->display, xevent->xselectionrequest.requestor, 0, 0, respond); + XFlush(xfi->display); + xfree(respond); + } + + return True; +} + +boolean xf_cliprdr_process_selection_clear(xfInfo* xfi, XEvent* xevent) +{ + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + if (xf_cliprdr_is_self_owned(xfi)) + return False; + + XDeleteProperty(xfi->display, cb->root_window, cb->property_atom); + + return True; +} + +boolean xf_cliprdr_process_property_notify(xfInfo* xfi, XEvent* xevent) +{ + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + if (xevent->xproperty.atom != cb->property_atom) + return False; /* Not cliprdr-related */ + + if (xevent->xproperty.window == cb->root_window) + { + DEBUG_X11("root window PropertyNotify"); + xf_cliprdr_send_format_list(xfi); + } + else if (xevent->xproperty.window == xfi->window->handle && + xevent->xproperty.state == PropertyNewValue && + cb->incr_starts && + cb->request_index >= 0) + { + DEBUG_X11("cliprdr window PropertyNotify"); + xf_cliprdr_get_requested_data(xfi, + cb->format_mappings[cb->request_index].target_format); + } + + return True; +} + +void xf_cliprdr_check_owner(xfInfo* xfi) +{ + Window owner; + clipboardContext* cb = (clipboardContext*) xfi->clipboard_context; + + if (cb->sync) + { + owner = XGetSelectionOwner(xfi->display, cb->clipboard_atom); + if (cb->owner != owner) + { + cb->owner = owner; + xf_cliprdr_send_format_list(xfi); + } + } +} + diff --git a/client/X11/xf_cliprdr.h b/client/X11/xf_cliprdr.h new file mode 100644 index 000000000..1b6b5ae32 --- /dev/null +++ b/client/X11/xf_cliprdr.h @@ -0,0 +1,34 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * X11 Clipboard Redirection + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __XF_CLIPRDR_H +#define __XF_CLIPRDR_H + +#include "xfreerdp.h" + +void xf_cliprdr_init(xfInfo* xfi, rdpChanMan* chanman); +void xf_cliprdr_uninit(xfInfo* xfi); +void xf_process_cliprdr_event(xfInfo* xfi, RDP_EVENT* event); +boolean xf_cliprdr_process_selection_notify(xfInfo* xfi, XEvent* xevent); +boolean xf_cliprdr_process_selection_request(xfInfo* xfi, XEvent* xevent); +boolean xf_cliprdr_process_selection_clear(xfInfo* xfi, XEvent* xevent); +boolean xf_cliprdr_process_property_notify(xfInfo* xfi, XEvent* xevent); +void xf_cliprdr_check_owner(xfInfo* xfi); + +#endif /* __XF_CLIPRDR_H */ diff --git a/client/X11/xf_event.c b/client/X11/xf_event.c index 8cfc00882..4d3eececb 100644 --- a/client/X11/xf_event.c +++ b/client/X11/xf_event.c @@ -21,6 +21,7 @@ #include #include "xf_rail.h" +#include "xf_cliprdr.h" #include "xf_event.h" @@ -347,6 +348,8 @@ boolean xf_event_FocusIn(xfInfo* xfi, XEvent* event, boolean app) //xf_rail_send_activate(xfi, event->xany.window, True); xf_kbd_focus_in(xfi); + xf_cliprdr_check_owner(xfi); + return True; } @@ -499,6 +502,38 @@ boolean xf_event_MapNotify(xfInfo* xfi, XEvent* event, boolean app) return True; } +boolean xf_event_SelectionNotify(xfInfo* xfi, XEvent* event, boolean app) +{ + if (xf_cliprdr_process_selection_notify(xfi, event)) + return True; + + return True; +} + +boolean xf_event_SelectionRequest(xfInfo* xfi, XEvent* event, boolean app) +{ + if (xf_cliprdr_process_selection_request(xfi, event)) + return True; + + return True; +} + +boolean xf_event_SelectionClear(xfInfo* xfi, XEvent* event, boolean app) +{ + if (xf_cliprdr_process_selection_clear(xfi, event)) + return True; + + return True; +} + +boolean xf_event_PropertyNotify(xfInfo* xfi, XEvent* event, boolean app) +{ + if (xf_cliprdr_process_property_notify(xfi, event)) + return True; + + return True; +} + boolean xf_event_process(freerdp* instance, XEvent* event) { boolean app = False; @@ -590,6 +625,22 @@ boolean xf_event_process(freerdp* instance, XEvent* event) status = xf_event_ClientMessage(xfi, event, app); break; + case SelectionNotify: + status = xf_event_SelectionNotify(xfi, event, app); + break; + + case SelectionRequest: + status = xf_event_SelectionRequest(xfi, event, app); + break; + + case SelectionClear: + status = xf_event_SelectionClear(xfi, event, app); + break; + + case PropertyNotify: + status = xf_event_PropertyNotify(xfi, event, app); + break; + default: DEBUG_X11("xf_event_process unknown event %d", event->type); break; diff --git a/client/X11/xf_window.c b/client/X11/xf_window.c index 20dfcb274..48fea3d99 100644 --- a/client/X11/xf_window.c +++ b/client/X11/xf_window.c @@ -226,7 +226,8 @@ xfWindow* xf_CreateDesktopWindow(xfInfo* xfi, char* name, int width, int height) input_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | VisibilityChangeMask | FocusChangeMask | StructureNotifyMask | - PointerMotionMask | ExposureMask | EnterWindowMask | LeaveWindowMask; + PointerMotionMask | ExposureMask | EnterWindowMask | LeaveWindowMask | + PropertyChangeMask; XSelectInput(xfi->display, window->handle, input_mask); XMapWindow(xfi->display, window->handle); diff --git a/client/X11/xfreerdp.c b/client/X11/xfreerdp.c index 8fbad33d9..45a2ac847 100644 --- a/client/X11/xfreerdp.c +++ b/client/X11/xfreerdp.c @@ -48,6 +48,7 @@ #include "xf_rail.h" #include "xf_tsmf.h" #include "xf_event.h" +#include "xf_cliprdr.h" #include "xf_monitor.h" #include "xf_keyboard.h" @@ -466,6 +467,7 @@ boolean xf_post_connect(freerdp* instance) freerdp_chanman_post_connect(GET_CHANMAN(instance), instance); xf_tsmf_init(xfi, xv_port); + xf_cliprdr_init(xfi, GET_CHANMAN(instance)); return True; } @@ -523,32 +525,6 @@ int xf_receive_channel_data(freerdp* instance, int channelId, uint8* data, int s return freerdp_chanman_data(instance, channelId, data, size, flags, total_size); } -void xf_process_cb_sync_event(xfInfo* xfi, rdpChanMan* chanman) -{ - RDP_EVENT* event; - RDP_CB_FORMAT_LIST_EVENT* format_list_event; - - event = freerdp_event_new(RDP_EVENT_CLASS_CLIPRDR, RDP_EVENT_TYPE_CB_FORMAT_LIST, NULL, NULL); - - format_list_event = (RDP_CB_FORMAT_LIST_EVENT*) event; - format_list_event->num_formats = 0; - - freerdp_chanman_send_event(chanman, event); -} - -void xf_process_cliprdr_event(xfInfo* xfi, rdpChanMan* chanman, RDP_EVENT* event) -{ - switch (event->event_type) - { - case RDP_EVENT_TYPE_CB_SYNC: - xf_process_cb_sync_event(xfi, chanman); - break; - - default: - break; - } -} - void xf_process_channel_event(rdpChanMan* chanman, freerdp* instance) { xfInfo* xfi; @@ -571,7 +547,7 @@ void xf_process_channel_event(rdpChanMan* chanman, freerdp* instance) break; case RDP_EVENT_CLASS_CLIPRDR: - xf_process_cliprdr_event(xfi, chanman, event); + xf_process_cliprdr_event(xfi, event); break; default: @@ -616,6 +592,7 @@ void xf_window_free(xfInfo* xfi) rail_free(xfi->rail); xf_tsmf_uninit(xfi); + xf_cliprdr_uninit(xfi); } void xf_free(xfInfo* xfi) diff --git a/client/X11/xfreerdp.h b/client/X11/xfreerdp.h index 5740f455b..61b03d80d 100644 --- a/client/X11/xfreerdp.h +++ b/client/X11/xfreerdp.h @@ -94,6 +94,7 @@ struct xf_info uint8* bmp_codec_none; void* rfx_context; void* xv_context; + void* clipboard_context; Atom _NET_WM_ICON; Atom _MOTIF_WM_HINTS; diff --git a/include/freerdp/plugins/cliprdr.h b/include/freerdp/plugins/cliprdr.h index aaa8a09c6..eece53ef1 100644 --- a/include/freerdp/plugins/cliprdr.h +++ b/include/freerdp/plugins/cliprdr.h @@ -56,6 +56,8 @@ struct _RDP_CB_FORMAT_LIST_EVENT RDP_EVENT event; uint32* formats; uint16 num_formats; + uint8* raw_format_data; + uint32 raw_format_data_size; }; typedef struct _RDP_CB_FORMAT_LIST_EVENT RDP_CB_FORMAT_LIST_EVENT; diff --git a/libfreerdp-utils/event.c b/libfreerdp-utils/event.c index 1555aba68..a56149ed0 100644 --- a/libfreerdp-utils/event.c +++ b/libfreerdp-utils/event.c @@ -113,6 +113,7 @@ static void freerdp_cliprdr_event_free(RDP_EVENT* event) { RDP_CB_FORMAT_LIST_EVENT* cb_event = (RDP_CB_FORMAT_LIST_EVENT*)event; xfree(cb_event->formats); + xfree(cb_event->raw_format_data); } break; case RDP_EVENT_TYPE_CB_DATA_RESPONSE: