From c909198eca202f1a2424ce739c541a740dd61281 Mon Sep 17 00:00:00 2001 From: Arnon Gilboa Date: Fri, 1 Oct 2010 16:06:10 +0200 Subject: client: support clipboard/selection-owner model (v2) -includes most of Hans' review fixes (up to the SelectionRequest comment [4]) & X11 wips sent by Hans (10x!) -use the VD_AGENT_CLIPBOARD_* types in the platform code -add ifs for VD_AGENT_CAP_CLIPBOARD_BY_DEMAND in both sides -support the GRAB/REQUEST/DATA/RELEASE verbs in both ways -pasting clipboard data is now "only-by-demand" from both sides (client and agent), whose behavior is symmetric -client and agent don't read or send the contents of the clipboard unnecessarily (e.g. copy, internal paste, repeating paste, focus change) -set client as clipboard listener instead of application -add atexit(cleanup) in win platform linux: -instead of clipboard atom selection instead of XA_PRIMARY -enable USE_XRANDR_1_2 and support clipboard in MultyMonScreen -send utf8 with no null termination, remove ++size -add xfixes in configure.ac & Makefile.am windows: -bonus: support image cut & paste, currently only on windows not done yet: -clipboards formats are still uint32_t, not mime types stores as strings -platform_win is still used, not the root window -not replaced the ugly windows CF_DIB in agent/winclient --- client/application.cpp | 6 - client/application.h | 2 - client/platform.h | 13 +- client/red_client.cpp | 159 ++++++++++-------- client/red_client.h | 30 +++- client/windows/platform.cpp | 264 ++++++++++++++++++++++++------ client/x11/Makefile.am | 2 + client/x11/platform.cpp | 389 ++++++++++++++++++++++++++++++-------------- 8 files changed, 600 insertions(+), 265 deletions(-) (limited to 'client') diff --git a/client/application.cpp b/client/application.cpp index 12580679..c5d34ff0 100644 --- a/client/application.cpp +++ b/client/application.cpp @@ -1318,12 +1318,6 @@ void Application::on_app_activated() { _active = true; _key_handler->on_focus_in(); - Platform::set_clipboard_listener(this); -} - -void Application::on_clipboard_change() -{ - _client.on_clipboard_change(); } void Application::on_app_deactivated() diff --git a/client/application.h b/client/application.h index dde37fca..36ae86e8 100644 --- a/client/application.h +++ b/client/application.h @@ -141,7 +141,6 @@ typedef std::list GUIBarriers; class Application : public ProcessLoop, public Platform::EventListener, public Platform::DisplayModeListener, - public Platform::ClipboardListener, public CommandTarget { public: @@ -191,7 +190,6 @@ public: virtual void on_app_deactivated(); virtual void on_monitors_change(); virtual void on_display_mode_change(); - virtual void on_clipboard_change(); void on_connected(); void on_disconnected(int spice_error_code); void on_disconnecting(); diff --git a/client/platform.h b/client/platform.h index 6288380a..47278893 100644 --- a/client/platform.h +++ b/client/platform.h @@ -121,13 +121,10 @@ public: class ClipboardListener; static void set_clipboard_listener(ClipboardListener* listener); - enum { - CLIPBOARD_UTF8_TEXT = 1, - }; - + static bool set_clipboard_owner(uint32_t type); static bool set_clipboard_data(uint32_t type, const uint8_t* data, int32_t size); - static bool get_clipboard_data(uint32_t type, uint8_t* data, int32_t size); - static int32_t get_clipboard_data_size(uint32_t type); + static bool request_clipboard_notification(uint32_t type); + static void release_clipboard(); }; class Platform::EventListener { @@ -141,7 +138,9 @@ public: class Platform::ClipboardListener { public: virtual ~ClipboardListener() {} - virtual void on_clipboard_change() = 0; + virtual void on_clipboard_grab(uint32_t type) = 0; + virtual void on_clipboard_request(uint32_t type) = 0; + virtual void on_clipboard_notify(uint32_t type, uint8_t* data, int32_t size) = 0; }; class Platform::RecordClient { diff --git a/client/red_client.cpp b/client/red_client.cpp index 79f5e6d6..562b8269 100644 --- a/client/red_client.cpp +++ b/client/red_client.cpp @@ -81,9 +81,18 @@ uint32_t default_agent_caps[] = { (1 << VD_AGENT_CAP_REPLY) }; -void ClipboardEvent::response(AbstractProcessLoop& events_loop) +void ClipboardGrabEvent::response(AbstractProcessLoop& events_loop) { - static_cast(events_loop.get_owner())->send_agent_clipboard(); + VDAgentClipboardGrab grab = {_type}; + static_cast(events_loop.get_owner())->send_agent_clipboard_message( + VD_AGENT_CLIPBOARD_GRAB, sizeof(grab), &grab); +} + +void ClipboardRequestEvent::response(AbstractProcessLoop& events_loop) +{ + VDAgentClipboardRequest request = {_type}; + static_cast(events_loop.get_owner())->send_agent_clipboard_message( + VD_AGENT_CLIPBOARD_REQUEST, sizeof(request), &request); } Migrate::Migrate(RedClient& client) @@ -339,6 +348,7 @@ RedClient::RedClient(Application& application) , _migrate (*this) , _glz_window (0, _glz_debug) { + Platform::set_clipboard_listener(this); MainChannelLoop* message_loop = static_cast(get_message_handler()); uint32_t default_caps_size = SPICE_N_ELEMENTS(default_agent_caps); @@ -542,6 +552,7 @@ bool RedClient::abort_channels() bool RedClient::abort() { if (!_aborting) { + Platform::set_clipboard_listener(NULL); Lock lock(_sync_lock); _aborting = true; _sync_condition.notify_all(); @@ -674,8 +685,8 @@ void RedClient::send_agent_announce_capabilities(bool request) VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MOUSE_STATE); VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MONITORS_CONFIG); VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_REPLY); - VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD); VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_DISPLAY_CONFIG); + VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND); ASSERT(_agent_tokens) _agent_tokens--; post_message(message); @@ -784,16 +795,6 @@ void RedClient::on_display_mode_change() #endif } -uint32_t get_agent_clipboard_type(uint32_t type) -{ - switch (type) { - case Platform::CLIPBOARD_UTF8_TEXT: - return VD_AGENT_CLIPBOARD_UTF8_TEXT; - default: - return 0; - } -} - void RedClient::do_send_agent_clipboard() { uint32_t size; @@ -802,8 +803,7 @@ void RedClient::do_send_agent_clipboard() (size = MIN(VD_AGENT_MAX_DATA_SIZE, _agent_out_msg_size - _agent_out_msg_pos))) { Message* message = new Message(SPICE_MSGC_MAIN_AGENT_DATA); - void* data = - spice_marshaller_reserve_space(message->marshaller(), size); + void* data = spice_marshaller_reserve_space(message->marshaller(), size); memcpy(data, (uint8_t*)_agent_out_msg + _agent_out_msg_pos, size); _agent_tokens--; post_message(message); @@ -817,14 +817,47 @@ void RedClient::do_send_agent_clipboard() } } -//FIXME: currently supports text only; better name - poll_clipboard? -void RedClient::send_agent_clipboard() +void RedClient::send_agent_clipboard_message(uint32_t message_type, uint32_t size, void* data) +{ + Message* message = new Message(SPICE_MSGC_MAIN_AGENT_DATA); + VDAgentMessage* msg = (VDAgentMessage*) + spice_marshaller_reserve_space(message->marshaller(), sizeof(VDAgentMessage) + size); + msg->protocol = VD_AGENT_PROTOCOL; + msg->type = message_type; + msg->opaque = 0; + msg->size = size; + if (size && data) { + memcpy(msg->data, data, size); + } + ASSERT(_agent_tokens) + _agent_tokens--; + post_message(message); +} + +void RedClient::on_clipboard_grab(uint32_t type) +{ + if (!_agent_caps || !VD_AGENT_HAS_CAPABILITY(_agent_caps, _agent_caps_size, + VD_AGENT_CAP_CLIPBOARD_BY_DEMAND)) { + return; + } + AutoRef event(new ClipboardGrabEvent(type)); + get_process_loop().push_event(*event); +} + +void RedClient::on_clipboard_request(uint32_t type) { - //FIXME: check connected - assert on disconnect - uint32_t clip_type = Platform::CLIPBOARD_UTF8_TEXT; - int32_t clip_size = Platform::get_clipboard_data_size(clip_type); + if (!_agent_caps || !VD_AGENT_HAS_CAPABILITY(_agent_caps, _agent_caps_size, + VD_AGENT_CAP_CLIPBOARD_BY_DEMAND)) { + return; + } + AutoRef event(new ClipboardRequestEvent(type)); + get_process_loop().push_event(*event); +} - if (!clip_size || !_agent_connected) { +void RedClient::on_clipboard_notify(uint32_t type, uint8_t* data, int32_t size) +{ + ASSERT(size && data); + if (!_agent_connected) { return; } if (_agent_out_msg) { @@ -832,32 +865,20 @@ void RedClient::send_agent_clipboard() return; } _agent_out_msg_pos = 0; - _agent_out_msg_size = sizeof(VDAgentMessage) + sizeof(VDAgentClipboard) + clip_size; + _agent_out_msg_size = sizeof(VDAgentMessage) + sizeof(VDAgentClipboard) + size; _agent_out_msg = (VDAgentMessage*)new uint8_t[_agent_out_msg_size]; _agent_out_msg->protocol = VD_AGENT_PROTOCOL; _agent_out_msg->type = VD_AGENT_CLIPBOARD; _agent_out_msg->opaque = 0; - _agent_out_msg->size = sizeof(VDAgentClipboard) + clip_size; + _agent_out_msg->size = sizeof(VDAgentClipboard) + size; VDAgentClipboard* clipboard = (VDAgentClipboard*)_agent_out_msg->data; - clipboard->type = get_agent_clipboard_type(clip_type); - if (!Platform::get_clipboard_data(clip_type, clipboard->data, clip_size)) { - delete[] (uint8_t *)_agent_out_msg; - _agent_out_msg = NULL; - _agent_out_msg_size = 0; - return; - } - + clipboard->type = type; + memcpy(clipboard->data, data, size); if (_agent_tokens) { do_send_agent_clipboard(); } } -void RedClient::on_clipboard_change() -{ - AutoRef event(new ClipboardEvent()); - get_process_loop().push_event(*event); -} - void RedClient::set_mouse_mode(uint32_t supported_modes, uint32_t current_mode) { if (current_mode != _mouse_mode) { @@ -884,17 +905,6 @@ void RedClient::set_mouse_mode(uint32_t supported_modes, uint32_t current_mode) } } -void RedClient::on_agent_clipboard(VDAgentClipboard* clipboard, uint32_t size) -{ - switch (clipboard->type) { - case VD_AGENT_CLIPBOARD_UTF8_TEXT: - Platform::set_clipboard_data(Platform::CLIPBOARD_UTF8_TEXT, clipboard->data, size); - break; - default: - THROW("unexpected vdagent clipboard data type"); - } -} - void RedClient::handle_init(RedPeer::InMessage* message) { SpiceMsgMainInit *init = (SpiceMsgMainInit *)message->data(); @@ -1055,25 +1065,7 @@ void RedClient::handle_agent_data(RedPeer::InMessage* message) } if (_agent_msg_pos == sizeof(VDAgentMessage) + _agent_msg->size) { DBG(0, "agent msg end"); - switch (_agent_msg->type) { - case VD_AGENT_ANNOUNCE_CAPABILITIES: { - on_agent_announce_capabilities( - (VDAgentAnnounceCapabilities*)_agent_msg_data, - _agent_msg->size); - break; - } - case VD_AGENT_REPLY: { - on_agent_reply((VDAgentReply*)_agent_msg_data); - break; - } - case VD_AGENT_CLIPBOARD: { - on_agent_clipboard((VDAgentClipboard*)_agent_msg_data, - _agent_msg->size - sizeof(VDAgentClipboard)); - break; - } - default: - DBG(0, "Unsupported message type %u size %u", _agent_msg->type, _agent_msg->size); - } + dispatch_agent_message(_agent_msg, _agent_msg_data); delete[] _agent_msg_data; _agent_msg_data = NULL; _agent_msg_pos = 0; @@ -1081,6 +1073,39 @@ void RedClient::handle_agent_data(RedPeer::InMessage* message) } } +void RedClient::dispatch_agent_message(VDAgentMessage* msg, void* data) +{ + switch (msg->type) { + case VD_AGENT_ANNOUNCE_CAPABILITIES: { + on_agent_announce_capabilities((VDAgentAnnounceCapabilities*)data, msg->size); + break; + } + case VD_AGENT_REPLY: { + on_agent_reply((VDAgentReply*)data); + break; + } + case VD_AGENT_CLIPBOARD: { + VDAgentClipboard* clipboard = (VDAgentClipboard*)data; + Platform::set_clipboard_data(clipboard->type, clipboard->data, + msg->size - sizeof(VDAgentClipboard)); + break; + } + case VD_AGENT_CLIPBOARD_GRAB: + Platform::set_clipboard_owner(((VDAgentClipboardGrab*)data)->type); + break; + case VD_AGENT_CLIPBOARD_REQUEST: + if (!Platform::request_clipboard_notification(((VDAgentClipboardRequest*)data)->type)) { + send_agent_clipboard_message(VD_AGENT_CLIPBOARD_RELEASE); + } + break; + case VD_AGENT_CLIPBOARD_RELEASE: + Platform::release_clipboard(); + break; + default: + DBG(0, "Unsupported message type %u size %u", msg->type, msg->size); + } +} + void RedClient::handle_agent_tokens(RedPeer::InMessage* message) { SpiceMsgMainAgentTokens *token = (SpiceMsgMainAgentTokens *)message->data(); diff --git a/client/red_client.h b/client/red_client.h index ba8b4ee9..3ccb8e32 100644 --- a/client/red_client.h +++ b/client/red_client.h @@ -144,16 +144,31 @@ public: uint32_t _color_depth; }; -class ClipboardEvent : public Event { +class ClipboardGrabEvent : public Event { public: + ClipboardGrabEvent(uint32_t type) : _type (type) {} virtual void response(AbstractProcessLoop& events_loop); + +private: + uint32_t _type; }; -class RedClient: public RedChannel { +class ClipboardRequestEvent : public Event { +public: + ClipboardRequestEvent(uint32_t type) : _type (type) {} + virtual void response(AbstractProcessLoop& events_loop); + +private: + uint32_t _type; +}; + +class RedClient: public RedChannel, + public Platform::ClipboardListener { public: friend class RedChannel; friend class Migrate; - friend class ClipboardEvent; + friend class ClipboardGrabEvent; + friend class ClipboardRequestEvent; RedClient(Application& application); ~RedClient(); @@ -192,7 +207,10 @@ public: PixmapCache& get_pixmap_cache() {return _pixmap_cache;} uint64_t get_pixmap_cache_size() { return _pixmap_cache_size;} void on_display_mode_change(); - void on_clipboard_change(); + void on_clipboard_grab(uint32_t type); + void on_clipboard_request(uint32_t type); + void on_clipboard_notify(uint32_t type, uint8_t* data, int32_t size); + void for_each_channel(ForEachChannelFunc& func); void on_mouse_capture_trigger(RedScreen& screen); @@ -228,13 +246,13 @@ private: void handle_agent_data(RedPeer::InMessage* message); void handle_agent_tokens(RedPeer::InMessage* message); void handle_migrate_switch_host(RedPeer::InMessage* message); + void dispatch_agent_message(VDAgentMessage* msg, void* data); void on_agent_reply(VDAgentReply* reply); void on_agent_announce_capabilities(VDAgentAnnounceCapabilities* caps, uint32_t msg_size); - void on_agent_clipboard(VDAgentClipboard* clipboard, uint32_t size); - void send_agent_clipboard(); void do_send_agent_clipboard(); + void send_agent_clipboard_message(uint32_t message_type, uint32_t size = 0, void* data = NULL); ChannelFactory* find_factory(uint32_t type); void create_channel(uint32_t type, uint32_t id); diff --git a/client/windows/platform.cpp b/client/windows/platform.cpp index ae49d028..95876dc5 100644 --- a/client/windows/platform.cpp +++ b/client/windows/platform.cpp @@ -32,6 +32,7 @@ #include "playback.h" #include "cursor.h" #include "named_pipe.h" +#include int gdi_handlers = 0; extern HINSTANCE instance; @@ -45,9 +46,40 @@ public: static DefaultEventListener default_event_listener; static Platform::EventListener* event_listener = &default_event_listener; -static HWND paltform_win; +static HWND platform_win = NULL; static ProcessLoop* main_loop = NULL; +class DefaultClipboardListener: public Platform::ClipboardListener { +public: + virtual void on_clipboard_grab(uint32_t type) {} + virtual void on_clipboard_request(uint32_t type) {} + virtual void on_clipboard_notify(uint32_t type, uint8_t* data, int32_t size) {} +}; + +static DefaultClipboardListener default_clipboard_listener; +static Platform::ClipboardListener* clipboard_listener = &default_clipboard_listener; + +// The next window in the clipboard viewer chain, which is refered in all clipboard events. +static HWND next_clipboard_viewer_win = NULL; +static HANDLE clipboard_event = NULL; + +// clipboard_changer says whether the client was the last one to change cliboard, for loop +// prevention. It's initialized to true so we ignore the first clipboard change event which +// happens right when we call SetClipboardViewer(). +static bool clipboard_changer = true; + +static const int CLIPBOARD_TIMEOUT_MS = 10000; + +typedef struct ClipboardFormat { + uint32_t format; + uint32_t type; +} ClipboardFormat; + +static ClipboardFormat clipboard_formats[] = { + {CF_UNICODETEXT, VD_AGENT_CLIPBOARD_UTF8_TEXT}, + {CF_DIB, VD_AGENT_CLIPBOARD_BITMAP}, + {0, 0}}; + static const unsigned long MODAL_LOOP_TIMER_ID = 1; static const int MODAL_LOOP_DEFAULT_TIMEOUT = 100; static bool modal_loop_active = false; @@ -59,6 +91,32 @@ void Platform::send_quit_request() main_loop->quit(0); } +static uint32_t get_clipboard_type(uint32_t format) { + ClipboardFormat* iter; + + for (iter = clipboard_formats; iter->type && iter->format != format; iter++); + return iter->type; +} + +static uint32_t get_clipboard_format(uint32_t type) { + ClipboardFormat* iter; + + for (iter = clipboard_formats; iter->format && iter->type != type; iter++); + return iter->format; +} + +static uint32_t get_available_clipboard_type() +{ + uint32_t type = 0; + + for (ClipboardFormat* iter = clipboard_formats; iter->format && !type; iter++) { + if (IsClipboardFormatAvailable(iter->format)) { + type = iter->type; + } + } + return type; +} + static LRESULT CALLBACK PlatformWinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { @@ -82,6 +140,44 @@ static LRESULT CALLBACK PlatformWinProc(HWND hWnd, UINT message, WPARAM wParam, case WM_DISPLAYCHANGE: event_listener->on_monitors_change(); break; + case WM_CHANGECBCHAIN: + if (next_clipboard_viewer_win == (HWND)wParam) { + next_clipboard_viewer_win = (HWND)lParam; + } else if (next_clipboard_viewer_win) { + SendMessage(next_clipboard_viewer_win, message, wParam, lParam); + } + break; + case WM_DRAWCLIPBOARD: + if (!clipboard_changer) { + uint32_t type = get_available_clipboard_type(); + if (type) { + clipboard_listener->on_clipboard_grab(type); + } else { + LOG_INFO("Unsupported clipboard format"); + } + } else { + clipboard_changer = false; + } + if (next_clipboard_viewer_win) { + SendMessage(next_clipboard_viewer_win, message, wParam, lParam); + } + break; + case WM_RENDERFORMAT: { + // In delayed rendering, Windows requires us to SetClipboardData before we return from + // handling WM_RENDERFORMAT. Therefore, we try our best by sending CLIPBOARD_REQUEST to the + // agent, while waiting alertably for a while (hoping for good) for receiving CLIPBOARD data + // or CLIPBOARD_RELEASE from the agent, which both will signal clipboard_event. + uint32_t type = get_clipboard_type(wParam); + if (!type) { + LOG_INFO("Unsupported clipboard format %u", wParam); + break; + } + clipboard_listener->on_clipboard_request(type); + DWORD start_tick = GetTickCount(); + while (WaitForSingleObjectEx(clipboard_event, 1000, TRUE) != WAIT_OBJECT_0 && + GetTickCount() < start_tick + CLIPBOARD_TIMEOUT_MS); + break; + } default: return DefWindowProc(hWnd, message, wParam, lParam); } @@ -92,7 +188,6 @@ static void create_message_wind() { WNDCLASSEX wclass; ATOM class_atom; - HWND window; const LPCWSTR class_name = L"spicec_platform_wclass"; @@ -113,11 +208,16 @@ static void create_message_wind() THROW("register class failed"); } - if (!(window = CreateWindow(class_name, L"", 0, 0, 0, 0, 0, NULL, NULL, instance, NULL))) { + if (!(platform_win = CreateWindow(class_name, L"", 0, 0, 0, 0, 0, NULL, NULL, instance, NULL))) { THROW("create message window failed"); } - paltform_win = window; + if (!(next_clipboard_viewer_win = SetClipboardViewer(platform_win)) && GetLastError()) { + THROW("set clipboard viewer failed"); + } + if (!(clipboard_event = CreateEvent(NULL, FALSE, FALSE, NULL))) { + THROW("create clipboard event failed"); + } } NamedPipe::ListenerRef NamedPipe::create(const char *name, ListenerInterface& listener_interface) @@ -460,9 +560,16 @@ void Platform::path_append(std::string& path, const std::string& partial_path) path += partial_path; } +static void cleanup() +{ + ChangeClipboardChain(platform_win, next_clipboard_viewer_win); + CloseHandle(clipboard_event); +} + void Platform::init() { create_message_wind(); + atexit(cleanup); } void Platform::set_process_loop(ProcessLoop& main_process_loop) @@ -733,7 +840,7 @@ static bool set_modal_loop_timer() the enterance to the loop*/ } - if (!SetTimer(paltform_win, MODAL_LOOP_TIMER_ID, timeout, NULL)) { + if (!SetTimer(platform_win, MODAL_LOOP_TIMER_ID, timeout, NULL)) { return false; } return true; @@ -745,99 +852,152 @@ void WinPlatform::exit_modal_loop() LOG_INFO("not inside the loop"); return; } - KillTimer(paltform_win, MODAL_LOOP_TIMER_ID); + KillTimer(platform_win, MODAL_LOOP_TIMER_ID); modal_loop_active = false; } -void Platform::set_clipboard_listener(ClipboardListener* listener) +bool Platform::set_clipboard_owner(uint32_t type) { - //FIXME: call only on change, use statics - listener->on_clipboard_change(); + uint32_t format = get_clipboard_format(type); + + if (!format) { + LOG_INFO("Unsupported clipboard type %u", type); + return false; + } + if (!OpenClipboard(platform_win)) { + return false; + } + clipboard_changer = true; + EmptyClipboard(); + SetClipboardData(format, NULL); + CloseClipboard(); + return true; } -UINT get_format(uint32_t type) +void Platform::set_clipboard_listener(ClipboardListener* listener) { - switch (type) { - case Platform::CLIPBOARD_UTF8_TEXT: - return CF_UNICODETEXT; - default: - return 0; - } + clipboard_listener = listener ? listener : &default_clipboard_listener; } bool Platform::set_clipboard_data(uint32_t type, const uint8_t* data, int32_t size) { - UINT format = get_format(type); HGLOBAL clip_data; LPVOID clip_buf; int clip_size; - bool ret; + int clip_len; + UINT format; + bool ret = false; - //LOG_INFO("type %u size %d %s", type, size, data); - if (!format || !OpenClipboard(paltform_win)) { + // Get the required clipboard size + switch (type) { + case VD_AGENT_CLIPBOARD_UTF8_TEXT: + // Received utf8 string is not null-terminated + if (!(clip_len = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)data, size, NULL, 0))) { + return false; + } + clip_len++; + clip_size = clip_len * sizeof(WCHAR); + break; + case VD_AGENT_CLIPBOARD_BITMAP: + clip_size = size; + break; + default: + LOG_INFO("Unsupported clipboard type %u", type); return false; } - clip_size = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)data, size, NULL, 0); - if (!clip_size || !(clip_data = GlobalAlloc(GMEM_DDESHARE, clip_size * sizeof(WCHAR)))) { - CloseClipboard(); + + // Allocate and lock clipboard memory + if (!(clip_data = GlobalAlloc(GMEM_DDESHARE, clip_size))) { return false; } if (!(clip_buf = GlobalLock(clip_data))) { GlobalFree(clip_data); - CloseClipboard(); return false; } - ret = !!MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)data, size, (LPWSTR)clip_buf, clip_size); + + // Translate data and set clipboard content + switch (type) { + case VD_AGENT_CLIPBOARD_UTF8_TEXT: + ret = !!MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)data, size, (LPWSTR)clip_buf, clip_len); + ((LPWSTR)clip_buf)[clip_len - 1] = L'\0'; + break; + case VD_AGENT_CLIPBOARD_BITMAP: + memcpy(clip_buf, data, size); + ret = true; + break; + } GlobalUnlock(clip_data); - if (ret) { - EmptyClipboard(); - ret = !!SetClipboardData(format, clip_data); + if (!ret) { + return false; + } + format = get_clipboard_format(type); + if (SetClipboardData(format, clip_data)) { + SetEvent(clipboard_event); + return true; } + // We retry clipboard open-empty-set-close only when there is a timeout in WM_RENDERFORMAT + if (!OpenClipboard(platform_win)) { + return false; + } + EmptyClipboard(); + ret = !!SetClipboardData(format, clip_data); CloseClipboard(); return ret; } -bool Platform::get_clipboard_data(uint32_t type, uint8_t* data, int32_t size) +bool Platform::request_clipboard_notification(uint32_t type) { - UINT format = get_format(type); + UINT format = get_clipboard_format(type); HANDLE clip_data; LPVOID clip_buf; - bool ret; + bool ret = false; - LOG_INFO("type %u size %d", type, size); - if (!format || !IsClipboardFormatAvailable(format) || !OpenClipboard(paltform_win)) { + if (!format || !IsClipboardFormatAvailable(format) || !OpenClipboard(platform_win)) { return false; } if (!(clip_data = GetClipboardData(format)) || !(clip_buf = GlobalLock(clip_data))) { CloseClipboard(); return false; } - ret = !!WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)clip_buf, -1, (LPSTR)data, size, NULL, NULL); - GlobalUnlock(clip_data); - CloseClipboard(); - return ret; -} - -int32_t Platform::get_clipboard_data_size(uint32_t type) -{ - UINT format = get_format(type); - HANDLE clip_data; - LPVOID clip_buf; - int clip_size; - if (!format || !IsClipboardFormatAvailable(format) || !OpenClipboard(paltform_win)) { - return 0; + switch (type) { + case VD_AGENT_CLIPBOARD_UTF8_TEXT: { + size_t len = wcslen((wchar_t*)clip_buf); + int utf8_size = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)clip_buf, len, NULL, 0, NULL, NULL); + if (!utf8_size) { + break; + } + uint8_t* utf8_data = new uint8_t[utf8_size]; + if (WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)clip_buf, len, (LPSTR)utf8_data, utf8_size, + NULL, NULL)) { + clipboard_listener->on_clipboard_notify(type, utf8_data, utf8_size); + ret = true; + } + delete[] (uint8_t *)utf8_data; + break; } - if (!(clip_data = GetClipboardData(format)) || !(clip_buf = GlobalLock(clip_data))) { - CloseClipboard(); - return 0; + case VD_AGENT_CLIPBOARD_BITMAP: { + size_t clip_size = GlobalSize(clip_data); + if (!clip_size) { + break; + } + clipboard_listener->on_clipboard_notify(type, (uint8_t*)clip_buf, clip_size); + ret = true; + break; } - clip_size = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)clip_buf, -1, NULL, 0, NULL, NULL); + default: + LOG_INFO("Unsupported clipboard type %u", type); + } + GlobalUnlock(clip_data); CloseClipboard(); - return clip_size; + return ret; } +void Platform::release_clipboard() +{ + SetEvent(clipboard_event); +} static bool has_console = false; diff --git a/client/x11/Makefile.am b/client/x11/Makefile.am index a992aa17..02aa8eb2 100644 --- a/client/x11/Makefile.am +++ b/client/x11/Makefile.am @@ -22,6 +22,7 @@ INCLUDES = \ $(CELT051_CFLAGS) \ $(SSL_CFLAGS) \ $(XRANDR_CFLAGS) \ + $(XFIXES_CFLAGS) \ $(MISC_X_CFLAGS) \ $(CEGUI_CFLAGS) \ $(WARN_CFLAGS) \ @@ -202,5 +203,6 @@ spicec_LDADD = \ $(ALSA_LIBS) \ $(GL_LIBS) \ $(XRANDR_LIBS) \ + $(XFIXES_LIBS) \ $(MISC_X_LIBS) \ $(CEGUI_LIBS) diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp index ca5f1d57..302b7510 100644 --- a/client/x11/platform.cpp +++ b/client/x11/platform.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +59,7 @@ #include "res.h" #include "cursor.h" #include "process_loop.h" +#include #define DWORD uint32_t #define BOOL bool @@ -96,24 +98,39 @@ static int xrandr_minor = 0; static bool using_xrender_0_5 = false; +static bool using_xfixes_1_0 = false; + +static int xfixes_event_base; +static int xfixes_error_base; + static unsigned int caps_lock_mask = 0; static unsigned int num_lock_mask = 0; //FIXME: nicify static uint8_t* clipboard_data = NULL; +static int32_t clipboard_data_type = 0; static int32_t clipboard_data_size = 0; static int32_t clipboard_data_space = 0; +static int32_t clipboard_request_type = 0; +static bool clipboard_changer = false; +static XEvent clipboard_event; static Mutex clipboard_lock; static Atom clipboard_prop; static Atom incr_atom; static Atom utf8_atom; -//static Atom clipboard_type_utf8; -#ifdef USE_XRANDR_1_2 -static bool clipboard_inited = false; -#endif +static Atom targets_atom; static Bool handle_x_error = false; static int x_error_code; +typedef struct ClipboardFormat { + uint32_t format; + uint32_t type; +} ClipboardFormat; + +static ClipboardFormat clipboard_formats[] = { + {0, 0}, + {0, 0}}; + class DefaultEventListener: public Platform::EventListener { public: virtual void on_app_activated() {} @@ -134,12 +151,28 @@ static Platform::DisplayModeListener* display_mode_listener = &default_display_m class DefaultClipboardListener: public Platform::ClipboardListener { public: - void on_clipboard_change() {} + void on_clipboard_grab(uint32_t type) {} + void on_clipboard_request(uint32_t type) {} + void on_clipboard_notify(uint32_t type, uint8_t* data, int32_t size) {} }; static DefaultClipboardListener default_clipboard_listener; static Platform::ClipboardListener* clipboard_listener = &default_clipboard_listener; +static uint32_t get_clipboard_type(uint32_t format) { + ClipboardFormat* iter; + + for (iter = clipboard_formats; iter->type && iter->format != format; iter++); + return iter->type; +} + +static uint32_t get_clipboard_format(uint32_t type) { + ClipboardFormat* iter; + + for (iter = clipboard_formats; iter->format && iter->type != type; iter++); + return iter->format; +} + NamedPipe::ListenerRef NamedPipe::create(const char *name, ListenerInterface& listener_interface) { ASSERT(main_loop && main_loop->is_same_thread(pthread_self())); @@ -771,6 +804,11 @@ static void intern_clipboard_atoms() clipboard_prop = XInternAtom(x_display, "CLIPBOARD", False); incr_atom = XInternAtom(x_display, "INCR", False); utf8_atom = XInternAtom(x_display, "UTF8_STRING", False); + targets_atom = XInternAtom(x_display, "TARGETS", False); + + clipboard_formats[0].format = utf8_atom; + clipboard_formats[0].type = VD_AGENT_CLIPBOARD_UTF8_TEXT; + interned = true; } @@ -786,13 +824,19 @@ DynamicScreen::DynamicScreen(Display* display, int screen, int& next_mon_id) platform_win = XCreateSimpleWindow(display, RootWindow(display, screen), 0, 0, 1, 1, 0, 0, 0); XSelectInput(display, platform_win, StructureNotifyMask); XRRSelectInput(display, platform_win, RRScreenChangeNotifyMask); + if (using_xfixes_1_0) { + XFixesSelectSelectionInput(display, platform_win, clipboard_prop, + XFixesSetSelectionOwnerNotifyMask | + XFixesSelectionWindowDestroyNotifyMask | + XFixesSelectionClientCloseNotifyMask); + } Monitor::self_monitors_change++; process_monitor_configure_events(platform_win); Monitor::self_monitors_change--; - XPlatform::set_win_proc(platform_win, root_win_proc); intern_clipboard_atoms(); + XPlatform::set_win_proc(platform_win, root_win_proc); X_DEBUG_SYNC(display); } @@ -881,7 +925,7 @@ bool DynamicScreen::set_screen_size(int size_index) Monitor::self_monitors_change++; /*what status*/ XRRSetScreenConfig(get_display(), config, root_window, size_index, rotation, CurrentTime); - process_monitor_configure_events(root_window); + process_monitor_configure_events(platform_win); Monitor::self_monitors_change--; XRRFreeScreenConfigInfo(config); X_DEBUG_SYNC(get_display()); @@ -1053,26 +1097,24 @@ MultyMonScreen::MultyMonScreen(Display* display, int screen, int& next_mon_id) throw; } - XSelectInput(display, root_window, StructureNotifyMask); + platform_win = XCreateSimpleWindow(display, RootWindow(display, screen), 0, 0, 1, 1, 0, 0, 0); + XSelectInput(display, platform_win, StructureNotifyMask); X_DEBUG_SYNC(get_display()); - XRRSelectInput(display, root_window, RRScreenChangeNotifyMask); + XRRSelectInput(display, platform_win, RRScreenChangeNotifyMask); X_DEBUG_SYNC(get_display()); - intern_clipboard_atoms(); - XPlatform::set_win_proc(root_window, root_win_proc); + if (using_xfixes_1_0) { + XFixesSelectSelectionInput(display, platform_win, clipboard_prop, + XFixesSetSelectionOwnerNotifyMask | + XFixesSelectionWindowDestroyNotifyMask | + XFixesSelectionClientCloseNotifyMask); + } XMonitor::inc_change_ref(); - process_monitor_configure_events(root_window); + process_monitor_configure_events(platform_win); XMonitor::dec_change_ref(); - X_DEBUG_SYNC(get_display()); - // - //platform_win = XCreateSimpleWindow(display, RootWindow(display, screen), 0, 0, 1, 1, 0, 0, 0); - //XSelectInput(display, platform_win, StructureNotifyMask); - //XRRSelectInput(display, platform_win, RRScreenChangeNotifyMask); - //XPlatform::set_win_proc(platform_win, root_win_proc); - //clipboard_prop = XInternAtom(x_display, "CLIPBOARD", False); - // - clipboard_inited = true; + intern_clipboard_atoms(); + XPlatform::set_win_proc(platform_win, root_win_proc); X_DEBUG_SYNC(get_display()); } @@ -1180,7 +1222,7 @@ void MultyMonScreen::restore() (*iter)->revert(); } enable(); - process_monitor_configure_events(root_window); + process_monitor_configure_events(platform_win); XMonitor::dec_change_ref(); X_DEBUG_SYNC(get_display()); } @@ -1742,8 +1784,7 @@ bool MultyMonScreen::set_monitor_mode(XMonitor& monitor, const XRRModeInfo& mode monitor.set_mode(mode_info); set_size(screen_width, screen_height); enable(); - Window root_window = RootWindow(get_display(), get_screen()); - process_monitor_configure_events(root_window); + process_monitor_configure_events(platform_win); XMonitor::dec_change_ref(); X_DEBUG_SYNC(get_display()); return true; @@ -2201,7 +2242,9 @@ static void ensure_clipboard_data_space(uint32_t size) static void realloc_clipboard_data_space(uint32_t size) { - if (size <= clipboard_data_space) return; + if (size <= clipboard_data_space) { + return; + } uint32_t old_alloc = clipboard_data_space; clipboard_data_space = size; uint8_t *newbuf = new uint8_t[clipboard_data_space]; @@ -2219,49 +2262,87 @@ static void update_clipboard(unsigned long size, uint8_t* data) clipboard_data_size = size; } - -/* NOTE: Function taken from xsel, original name get_append_property - * - * Get a window clipboard property and append its data to the clipboard_data - * - * Returns true if more data is available for receipt. - * - * Returns false if no data is availab, or on error. - */ -static bool -get_append_clipboard_data (XSelectionEvent* xsel) -{ - Atom target; - int format; - unsigned long bytesafter, length; - unsigned char * value; - - XGetWindowProperty (x_display, xsel->requestor, clipboard_prop, - 0L, 1000000, True, (Atom)AnyPropertyType, - &target, &format, &length, &bytesafter, &value); - - if (target != utf8_atom) { - LOG_INFO ("%s: target %d not UTF8", __func__, target); - // realloc clipboard_data to 0? - return false; - } else if (length == 0) { - /* A length of 0 indicates the end of the transfer */ - LOG_INFO ("Got zero length property; end of INCR transfer"); - return false; - } else if (format == 8) { +// Function based on xsel get_append_property() +// Get a window clipboard property and append its data to the clipboard_data +// Returns true if more data is available for receipt. false if no data is available, or on error. +static bool get_append_clipboard_data(XSelectionEvent* xsel) +{ + Atom target; + int format; + unsigned long bytesafter, length; + unsigned char* value; + + XGetWindowProperty(x_display, xsel->requestor, clipboard_prop, 0L, 1000000, True, + xsel->target, &target, &format, &length, &bytesafter, &value); + if (target != xsel->target) { + LOG_INFO("target %d != %u requested", target, xsel->target); + clipboard_data_size = 0; + return false; + } else if (length == 0) { + // A length of 0 indicates the end of the transfer + LOG_INFO("Got zero length property; end of INCR transfer"); + return false; + } else if (format != 8) { + LOG_INFO("Retrieved non-8-bit data"); + clipboard_data_size = 0; + return false; + } if (clipboard_data_size + length > clipboard_data_space) { - realloc_clipboard_data_space(clipboard_data_size + length); + realloc_clipboard_data_space(clipboard_data_size + length); } - strncpy ((char*)clipboard_data + clipboard_data_size, (char*)value, length); + memcpy((char*)clipboard_data + clipboard_data_size, (char*)value, length); clipboard_data_size += length; - LOG_INFO ("Appended %d bytes to buffer\n", length); - } else { - LOG_WARN ("Retrieved non-8-bit data\n"); - } - - return true; + LOG_INFO("Appended %d bytes to buffer", length); + return true; } +// FIXME: use INCR for large data transfers +static void send_selection_notify(Atom type) +{ + Window requestor_win = clipboard_event.xselectionrequest.requestor; + Atom prop = clipboard_event.xselectionrequest.property; + XEvent res; + + if (type != None) { + XChangeProperty(x_display, requestor_win, prop, type, 8, PropModeReplace, + (unsigned char *)clipboard_data, clipboard_data_size); + res.xselection.property = prop; + } else { + res.xselection.property = None; + } + res.xselection.type = SelectionNotify; + res.xselection.display = clipboard_event.xselectionrequest.display; + res.xselection.requestor = requestor_win; + res.xselection.selection = clipboard_event.xselectionrequest.selection; + res.xselection.target = clipboard_event.xselectionrequest.target; + res.xselection.time = clipboard_event.xselectionrequest.time; + XSendEvent(x_display, requestor_win, 0, 0, &res); + XFlush(x_display); +} + +static void send_targets(XEvent& request_event) +{ + XEvent res; + /* FIXME add MULTIPLE */ + /* FIXME add (and support) all 3 utf8 atom variations (see linux agent) */ + Atom targets[2] = { targets_atom, utf8_atom }; + + Window requestor_win = request_event.xselectionrequest.requestor; + Atom prop = request_event.xselectionrequest.property; + XChangeProperty(x_display, requestor_win, prop, XA_ATOM, 32, + PropModeReplace, (unsigned char *)&targets, + sizeof(targets)/sizeof(Atom)); + + res.xselection.property = prop; + res.xselection.type = SelectionNotify; + res.xselection.display = request_event.xselectionrequest.display; + res.xselection.requestor = requestor_win; + res.xselection.selection = request_event.xselectionrequest.selection; + res.xselection.target = targets_atom; + res.xselection.time = request_event.xselectionrequest.time; + XSendEvent(x_display, requestor_win, 0, 0, &res); + XFlush(x_display); +} static void root_win_proc(XEvent& event) { @@ -2289,28 +2370,45 @@ static void root_win_proc(XEvent& event) event_listener->on_monitors_change(); return; } - + if (event.type == XFixesSelectionNotify + xfixes_event_base) { + XFixesSelectionNotifyEvent* selection_event = (XFixesSelectionNotifyEvent *)&event; + if (selection_event->subtype != XFixesSetSelectionOwnerNotify) { + // FIXME: for some reason the XFixesSelectionWindowDestroyNotify/ClientCloseNotify + // events which can help for sending CLIPBOARD_RELEASE to the agent are not received + LOG_INFO("Unsupported selection event %u", selection_event->subtype); + return; + } + LOG_INFO("XFixesSetSelectionOwnerNotify %u", clipboard_changer); + if (clipboard_changer) { + clipboard_changer = false; + return; + } + // FIXME: use actual type + clipboard_listener->on_clipboard_grab(VD_AGENT_CLIPBOARD_UTF8_TEXT); + return; + } switch (event.type) { case SelectionRequest: { - //FIXME: support multi-chunk Lock lock(clipboard_lock); - if (clipboard_data_size == 0) { - return; + XSelectionRequestEvent* selection_request = (XSelectionRequestEvent*)&event; + + if (selection_request->target == targets_atom) { + send_targets(event); + break; } - Window requestor_win = event.xselectionrequest.requestor; - Atom prop = event.xselectionrequest.property; - XChangeProperty(x_display, requestor_win, prop, utf8_atom, 8, PropModeReplace, - (unsigned char *)clipboard_data, clipboard_data_size); - XEvent res; - res.xselection.property = prop; - res.xselection.type = SelectionNotify; - res.xselection.display = event.xselectionrequest.display; - res.xselection.requestor = requestor_win; - res.xselection.selection = event.xselectionrequest.selection; - res.xselection.target = event.xselectionrequest.target; - res.xselection.time = event.xselectionrequest.time; - XSendEvent(x_display, requestor_win, 0, 0, &res); - XFlush(x_display); + + uint32_t type = get_clipboard_type(selection_request->target); + if (!type) { + LOG_INFO("Unsupported selection type %s", XGetAtomName(x_display, selection_request->target)); + send_selection_notify(None); + break; + } + if (clipboard_data_size > 0) { + send_selection_notify(selection_request->target); + break; + } + clipboard_event = event; + clipboard_listener->on_clipboard_request(type); break; } case SelectionClear: { @@ -2325,17 +2423,17 @@ static void root_win_proc(XEvent& event) unsigned long size; unsigned long dummy; unsigned char *data; + XGetWindowProperty(x_display, platform_win, clipboard_prop, 0, 0, False, - AnyPropertyType, &type, &format, &len, &size, &data); + event.xselection.target, &type, &format, &len, &size, &data); if (size == 0) { + LOG_INFO("XGetWindowProperty(size) failed"); break; } - if (XGetWindowProperty(x_display, platform_win, clipboard_prop, 0, size, - False, AnyPropertyType, &type, &format, &len, &dummy, &data) != Success) { - LOG_INFO("XGetWindowProperty failed"); + if (type != event.xselection.target && type != incr_atom) { + LOG_INFO("type %d != %u requested", type, event.xselection.target); break; } - LOG_INFO("data: %s len: %u", data, len); { Lock lock(clipboard_lock); clipboard_data_size = 0; @@ -2344,7 +2442,7 @@ static void root_win_proc(XEvent& event) Window requestor_win = event.xselection.requestor; Atom prop = event.xselection.property; // is this always "CLIPBOARD"? // According to ICCCM spec 2.7.2 INCR Properties, and xsel reference - XSelectInput (x_display, requestor_win, PropertyChangeMask); + XSelectInput(x_display, requestor_win, PropertyChangeMask); XDeleteProperty(x_display, requestor_win, prop); waiting_for_property_notify = true; { @@ -2353,33 +2451,44 @@ static void root_win_proc(XEvent& event) } break; } + if (XGetWindowProperty(x_display, platform_win, clipboard_prop, 0, size, True, + event.xselection.target, &type, &format, &len, + &dummy, &data) != Success) { + LOG_INFO("XGetWindowProperty(data) failed"); + break; + } + if (type != event.xselection.target) { + LOG_INFO("type %d != %u requested", type, event.xselection.target); + break; + } { Lock lock(clipboard_lock); - update_clipboard(++size, data); + update_clipboard(size, data); } + clipboard_listener->on_clipboard_notify(clipboard_request_type, clipboard_data, + clipboard_data_size); + clipboard_request_type = 0; XFree(data); - clipboard_listener->on_clipboard_change(); break; } - case PropertyNotify: - if (!waiting_for_property_notify) { + case PropertyNotify: { + if (!waiting_for_property_notify || event.xproperty.state != PropertyNewValue) { break; } + bool finished_incr = false; { - if (event.xproperty.state != PropertyNewValue) break; - bool finished_incr = false; - { - Lock lock(clipboard_lock); - finished_incr = !get_append_clipboard_data(&event.xselection); - } - if (finished_incr) { - waiting_for_property_notify = false; - XDeleteProperty(x_display, event.xselection.requestor, - clipboard_prop); - clipboard_listener->on_clipboard_change(); - } + Lock lock(clipboard_lock); + finished_incr = !get_append_clipboard_data(&event.xselection); + } + if (finished_incr) { + waiting_for_property_notify = false; + XDeleteProperty(x_display, event.xselection.requestor, clipboard_prop); + clipboard_listener->on_clipboard_notify(clipboard_request_type, clipboard_data, + clipboard_data_size); + clipboard_request_type = 0; } break; + } default: return; } @@ -2471,6 +2580,15 @@ static void init_xrender() XRenderQueryVersion(x_display, &major, &minor) && (major > 0 || minor >= 5); } +static void init_xfixes() +{ + int major; + int minor; + + using_xfixes_1_0 = XFixesQueryExtension(x_display, &xfixes_event_base, &xfixes_error_base) && + XFixesQueryVersion(x_display, &major, &minor) && major >= 1; +} + unsigned int get_modifier_mask(KeySym modifier) { int mask = 0; @@ -2681,6 +2799,7 @@ void Platform::init() init_kbd(); init_xrandr(); init_xrender(); + init_xfixes(); init_XIM(); struct sigaction act; @@ -3017,26 +3136,35 @@ LocalCursor* Platform::create_default_cursor() return new XDefaultCursor(); } -void Platform::set_clipboard_listener(ClipboardListener* listener) +bool Platform::set_clipboard_owner(uint32_t type) { - //FIXME: XA_CLIPBOARD(x_display) - if (XGetSelectionOwner(x_display, XA_PRIMARY) == None) { - return; - } - clipboard_listener = listener; - /* Seems platform_win can be NULL, we'll just ignore that for now. - This will be fixed when the rest of cut and paste lands */ - if (platform_win) { - XConvertSelection(x_display, XA_PRIMARY, utf8_atom, clipboard_prop, - platform_win, CurrentTime); + Lock lock(clipboard_lock); + uint32_t format = get_clipboard_format(type); + + if (!format) { + LOG_INFO("Unsupported clipboard type %u", type); + return false; } + clipboard_changer = true; + clipboard_data_size = 0; + XSetSelectionOwner(x_display, clipboard_prop, platform_win, CurrentTime); + return true; +} + +void Platform::set_clipboard_listener(ClipboardListener* listener) +{ + clipboard_listener = listener ? listener : &default_clipboard_listener; } bool Platform::set_clipboard_data(uint32_t type, const uint8_t* data, int32_t size) { Lock lock(clipboard_lock); + uint32_t format = get_clipboard_format(type); - LOG_INFO("type %u size %u data %s", type, size, data); + if (!format) { + LOG_INFO("Unsupported clipboard type %u", type); + return false; + } if (size > clipboard_data_space) { delete clipboard_data; clipboard_data = new uint8_t[size]; @@ -3044,22 +3172,33 @@ bool Platform::set_clipboard_data(uint32_t type, const uint8_t* data, int32_t si } memcpy(clipboard_data, data, size); clipboard_data_size = size; - //FIXME: XA_CLIPBOARD(x_display) - XSetSelectionOwner(x_display, XA_PRIMARY, platform_win, CurrentTime); - LOG_INFO("XSetSelectionOwner"); + clipboard_data_type = type; + send_selection_notify(format); return true; } -bool Platform::get_clipboard_data(uint32_t type, uint8_t* data, int32_t size) +bool Platform::request_clipboard_notification(uint32_t type) { - //FIXME: check type - memcpy(data, clipboard_data, size); + uint32_t format = get_clipboard_format(type); + + if (!format) { + LOG_INFO("Unsupported clipboard type %u", type); + return false; + } + if (XGetSelectionOwner(x_display, clipboard_prop) == None) { + LOG_INFO("No owner for the selection"); + return false; + } + if (clipboard_request_type) { + LOG_INFO("XConvertSelection request is already pending"); + return false; + } + clipboard_request_type = type; + XConvertSelection(x_display, clipboard_prop, format, clipboard_prop, platform_win, CurrentTime); return true; } -int32_t Platform::get_clipboard_data_size(uint32_t type) +void Platform::release_clipboard() { - //FIXME: check type - return clipboard_data_size; + XSetSelectionOwner(x_display, clipboard_prop, None, CurrentTime); } - -- cgit