[Spice-devel] [PATCH] vd_agent: support clipboard/selection-owner model

Arnon Gilboa agilboa at redhat.com
Tue Sep 21 10:16:50 PDT 2010


-enable the clipboard support
-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)
-bonus (no cost): support image cut & paste, currently only with win client
---
 vdagent/vdagent.cpp |  323 ++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 253 insertions(+), 70 deletions(-)

diff --git a/vdagent/vdagent.cpp b/vdagent/vdagent.cpp
index 8ef1a68..5ff595f 100644
--- a/vdagent/vdagent.cpp
+++ b/vdagent/vdagent.cpp
@@ -20,12 +20,21 @@
 #include "display_setting.h"
 #include <lmcons.h>
 
-//#define CLIPBOARD_ENABLED
-
 #define VD_AGENT_LOG_PATH       TEXT("%svdagent.log")
 #define VD_AGENT_WINCLASS_NAME  TEXT("VDAGENT")
 #define VD_INPUT_INTERVAL_MS    20
 #define VD_TIMER_ID             1
+#define VD_CLIPBOARD_TIMEOUT_MS 10000
+
+typedef struct VDClipboardFormat {
+    uint32_t format;
+    uint32_t type;
+} VDClipboardFormat;
+
+VDClipboardFormat supported_clipboard_formats[] = {
+    {CF_UNICODETEXT, VD_AGENT_CLIPBOARD_UTF8_TEXT},
+    {CF_DIB, VD_AGENT_CLIPBOARD_BITMAP},
+    {0, 0}};
 
 class VDAgent {
 public:
@@ -37,13 +46,17 @@ private:
     VDAgent();
     void input_desktop_message_loop();
     bool handle_mouse_event(VDAgentMouseState* state);
-    bool handle_announce_capabilities(
-        VDAgentAnnounceCapabilities* announce_capabilities, uint32_t msg_size);
+    bool handle_announce_capabilities(VDAgentAnnounceCapabilities* announce_capabilities,
+                                      uint32_t msg_size);
     bool handle_mon_config(VDAgentMonitorsConfig* mon_config, uint32_t port);
     bool handle_clipboard(VDAgentClipboard* clipboard, uint32_t size);
+    bool handle_clipboard_grab(VDAgentClipboardGrab* clipboard_grab);
+    bool handle_clipboard_request(VDAgentClipboardRequest* clipboard_request);
+    bool handle_clipboard_release();
     bool handle_display_config(VDAgentDisplayConfig* display_config, uint32_t port);
     bool handle_control(VDPipeMessage* msg);
     bool on_clipboard_change();
+    bool on_clipboard_render(UINT format);
     DWORD get_buttons_change(DWORD last_buttons_state, DWORD new_buttons_state,
                              DWORD mask, DWORD down_flag, DWORD up_flag);
     static LRESULT CALLBACK wnd_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);
@@ -51,14 +64,18 @@ private:
     static VOID CALLBACK write_completion(DWORD err, DWORD bytes, LPOVERLAPPED overlap);
     static DWORD WINAPI event_thread_proc(LPVOID param);
     static void dispatch_message(VDAgentMessage* msg, uint32_t port);
+    static uint32_t get_clipboard_format(uint32_t type);
+    static uint32_t get_clipboard_type(uint32_t format);
     uint8_t* write_lock(DWORD bytes = 0);
     void write_unlock(DWORD bytes = 0);
+    bool write_message(uint32_t type, uint32_t size, void* data);
     bool write_clipboard();
     bool connect_pipe();
     bool send_input();
     void set_display_depth(uint32_t depth);
     void load_display_setting();
     bool send_announce_capabilities(bool request);
+    void cleanup();
 
 private:
     static VDAgent* _singleton;
@@ -71,6 +88,7 @@ private:
     INPUT _input;
     DWORD _input_time;
     HANDLE _desktop_switch_event;
+    HANDLE _clipboard_event;
     VDAgentMessage* _in_msg;
     uint32_t _in_msg_pos;
     VDAgentMessage* _out_msg;
@@ -107,11 +125,13 @@ VDAgent* VDAgent::get()
 VDAgent::VDAgent()
     : _hwnd (NULL)
     , _hwnd_next_viewer (NULL)
-    , _clipboard_changer (false)
+    , _clipboard_changer (true)
     , _buttons_state (0)
     , _mouse_x (0)
     , _mouse_y (0)
     , _input_time (0)
+    , _desktop_switch_event (NULL)
+    , _clipboard_event (NULL)
     , _in_msg (NULL)
     , _in_msg_pos (0)
     , _out_msg (NULL)
@@ -189,8 +209,10 @@ bool VDAgent::run()
         vd_printf("SetProcessShutdownParameters failed %u", GetLastError());
     }
     _desktop_switch_event = CreateEvent(NULL, FALSE, FALSE, NULL);
-    if (!_desktop_switch_event) {
+    _clipboard_event = CreateEvent(NULL, FALSE, FALSE, NULL);
+    if (!_desktop_switch_event || !_clipboard_event) {
         vd_printf("CreateEvent() failed: %d", GetLastError());
+        cleanup();
         return false;
     }
     memset(&wcls, 0, sizeof(wcls));
@@ -198,6 +220,7 @@ bool VDAgent::run()
     wcls.lpszClassName = VD_AGENT_WINCLASS_NAME;
     if (!RegisterClass(&wcls)) {
         vd_printf("RegisterClass() failed: %d", GetLastError());
+        cleanup();
         return false;
     }
     _desktop_layout = new DesktopLayout();
@@ -205,8 +228,7 @@ bool VDAgent::run()
         vd_printf("No QXL devices!");
     }
     if (!connect_pipe()) {
-        CloseHandle(_desktop_switch_event);
-        delete _desktop_layout;
+        cleanup();
         return false;
     }
     _running = true;
@@ -214,9 +236,7 @@ bool VDAgent::run()
         &event_thread_id);
     if (!event_thread) {
         vd_printf("CreateThread() failed: %d", GetLastError());
-        CloseHandle(_desktop_switch_event);
-        CloseHandle(_pipe_state.pipe);
-        delete _desktop_layout;
+        cleanup();
         return false;
     }
     send_announce_capabilities(true);
@@ -226,10 +246,16 @@ bool VDAgent::run()
     }
     vd_printf("Agent stopped");
     CloseHandle(event_thread);
+    cleanup();
+    return true;
+}
+
+void VDAgent::cleanup()
+{
     CloseHandle(_desktop_switch_event);
+    CloseHandle(_clipboard_event);
     CloseHandle(_pipe_state.pipe);
     delete _desktop_layout;
-    return true;
 }
 
 void VDAgent::input_desktop_message_loop()
@@ -466,44 +492,66 @@ bool VDAgent::handle_mon_config(VDAgentMonitorsConfig* mon_config, uint32_t port
     return true;
 }
 
-//FIXME: currently supports text only
 bool VDAgent::handle_clipboard(VDAgentClipboard* clipboard, uint32_t size)
 {
     HGLOBAL clip_data;
     LPVOID clip_buf;
     int clip_size;
+    int clip_len;
     UINT format;
-    bool ret;
+    bool ret = false;
 
-    switch (clipboard->type) {
-    case VD_AGENT_CLIPBOARD_UTF8_TEXT:
-        format = CF_UNICODETEXT;
+    // Get the required clipboard size
+    switch (format = get_clipboard_format(clipboard->type)) {
+    case CF_UNICODETEXT:
+        // Received utf8 string is not null-terminated   
+        if (!(clip_len = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)clipboard->data, size, NULL, 0))) {
+            return false;
+        }
+        clip_len++;
+        clip_size = clip_len * sizeof(WCHAR);
+        break;
+    case CF_DIB:
+        clip_size = size;
         break;
     default:
         vd_printf("Unsupported clipboard type %u", clipboard->type);
         return false;
     }
-    if (!OpenClipboard(_hwnd)) {
-        return false;
-    }
-    clip_size = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)clipboard->data, -1, 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)clipboard->data, -1, (LPWSTR)clip_buf, clip_size);
+    // Translate data and set clipboard content
+    switch (format) {
+    case CF_UNICODETEXT:
+        ret = !!MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)clipboard->data, size, (LPWSTR)clip_buf,
+                                    clip_len);
+        ((LPWSTR)clip_buf)[clip_len - 1] = L'\0';
+        break;
+    case CF_DIB:
+        memcpy(clip_buf, clipboard->data, size);
+        ret = true;
+        break;
+    }
     GlobalUnlock(clip_data);
-    if (ret) {
-        EmptyClipboard();
-        //FIXME: nicify (compare clip_data)
-        _clipboard_changer = true;
-        ret = !!SetClipboardData(format, clip_data);
+    if (!ret) {
+        return false;
     }
+    if (SetClipboardData(format, clip_data)) {
+        SetEvent(_clipboard_event);
+        return true;
+    }
+    // We retry clipboard open-empty-set-close only when there is a timeout in on_clipboard_render()
+    if (!OpenClipboard(_hwnd)) {
+        return false;
+    }
+    EmptyClipboard();
+    ret = !!SetClipboardData(format, clip_data);
     CloseClipboard();
     return ret;
 }
@@ -526,7 +574,6 @@ void VDAgent::set_display_depth(uint32_t depth)
     }
 }
 
-
 void VDAgent::load_display_setting()
 {
     _display_setting.load();
@@ -562,9 +609,7 @@ bool VDAgent::send_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);
-#ifdef CLIPBOARD_ENABLED
     VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD);
-#endif
     VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_DISPLAY_CONFIG);
     vd_printf("sending capabilities:");
     for (uint32_t i = 0 ; i < caps_size; ++i) {
@@ -694,7 +739,6 @@ bool VDAgent::handle_control(VDPipeMessage* msg)
 
 #define MIN(a, b) ((a) > (b) ? (b) : (a))
 
-//FIXME: cleanup
 //FIXME: division to max size chunks should NOT be here, but in the service
 //       here we should write the max possible size to the pipe
 bool VDAgent::write_clipboard()
@@ -706,8 +750,7 @@ bool VDAgent::write_clipboard()
         return false;
     }
     pipe_msg->type = VD_AGENT_COMMAND;
-    //FIXME: client port should be in vd_agent.h
-    pipe_msg->opaque = 1;
+    pipe_msg->opaque = VDP_CLIENT_PORT;
     pipe_msg->size = n - sizeof(VDPipeMessage);
     memcpy(pipe_msg->data, (char*)_out_msg + _out_msg_pos, n - sizeof(VDPipeMessage));
     write_unlock(n);
@@ -724,14 +767,102 @@ bool VDAgent::write_clipboard()
     return true;
 }
 
-//FIXME: currently supports text only
+bool VDAgent::write_message(uint32_t type, uint32_t size = 0, void* data = NULL)
+{
+    VDPipeMessage* pipe_msg;
+    VDAgentMessage* msg;
+
+    pipe_msg = (VDPipeMessage*)write_lock(VD_MESSAGE_HEADER_SIZE + size);
+    if (!pipe_msg) {
+        return false;
+    }
+    pipe_msg->type = VD_AGENT_COMMAND;
+    pipe_msg->opaque = VDP_CLIENT_PORT;
+    pipe_msg->size = sizeof(VDAgentMessage) + size;
+    msg = (VDAgentMessage*)pipe_msg->data;
+    msg->protocol = VD_AGENT_PROTOCOL;
+    msg->type = type;
+    msg->opaque = 0;
+    msg->size = size;
+    if (size && data) {
+        memcpy(msg->data, data, size);
+    }
+    write_unlock(VD_MESSAGE_HEADER_SIZE + size);
+    if (!_pending_write) {
+        write_completion(0, 0, &_pipe_state.write.overlap);
+    }
+    return true;
+}
+
 bool VDAgent::on_clipboard_change()
 {
-    UINT format = CF_UNICODETEXT;
+    uint32_t type = 0;
+
+    for (VDClipboardFormat* iter = supported_clipboard_formats; iter->format && !type; iter++) {
+        if (IsClipboardFormatAvailable(iter->format)) {
+            type = iter->type;
+        }
+    }
+    if (!type) {
+        vd_printf("Unsupported clipboard format");
+        return false;
+    }
+    VDAgentClipboardGrab grab = {type};
+    return write_message(VD_AGENT_CLIPBOARD_GRAB, sizeof(grab), &grab);  
+}
+
+// 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.
+bool VDAgent::on_clipboard_render(UINT format)
+{
+    uint32_t type = get_clipboard_type(format);
+
+    if (!type) {
+        vd_printf("Unsupported clipboard format %u", format);
+        return false;
+    }
+    VDAgentClipboardRequest request = {type};
+    if (!write_message(VD_AGENT_CLIPBOARD_REQUEST, sizeof(request), &request)) {
+        return false;
+    }
+    DWORD start_tick = GetTickCount();
+    while (WaitForSingleObjectEx(_clipboard_event, 1000, TRUE) != WAIT_OBJECT_0 &&
+           GetTickCount() < start_tick + VD_CLIPBOARD_TIMEOUT_MS);
+    return true;
+}
+
+bool VDAgent::handle_clipboard_grab(VDAgentClipboardGrab* clipboard_grab)
+{
+    uint32_t format = get_clipboard_format(clipboard_grab->type);
+
+    if (!format) {
+        vd_printf("Unsupported clipboard type %u", clipboard_grab->type);
+        return false;
+    }
+    if (!OpenClipboard(_hwnd)) {
+        return false;
+    }
+    _clipboard_changer = true;
+    EmptyClipboard();
+    SetClipboardData(format, NULL);
+    CloseClipboard();
+    return true;
+}
+
+bool VDAgent::handle_clipboard_request(VDAgentClipboardRequest* clipboard_request)
+{
+    UINT format;
     HANDLE clip_data;
     LPVOID clip_buf;
     int clip_size;
+    size_t len;
 
+    if (!(format = get_clipboard_format(clipboard_request->type))) {
+        vd_printf("Unsupported clipboard type %u", clipboard_request->type);
+        return false;
+    }
     if (_out_msg) {
         vd_printf("clipboard change is already pending");
         return false;
@@ -743,7 +874,16 @@ bool VDAgent::on_clipboard_change()
         CloseClipboard();
         return false;
     }
-    clip_size = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)clip_buf, -1, NULL, 0, NULL, NULL);
+    switch (format) {
+    case CF_UNICODETEXT:
+        len = wcslen((wchar_t*)clip_buf);
+        clip_size = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)clip_buf, (int)len, NULL, 0, NULL, NULL);
+        break;
+    case CF_DIB:
+        clip_size = (int)GlobalSize(clip_data);
+        break;
+    }
+
     if (!clip_size) {
         GlobalUnlock(clip_data);
         CloseClipboard();
@@ -757,11 +897,48 @@ bool VDAgent::on_clipboard_change()
     _out_msg->opaque = 0;
     _out_msg->size = (uint32_t)(sizeof(VDAgentClipboard) + clip_size);
     VDAgentClipboard* clipboard = (VDAgentClipboard*)_out_msg->data;
-    clipboard->type = VD_AGENT_CLIPBOARD_UTF8_TEXT;
-    WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)clip_buf, -1, (LPSTR)clipboard->data, clip_size, NULL, NULL);
+    clipboard->type = clipboard_request->type;
+
+    switch (format) {
+    case CF_UNICODETEXT:
+        WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)clip_buf, (int)len, (LPSTR)clipboard->data,
+                            clip_size, NULL, NULL);
+        break;
+    case CF_DIB:
+        memcpy(clipboard->data, clip_buf, clip_size);
+        break;
+    }
+
     GlobalUnlock(clip_data);
     CloseClipboard();
-    return write_clipboard();
+    write_clipboard();
+    return true;
+}
+
+bool VDAgent::handle_clipboard_release()
+{
+    SetEvent(_clipboard_event);
+    return true;
+}
+
+uint32_t VDAgent::get_clipboard_format(uint32_t type)
+{
+    for (VDClipboardFormat* iter = supported_clipboard_formats; iter->format && iter->type; iter++) {
+        if (iter->type == type) {
+            return iter->format;
+        }
+    }
+    return 0;
+}
+
+uint32_t VDAgent::get_clipboard_type(uint32_t format)
+{
+    for (VDClipboardFormat* iter = supported_clipboard_formats; iter->format && iter->type; iter++) {
+        if (iter->format == format) {
+            return iter->type;
+        }
+    }
+    return 0;
 }
 
 bool VDAgent::connect_pipe()
@@ -791,48 +968,48 @@ bool VDAgent::connect_pipe()
     vd_printf("Connected to service pipe");
     return true;
 }
+
 void VDAgent::dispatch_message(VDAgentMessage* msg, uint32_t port)
 {
     VDAgent* a = _singleton;
+    bool res = true;
 
     switch (msg->type) {
     case VD_AGENT_MOUSE_STATE:
-        if (!a->handle_mouse_event((VDAgentMouseState*)msg->data)) {
-            vd_printf("handle_mouse_event failed: %u", GetLastError());
-            a->_running = false;
-        }
+        res = a->handle_mouse_event((VDAgentMouseState*)msg->data);
         break;
     case VD_AGENT_MONITORS_CONFIG:
-        if (!a->handle_mon_config((VDAgentMonitorsConfig*)msg->data, port)) {
-            vd_printf("handle_mon_config failed: %u", GetLastError());
-            a->_running = false;
-        }
+        res = a->handle_mon_config((VDAgentMonitorsConfig*)msg->data, port);
         break;
-#ifdef CLIPBOARD_ENABLED
     case VD_AGENT_CLIPBOARD:
-        if (!a->handle_clipboard((VDAgentClipboard*)msg->data,
-                                 msg->size - sizeof(VDAgentClipboard))) {
-            vd_printf("handle_clipboard failed: %u", GetLastError());
-            a->_running = false;
+        res = a->handle_clipboard((VDAgentClipboard*)msg->data,
+                                  msg->size - sizeof(VDAgentClipboard));
+        break;
+    case VD_AGENT_CLIPBOARD_GRAB:
+        res = a->handle_clipboard_grab((VDAgentClipboardGrab*)msg->data);
+        break;
+    case VD_AGENT_CLIPBOARD_REQUEST:
+        res = a->handle_clipboard_request((VDAgentClipboardRequest*)msg->data);
+        if (!res) {
+            res = a->write_message(VD_AGENT_CLIPBOARD_RELEASE);
         }
         break;
-#endif // CLIPBOARD_ENABLED
+    case VD_AGENT_CLIPBOARD_RELEASE:
+        res = a->handle_clipboard_release();
+        break;
     case VD_AGENT_DISPLAY_CONFIG:
-        if (!a->handle_display_config((VDAgentDisplayConfig*)msg->data, port)) {
-            vd_printf("handle_display_config failed");
-            a->_running = false;
-        }
+        res = a->handle_display_config((VDAgentDisplayConfig*)msg->data, port);
         break;
     case VD_AGENT_ANNOUNCE_CAPABILITIES:
-        if (!a->handle_announce_capabilities((VDAgentAnnounceCapabilities*)msg->data,
-                msg->size)) {
-            vd_printf("handle_announce_capabilities failed");
-            a->_running = false;
-        }
+        res = a->handle_announce_capabilities((VDAgentAnnounceCapabilities*)msg->data, msg->size);
         break;
     default:
         vd_printf("Unsupported message type %u size %u", msg->type, msg->size);
     }
+    if (!res) {
+        vd_printf("handling message type %u failed: %u", msg->type, GetLastError());
+        a->_running = false;
+    }
 }
 
 VOID CALLBACK VDAgent::read_completion(DWORD err, DWORD bytes, LPOVERLAPPED overlap)
@@ -862,8 +1039,8 @@ VOID CALLBACK VDAgent::read_completion(DWORD err, DWORD bytes, LPOVERLAPPED over
             break;
         }
 
-        //FIXME: cleanup, specific to one port
-        if (a->_in_msg_pos == 0 || pipe_msg->opaque == 2 /*FIXME!*/) {
+        //FIXME: currently assumes that multi-part msg arrives only from client port
+        if (a->_in_msg_pos == 0 || pipe_msg->opaque == VDP_SERVER_PORT) {
             if (len < VD_MESSAGE_HEADER_SIZE) {
                 break;
             }
@@ -885,7 +1062,6 @@ VOID CALLBACK VDAgent::read_completion(DWORD err, DWORD bytes, LPOVERLAPPED over
         } else {
             memcpy((uint8_t*)a->_in_msg + a->_in_msg_pos, pipe_msg->data, pipe_msg->size);
             a->_in_msg_pos += pipe_msg->size;
-            //vd_printf("DEBUG: pipe_msg size %u pos %u total %u", pipe_msg->size, a->_in_msg_pos, sizeof(VDAgentMessage) + a->_in_msg->size);
             if (a->_in_msg_pos == sizeof(VDAgentMessage) + a->_in_msg->size) {
                 dispatch_message(a->_in_msg, 0);
                 a->_in_msg_pos = 0;
@@ -970,7 +1146,6 @@ LRESULT CALLBACK VDAgent::wnd_proc(HWND hwnd, UINT message, WPARAM wparam, LPARA
     case WM_TIMER:
         a->send_input();
         break;
-#ifdef CLIPBOARD_ENABLED
     case WM_CHANGECBCHAIN:
         if (a->_hwnd_next_viewer == (HWND)wparam) {
             a->_hwnd_next_viewer = (HWND)lparam;
@@ -986,7 +1161,15 @@ LRESULT CALLBACK VDAgent::wnd_proc(HWND hwnd, UINT message, WPARAM wparam, LPARA
         }
         SendMessage(a->_hwnd_next_viewer, message, wparam, lparam);
         break;
-#endif // CLIPBOARD_ENABLED
+    case WM_RENDERFORMAT:
+        a->on_clipboard_render((UINT)wparam);
+        break;
+    case WM_RENDERALLFORMATS:
+        vd_printf("WM_RENDERALLFORMATS");
+        break;
+    case WM_DESTROYCLIPBOARD:
+        vd_printf("WM_DESTROYCLIPBOARD");
+        break;
     default:
         return DefWindowProc(hwnd, message, wparam, lparam);
     }
-- 
1.5.5.6



More information about the Spice-devel mailing list