FreeRDP/client/Windows/wf_cliprdr.c

711 lines
17 KiB
C

/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Windows Clipboard Redirection
*
* Copyright 2012 Jason Champion
*
* 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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <assert.h>
#include <winpr/crt.h>
#include <freerdp/utils/event.h>
#include <winpr/stream.h>
#include <freerdp/client/cliprdr.h>
#include "wf_cliprdr.h"
/* this macro will update _p pointer */
#define Read_UINT32(_p, _v) do { _v = \
(UINT32)(*_p) + \
(((UINT32)(*(_p + 1))) << 8) + \
(((UINT32)(*(_p + 2))) << 16) + \
(((UINT32)(*(_p + 3))) << 24); \
_p += 4; } while (0)
/* this macro will NOT update _p pointer */
#define Write_UINT32(_p, _v) do { \
*(_p) = (_v) & 0xFF; \
*(_p + 1) = ((_v) >> 8) & 0xFF; \
*(_p + 2) = ((_v) >> 16) & 0xFF; \
*(_p + 3) = ((_v) >> 24) & 0xFF; } while (0)
static UINT32 get_local_format_id_by_name(cliprdrContext *cliprdr, void *format_name)
{
formatMapping *map;
int i;
for (i = 0; i < cliprdr->map_size; i++)
{
map = &cliprdr->format_mappings[i];
if ((cliprdr->capabilities & CB_USE_LONG_FORMAT_NAMES) != 0)
{
if (map->name)
{
if (memcmp(map->name, format_name, wcslen((LPCWSTR)format_name)) == 0)
return map->local_format_id;
}
}
}
return 0;
}
static UINT32 get_remote_format_id(cliprdrContext *cliprdr, UINT32 local_format)
{
formatMapping *map;
int i;
for (i = 0; i < cliprdr->map_size; i++)
{
map = &cliprdr->format_mappings[i];
if (map->local_format_id == local_format)
{
return map->remote_format_id;
}
}
return 0;
}
static void map_ensure_capacity(cliprdrContext *cliprdr)
{
if (cliprdr->map_size >= cliprdr->map_capacity)
{
cliprdr->format_mappings = (formatMapping *)realloc(cliprdr->format_mappings,
sizeof(formatMapping) * cliprdr->map_capacity * 2);
cliprdr->map_capacity *= 2;
}
}
static void clear_format_map(cliprdrContext *cliprdr)
{
formatMapping *map;
int i;
if (cliprdr->format_mappings)
{
for (i = 0; i < cliprdr->map_capacity; i++)
{
map = &cliprdr->format_mappings[i];
map->remote_format_id = 0;
map->local_format_id = 0;
if (map->name)
{
free(map->name);
map->name = NULL;
}
}
}
cliprdr->map_size= 0;
}
static void cliprdr_send_format_list(cliprdrContext *cliprdr)
{
RDP_CB_FORMAT_LIST_EVENT *cliprdr_event;
BYTE *format_data;
int format = 0;
int data_size;
int format_count;
int len = 0;
int namelen;
if (!OpenClipboard(cliprdr->hwndClipboard))
{
DEBUG_CLIPRDR("OpenClipboard failed with 0x%x", GetLastError());
return;
}
format_count = CountClipboardFormats();
data_size = format_count * (4 + MAX_PATH * 2);
format_data = (BYTE *)calloc(1, data_size);
assert(format_data != NULL);
while (format = EnumClipboardFormats(format))
{
Write_UINT32(format_data + len, format);
len += 4;
if ((cliprdr->capabilities & CB_USE_LONG_FORMAT_NAMES) != 0)
{
if (format >= CF_MAX)
{
namelen = GetClipboardFormatNameW(format, (LPWSTR)(format_data + len), MAX_PATH);
len += namelen * sizeof(WCHAR);
}
len += 2; /* end of Unicode string */
}
else
{
if (format >= CF_MAX)
{
static wchar_t wName[MAX_PATH] = {0};
int wLen;
ZeroMemory(wName, MAX_PATH*2);
wLen = GetClipboardFormatNameW(format, wName, MAX_PATH);
if (wLen < 16)
{
memcpy(format_data + len, wName, wLen * sizeof(WCHAR));
}
else
{
memcpy(format_data + len, wName, 32); /* truncate the long name to 32 bytes */
}
}
len += 32;
}
}
CloseClipboard();
cliprdr_event = (RDP_CB_FORMAT_LIST_EVENT *) freerdp_event_new(CliprdrChannel_Class,
CliprdrChannel_FormatList, NULL, NULL);
cliprdr_event->raw_format_data = (BYTE *)calloc(1, len);
assert(cliprdr_event->raw_format_data != NULL);
CopyMemory(cliprdr_event->raw_format_data, format_data, len);
cliprdr_event->raw_format_data_size = len;
free(format_data);
freerdp_channels_send_event(cliprdr->channels, (wMessage *) cliprdr_event);
}
int cliprdr_send_data_request(cliprdrContext *cliprdr, UINT32 format)
{
RDP_CB_DATA_REQUEST_EVENT *cliprdr_event;
int ret;
cliprdr_event = (RDP_CB_DATA_REQUEST_EVENT *)freerdp_event_new(CliprdrChannel_Class,
CliprdrChannel_DataRequest, NULL, NULL);
if (!cliprdr_event)
return -1;
cliprdr_event->format = get_remote_format_id(cliprdr, format);
if (cliprdr_event->format == 0)
return -1;
ret = freerdp_channels_send_event(cliprdr->channels, (wMessage *)cliprdr_event);
if (ret != 0)
return -1;
WaitForSingleObject(cliprdr->response_data_event, INFINITE);
ResetEvent(cliprdr->response_data_event);
return 0;
}
static LRESULT CALLBACK cliprdr_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
static cliprdrContext *cliprdr = NULL;
switch (Msg)
{
case WM_CREATE:
cliprdr = (cliprdrContext *)((CREATESTRUCT *)lParam)->lpCreateParams;
cliprdr->hwndNextViewer = SetClipboardViewer(hWnd);
if (cliprdr->hwndNextViewer == NULL && GetLastError() != 0)
{
DEBUG_CLIPRDR("error: SetClipboardViewer failed with 0x%0x.", GetLastError());
}
cliprdr->hwndClipboard = hWnd;
break;
case WM_CLOSE:
ChangeClipboardChain(hWnd, cliprdr->hwndNextViewer);
break;
case WM_CHANGECBCHAIN:
if (cliprdr->hwndNextViewer == (HWND)wParam)
{
cliprdr->hwndNextViewer = (HWND)lParam;
}
else if (cliprdr->hwndNextViewer != NULL)
{
SendMessage(cliprdr->hwndNextViewer, Msg, wParam, lParam);
}
break;
case WM_DRAWCLIPBOARD:
if (cliprdr->channel_initialized)
{
if (GetClipboardOwner() != cliprdr->hwndClipboard)
{
if (!cliprdr->hmem)
{
cliprdr->hmem = GlobalFree(cliprdr->hmem);
}
cliprdr_send_format_list(cliprdr);
}
}
if (cliprdr->hwndNextViewer != NULL && cliprdr->hwndNextViewer != hWnd)
SendMessage(cliprdr->hwndNextViewer, Msg, wParam, lParam);
break;
case WM_RENDERALLFORMATS:
/* discard all contexts in clipboard */
if (!OpenClipboard(cliprdr->hwndClipboard))
{
DEBUG_CLIPRDR("OpenClipboard failed with 0x%x", GetLastError());
break;
}
EmptyClipboard();
CloseClipboard();
break;
case WM_RENDERFORMAT:
if (cliprdr_send_data_request(cliprdr, (UINT32)wParam) != 0)
{
DEBUG_CLIPRDR("error: cliprdr_send_data_request failed.");
break;
}
if (SetClipboardData(wParam, cliprdr->hmem) == NULL)
{
DEBUG_CLIPRDR("SetClipboardData failed with 0x%x", GetLastError());
cliprdr->hmem = GlobalFree(cliprdr->hmem);
}
/* Note: GlobalFree() is not needed when success */
break;
case WM_CLIPBOARDUPDATE:
case WM_DESTROYCLIPBOARD:
case WM_ASKCBFORMATNAME:
case WM_HSCROLLCLIPBOARD:
case WM_PAINTCLIPBOARD:
case WM_SIZECLIPBOARD:
case WM_VSCROLLCLIPBOARD:
default:
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
return 0;
}
static int create_cliprdr_window(cliprdrContext *cliprdr)
{
WNDCLASSEX wnd_cls;
ZeroMemory(&wnd_cls, sizeof(WNDCLASSEX));
wnd_cls.cbSize = sizeof(WNDCLASSEX);
wnd_cls.style = CS_OWNDC;
wnd_cls.lpfnWndProc = cliprdr_proc;
wnd_cls.cbClsExtra = 0;
wnd_cls.cbWndExtra = 0;
wnd_cls.hIcon = NULL;
wnd_cls.hCursor = NULL;
wnd_cls.hbrBackground = NULL;
wnd_cls.lpszMenuName = NULL;
wnd_cls.lpszClassName = L"ClipboardHiddenMessageProcessor";
wnd_cls.hInstance = GetModuleHandle(NULL);
wnd_cls.hIconSm = NULL;
RegisterClassEx(&wnd_cls);
cliprdr->hwndClipboard = CreateWindowEx(WS_EX_LEFT,
L"ClipboardHiddenMessageProcessor",
L"rdpclip",
0, 0, 0, 0, 0, HWND_MESSAGE, NULL, GetModuleHandle(NULL), cliprdr);
if (cliprdr->hwndClipboard == NULL)
{
DEBUG_CLIPRDR("error: CreateWindowEx failed with %x.", GetLastError());
return -1;
}
return 0;
}
static void *cliprdr_thread_func(void *arg)
{
cliprdrContext *cliprdr = (cliprdrContext *)arg;
BOOL mcode;
MSG msg;
int ret;
HRESULT result;
if ((ret = create_cliprdr_window(cliprdr)) != 0)
{
DEBUG_CLIPRDR("error: create clipboard window failed.");
return NULL;
}
while ((mcode = GetMessage(&msg, 0, 0, 0) != 0))
{
if (mcode == -1)
{
DEBUG_CLIPRDR("error: clipboard thread GetMessage failed.");
break;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return NULL;
}
void wf_cliprdr_init(wfContext* wfc, rdpChannels* channels)
{
cliprdrContext *cliprdr;
wfc->cliprdr_context = (cliprdrContext *) calloc(1, sizeof(cliprdrContext));
cliprdr = (cliprdrContext *) wfc->cliprdr_context;
assert(cliprdr != NULL);
cliprdr->channels = channels;
cliprdr->channel_initialized = FALSE;
cliprdr->map_capacity = 32;
cliprdr->map_size = 0;
cliprdr->format_mappings = (formatMapping *)calloc(1, sizeof(formatMapping) * cliprdr->map_capacity);
assert(cliprdr->format_mappings != NULL);
cliprdr->response_data_event = CreateEvent(NULL, TRUE, FALSE, L"response_data_event");
assert(cliprdr->response_data_event != NULL);
cliprdr->cliprdr_thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)cliprdr_thread_func, cliprdr, 0, NULL);
assert(cliprdr->cliprdr_thread != NULL);
return;
}
void wf_cliprdr_uninit(wfContext* wfc)
{
cliprdrContext *cliprdr = (cliprdrContext *) wfc->cliprdr_context;
if (!cliprdr)
return;
if (cliprdr->hwndClipboard)
PostMessage(cliprdr->hwndClipboard, WM_QUIT, 0, 0);
if (cliprdr->cliprdr_thread)
{
WaitForSingleObject(cliprdr->cliprdr_thread, INFINITE);
CloseHandle(cliprdr->cliprdr_thread);
}
if (cliprdr->response_data_event)
CloseHandle(cliprdr->response_data_event);
clear_format_map(cliprdr);
if (cliprdr->format_mappings)
free(cliprdr->format_mappings);
free(cliprdr);
}
static void wf_cliprdr_process_cb_clip_caps_event(wfContext *wfc, RDP_CB_CLIP_CAPS *caps_event)
{
cliprdrContext *cliprdr = (cliprdrContext *)wfc->cliprdr_context;
cliprdr->capabilities = caps_event->capabilities;
}
static void wf_cliprdr_process_cb_monitor_ready_event(wfContext *wfc, RDP_CB_MONITOR_READY_EVENT *ready_event)
{
cliprdrContext *cliprdr = (cliprdrContext *)wfc->cliprdr_context;
cliprdr->channel_initialized = TRUE;
cliprdr_send_format_list(wfc->cliprdr_context);
}
static void wf_cliprdr_process_cb_data_request_event(wfContext *wfc, RDP_CB_DATA_REQUEST_EVENT *event)
{
cliprdrContext *cliprdr = (cliprdrContext *)wfc->cliprdr_context;
RDP_CB_DATA_RESPONSE_EVENT *responce_event;
HANDLE hClipdata;
int size = 0;
char *buff = NULL;
char *globlemem = NULL;
UINT32 local_format;
local_format = event->format;
if (local_format == 0x9) /* FORMAT_ID_PALETTE */
{
/* TODO: implement this */
DEBUG_CLIPRDR("FORMAT_ID_PALETTE is not supported yet.");
}
else if (local_format == 0x3) /* FORMAT_ID_MATEFILE */
{
/* TODO: implement this */
DEBUG_CLIPRDR("FORMAT_ID_MATEFILE is not supported yet.");
}
else if (local_format == RegisterClipboardFormatW(L"FileGroupDescriptorW"))
{
/* TODO: implement this */
DEBUG_CLIPRDR("FileGroupDescriptorW is not supported yet.");
}
else
{
if (!OpenClipboard(cliprdr->hwndClipboard))
{
DEBUG_CLIPRDR("OpenClipboard failed with 0x%x", GetLastError());
return;
}
hClipdata = GetClipboardData(event->format);
if (!hClipdata)
{
DEBUG_CLIPRDR("GetClipboardData failed.");
CloseClipboard();
return;
}
globlemem = (char *)GlobalLock(hClipdata);
size = GlobalSize(hClipdata);
buff = (char *)malloc(size);
memcpy(buff, globlemem, size);
GlobalUnlock(hClipdata);
CloseClipboard();
}
responce_event = (RDP_CB_DATA_RESPONSE_EVENT *)freerdp_event_new(CliprdrChannel_Class,
CliprdrChannel_DataResponse, NULL, NULL);
responce_event->data = (BYTE *)buff;
responce_event->size = size;
freerdp_channels_send_event(cliprdr->channels, (wMessage *) responce_event);
/* Note: don't free buff here. */
}
static void wf_cliprdr_process_cb_format_list_event(wfContext *wfc, RDP_CB_FORMAT_LIST_EVENT *event)
{
cliprdrContext *cliprdr = (cliprdrContext *)wfc->cliprdr_context;
UINT32 left_size = event->raw_format_data_size;
int i = 0;
BYTE *p;
BYTE* end_mark;
BOOL format_forbidden = FALSE;
/* ignore the formats member in event struct, only parsing raw_format_data */
p = event->raw_format_data;
end_mark = p + event->raw_format_data_size;
clear_format_map(cliprdr);
if ((cliprdr->capabilities & CB_USE_LONG_FORMAT_NAMES) != 0)
{
while (left_size >= 6)
{
formatMapping *map;
BYTE* tmp;
int name_len;
map = &cliprdr->format_mappings[i++];
Read_UINT32(p, map->remote_format_id);
map->name = NULL;
/* get name_len */
for (tmp = p, name_len = 0; tmp + 1 < end_mark; tmp += 2, name_len += 2)
{
if (*((unsigned short*) tmp) == 0)
break;
}
if (name_len > 0)
{
map->name = malloc(name_len + 2);
memcpy(map->name, p, name_len + 2);
map->local_format_id = RegisterClipboardFormatW((LPCWSTR)map->name);
}
else
{
map->local_format_id = map->remote_format_id;
}
left_size -= name_len + 4 + 2;
p += name_len + 2; /* p's already +4 when Read_UINT32() is called. */
cliprdr->map_size++;
map_ensure_capacity(cliprdr);
}
}
else
{
int k;
for (k = 0; k < event->raw_format_data_size / 36; k++)
{
formatMapping *map;
int name_len;
map = &cliprdr->format_mappings[i++];
Read_UINT32(p, map->remote_format_id);
map->name = NULL;
if (event->raw_format_unicode)
{
/* get name_len, in bytes, if the file name is truncated, no terminated null will be included. */
for (name_len = 0; name_len < 32; name_len += 2)
{
if (*((unsigned short*) (p + name_len)) == 0)
break;
}
if (name_len > 0)
{
map->name = calloc(1, name_len + 2);
memcpy(map->name, p, name_len);
map->local_format_id = RegisterClipboardFormatW((LPCWSTR)map->name);
}
else
{
map->local_format_id = map->remote_format_id;
}
}
else
{
/* get name_len, in bytes, if the file name is truncated, no terminated null will be included. */
for (name_len = 0; name_len < 32; name_len += 1)
{
if (*((unsigned char*) (p + name_len)) == 0)
break;
}
if (name_len > 0)
{
map->name = calloc(1, name_len + 1);
memcpy(map->name, p, name_len);
map->local_format_id = RegisterClipboardFormatA((LPCSTR)map->name);
}
else
{
map->local_format_id = map->remote_format_id;
}
}
p += 32; /* p's already +4 when Read_UINT32() is called. */
cliprdr->map_size++;
map_ensure_capacity(cliprdr);
}
}
if (!OpenClipboard(cliprdr->hwndClipboard))
{
DEBUG_CLIPRDR("OpenClipboard failed with 0x%x", GetLastError());
return;
}
if (!EmptyClipboard())
{
DEBUG_CLIPRDR("EmptyClipboard failed with 0x%x", GetLastError());
CloseClipboard();
return;
}
for (i = 0; i < cliprdr->map_size; i++)
{
SetClipboardData(cliprdr->format_mappings[i].local_format_id, NULL);
}
CloseClipboard();
}
static void wf_cliprdr_process_cb_data_response_event(wfContext *wfc, RDP_CB_DATA_RESPONSE_EVENT *event)
{
cliprdrContext *cliprdr = (cliprdrContext *)wfc->cliprdr_context;
HANDLE hMem;
char *buff;
hMem = GlobalAlloc(GMEM_FIXED, event->size);
buff = (char *) GlobalLock(hMem);
memcpy(buff, event->data, event->size);
GlobalUnlock(hMem);
cliprdr->hmem = hMem;
SetEvent(cliprdr->response_data_event);
}
void wf_process_cliprdr_event(wfContext *wfc, wMessage *event)
{
switch (GetMessageType(event->id))
{
case CliprdrChannel_ClipCaps:
wf_cliprdr_process_cb_clip_caps_event(wfc, (RDP_CB_CLIP_CAPS *)event);
break;
case CliprdrChannel_MonitorReady:
wf_cliprdr_process_cb_monitor_ready_event(wfc, (RDP_CB_MONITOR_READY_EVENT *)event);
break;
case CliprdrChannel_FormatList:
wf_cliprdr_process_cb_format_list_event(wfc, (RDP_CB_FORMAT_LIST_EVENT *) event);
break;
case CliprdrChannel_DataRequest:
wf_cliprdr_process_cb_data_request_event(wfc, (RDP_CB_DATA_REQUEST_EVENT *) event);
break;
case CliprdrChannel_DataResponse:
wf_cliprdr_process_cb_data_response_event(wfc, (RDP_CB_DATA_RESPONSE_EVENT *) event);
break;
default:
break;
}
}
BOOL wf_cliprdr_process_selection_notify(wfContext* wfc, HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
return TRUE;
}
BOOL wf_cliprdr_process_selection_request(wfContext* wfc, HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
return TRUE;
}
BOOL wf_cliprdr_process_selection_clear(wfContext* wfc, HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
return TRUE;
}
BOOL wf_cliprdr_process_property_notify(wfContext* wfc, HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
return TRUE;
}
void wf_cliprdr_check_owner(wfContext* wfc)
{
}