[Spice-devel] [PATCH] spice: vdagent: add basic clipboard support

Arnon Gilboa agilboa at redhat.com
Mon Aug 9 02:58:39 PDT 2010


From: Arnon Gilboa <agilboa at agilboa.usersys.redhat.com>

-currently supports text only (UTF8)
-add VDAgent::dispatch_message()
-in VDAgent::read_completion() handle multi-chunk msgs
-fix chunk size bug in VDService::handle_pipe_data()
-add size to VDPipeMessage
---
 common/vdcommon.h       |    3 +-
 vdagent/vdagent.cpp     |  236 ++++++++++++++++++++++++++++++++++++++++++-----
 vdservice/vdi_port.cpp  |    5 +
 vdservice/vdi_port.h    |    1 +
 vdservice/vdservice.cpp |   39 +++++---
 5 files changed, 245 insertions(+), 39 deletions(-)

diff --git a/common/vdcommon.h b/common/vdcommon.h
index ba9d0ec..c807cbb 100644
--- a/common/vdcommon.h
+++ b/common/vdcommon.h
@@ -21,7 +21,7 @@
 #pragma warning(disable:4200)
 
 #include <windows.h>
-#include "vdagent.h"
+#include "vd_agent.h"
 #include "vdlog.h"
 
 #define VD_SERVICE_PIPE_NAME   TEXT("\\\\.\\pipe\\vdservicepipe")
@@ -38,6 +38,7 @@ enum {
 typedef __declspec (align(1)) struct VDPipeMessage {
     uint32_t type;
     uint32_t opaque;
+    uint32_t size;
     uint8_t data[0];
 } VDPipeMessage;
 
diff --git a/vdagent/vdagent.cpp b/vdagent/vdagent.cpp
index a578dfd..bf5f5f5 100644
--- a/vdagent/vdagent.cpp
+++ b/vdagent/vdagent.cpp
@@ -35,27 +35,38 @@ private:
     void input_desktop_message_loop();
     bool handle_mouse_event(VDAgentMouseState* state);
     bool handle_mon_config(VDAgentMonitorsConfig* mon_config, uint32_t port);
+    bool handle_clipboard(VDAgentClipboard* clipboard, uint32_t size);
     bool handle_control(VDPipeMessage* msg);
+    bool on_clipboard_change();
     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);
     static VOID CALLBACK read_completion(DWORD err, DWORD bytes, LPOVERLAPPED overlap);
     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);
     uint8_t* write_lock(DWORD bytes = 0);
     void write_unlock(DWORD bytes = 0);
+    bool write_clipboard();
     bool connect_pipe();
     bool send_input();
 
 private:
     static VDAgent* _singleton;
     HWND _hwnd;
+    HWND _hwnd_next_viewer;
+    bool _clipboard_changer;
     DWORD _buttons_state;
     LONG _mouse_x;
     LONG _mouse_y;
     INPUT _input;
     DWORD _input_time;
     HANDLE _desktop_switch_event;
+    VDAgentMessage* _in_msg;
+    uint32_t _in_msg_pos;
+    VDAgentMessage* _out_msg;
+    uint32_t _out_msg_pos;
+    uint32_t _out_msg_size;
     bool _pending_input;
     bool _pending_write;
     bool _running;
@@ -77,10 +88,17 @@ VDAgent* VDAgent::get()
 
 VDAgent::VDAgent()
     : _hwnd (NULL)
+    , _hwnd_next_viewer (NULL)
+    , _clipboard_changer (false)
     , _buttons_state (0)
     , _mouse_x (0)
     , _mouse_y (0)
     , _input_time (0)
+    , _in_msg (NULL)
+    , _in_msg_pos (0)
+    , _out_msg (NULL)
+    , _out_msg_pos (0)
+    , _out_msg_size (0)
     , _pending_input (false)
     , _pending_write (false)
     , _running (false)
@@ -219,6 +237,7 @@ void VDAgent::input_desktop_message_loop()
         _running = false;
         return;
     }
+    _hwnd_next_viewer = SetClipboardViewer(_hwnd);
     while (_running && !desktop_switch) {
         wait_ret = MsgWaitForMultipleObjectsEx(1, &_desktop_switch_event, INFINITE, QS_ALLINPUT,
                                                MWMO_ALERTABLE);
@@ -244,6 +263,7 @@ void VDAgent::input_desktop_message_loop()
         KillTimer(_hwnd, VD_TIMER_ID);
         _pending_input = false;
     }
+    ChangeClipboardChain(_hwnd, _hwnd_next_viewer);
     DestroyWindow(_hwnd);
     CloseDesktop(hdesk);
 }
@@ -386,6 +406,7 @@ bool VDAgent::handle_mon_config(VDAgentMonitorsConfig* mon_config, uint32_t port
     }
     reply_pipe_msg->type = VD_AGENT_COMMAND;
     reply_pipe_msg->opaque = port;
+    reply_pipe_msg->size = sizeof(VDAgentMessage) + sizeof(VDAgentReply);
     reply_msg = (VDAgentMessage*)reply_pipe_msg->data;
     reply_msg->protocol = VD_AGENT_PROTOCOL;
     reply_msg->type = VD_AGENT_REPLY;
@@ -401,6 +422,48 @@ 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;
+    UINT format;
+    bool ret;
+
+    switch (clipboard->type) {
+    case VD_AGENT_CLIPBOARD_UTF8_TEXT:
+        format = CF_UNICODETEXT;
+        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();
+        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);
+    GlobalUnlock(clip_data);  
+    if (ret) {
+        EmptyClipboard();
+        //FIXME: nicify (compare clip_data)
+        _clipboard_changer = true;
+        ret = !!SetClipboardData(format, clip_data);
+    }
+    CloseClipboard();
+    return ret;
+}
+
 bool VDAgent::handle_control(VDPipeMessage* msg)
 {
     switch (msg->type) {
@@ -429,6 +492,78 @@ bool VDAgent::handle_control(VDPipeMessage* msg)
     return true;
 }
 
+#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()
+{
+    ASSERT(_out_msg);
+    DWORD n = MIN(sizeof(VDPipeMessage) + _out_msg_size - _out_msg_pos, VD_AGENT_MAX_DATA_SIZE);
+    VDPipeMessage* pipe_msg = (VDPipeMessage*)write_lock(n);
+    if (!pipe_msg) {
+        return false;
+    }
+    pipe_msg->type = VD_AGENT_COMMAND;
+    //FIXME: client port should be in vd_agent.h
+    pipe_msg->opaque = 1;
+    pipe_msg->size = n - sizeof(VDPipeMessage);
+    memcpy(pipe_msg->data, (char*)_out_msg + _out_msg_pos, n - sizeof(VDPipeMessage));
+    write_unlock(n);
+    if (!_pending_write) {
+        write_completion(0, 0, &_pipe_state.write.overlap);
+    }
+    _out_msg_pos += (n - sizeof(VDPipeMessage));
+    if (_out_msg_pos == _out_msg_size) {
+        delete[] (uint8_t *)_out_msg;
+        _out_msg = NULL;
+        _out_msg_size = 0;
+        _out_msg_pos = 0;
+    }
+    return true;
+}
+
+//FIXME: currently supports text only
+bool VDAgent::on_clipboard_change()
+{
+    UINT format = CF_UNICODETEXT;
+    HANDLE clip_data;
+    LPVOID clip_buf;
+    int clip_size;
+
+    if (_out_msg) {
+        vd_printf("clipboard change is already pending");
+        return false;
+    }
+    if (!IsClipboardFormatAvailable(format) || !OpenClipboard(_hwnd)) {
+        return false;
+    }
+    if (!(clip_data = GetClipboardData(format)) || !(clip_buf = GlobalLock(clip_data))) {
+        CloseClipboard();
+        return false;
+    }
+    clip_size = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)clip_buf, -1, NULL, 0, NULL, NULL);
+    if (!clip_size) {
+        GlobalUnlock(clip_data);
+        CloseClipboard();
+        return false;
+    }
+    _out_msg_pos = 0;
+    _out_msg_size = sizeof(VDAgentMessage) + sizeof(VDAgentClipboard) + clip_size;
+    _out_msg = (VDAgentMessage*)new uint8_t[_out_msg_size];
+    _out_msg->protocol = VD_AGENT_PROTOCOL;
+    _out_msg->type = VD_AGENT_CLIPBOARD;
+    _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);
+    GlobalUnlock(clip_data);
+    CloseClipboard();
+    return write_clipboard();
+}
+
 bool VDAgent::connect_pipe()
 {
     VDAgent* a = _singleton;
@@ -456,6 +591,34 @@ bool VDAgent::connect_pipe()
     vd_printf("Connected to service pipe");
     return true;
 }
+void VDAgent::dispatch_message(VDAgentMessage* msg, uint32_t port)
+{
+    VDAgent* a = _singleton;
+
+    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;
+        }
+        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;
+        }
+        break;
+    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;
+        }
+        break;
+    default:
+        vd_printf("Unsupported message type %u size %u", msg->type, msg->size);
+    }
+}
 
 VOID CALLBACK VDAgent::read_completion(DWORD err, DWORD bytes, LPOVERLAPPED overlap)
 {
@@ -480,35 +643,42 @@ VOID CALLBACK VDAgent::read_completion(DWORD err, DWORD bytes, LPOVERLAPPED over
             ps->read.start += sizeof(VDPipeMessage);
             continue;
         }
-        if (len < VD_MESSAGE_HEADER_SIZE) {
-            break;
-        }
-        VDAgentMessage* msg = (VDAgentMessage*)pipe_msg->data;
-        if (len < VD_MESSAGE_HEADER_SIZE + msg->size) {
+        if (len < sizeof(VDPipeMessage) + pipe_msg->size) {
             break;
         }
-        if (msg->protocol != VD_AGENT_PROTOCOL) {
-            vd_printf("Invalid protocol %d", msg->protocol);
-            a->_running = false;
-            break;
-        }
-        switch (msg->type) {
-        case VD_AGENT_MOUSE_STATE:
-            if (!a->handle_mouse_event((VDAgentMouseState*)msg->data)) {
-                vd_printf("handle_mouse_event failed: %d", GetLastError());
-                a->_running = false;
+
+        //FIXME: cleanup, specific to one port
+        if (a->_in_msg_pos == 0 || pipe_msg->opaque == 2 /*FIXME!*/) {
+            if (len < VD_MESSAGE_HEADER_SIZE) {
+                break;
             }
-            break;
-        case VD_AGENT_MONITORS_CONFIG:
-            if (!a->handle_mon_config((VDAgentMonitorsConfig*)msg->data, pipe_msg->opaque)) {
-                vd_printf("handle_mon_config failed: %d", GetLastError());
+            VDAgentMessage* msg = (VDAgentMessage*)pipe_msg->data;
+            if (msg->protocol != VD_AGENT_PROTOCOL) {
+                vd_printf("Invalid protocol %u bytes %u", msg->protocol, bytes);
                 a->_running = false;
+                break;
+            }
+            uint32_t msg_size = sizeof(VDAgentMessage) + msg->size;
+            if (pipe_msg->size == msg_size) {
+                dispatch_message(msg, pipe_msg->opaque);
+            } else {
+                ASSERT(pipe_msg->size < msg_size);
+                a->_in_msg = (VDAgentMessage*)new uint8_t[msg_size];
+                memcpy(a->_in_msg, pipe_msg->data, pipe_msg->size);
+                a->_in_msg_pos = pipe_msg->size;
+            }
+        } else {
+            memcpy((uint8_t*)a->_in_msg + a->_in_msg_pos, pipe_msg->data, pipe_msg->size);
+            a->_in_msg_pos += pipe_msg->size;
+            if (a->_in_msg_pos == sizeof(VDAgentMessage) + a->_in_msg->size) {
+                dispatch_message(a->_in_msg, 0);
+                a->_in_msg_pos = 0;
+                delete[] (uint8_t *)a->_in_msg;
+                a->_in_msg = NULL;
             }
-            break;
-        default:
-            vd_printf("Unsupported message type %d size %d", msg->type, msg->size);
         }
-        ps->read.start += (VD_MESSAGE_HEADER_SIZE + msg->size);
+
+        ps->read.start += (sizeof(VDPipeMessage) + pipe_msg->size);
         if (ps->read.start == ps->read.end) {
             ps->read.start = ps->read.end = 0;
         }
@@ -542,6 +712,8 @@ VOID CALLBACK VDAgent::write_completion(DWORD err, DWORD bytes, LPOVERLAPPED ove
     ps->write.start += bytes;
     if (ps->write.start == ps->write.end) {
         ps->write.start = ps->write.end = 0;
+        //DEBUG
+        while (a->_out_msg && a->write_clipboard());
     } else if (WriteFileEx(ps->pipe, ps->write.data + ps->write.start,
                            ps->write.end - ps->write.start, overlap, write_completion)) {
         a->_pending_write = true;
@@ -554,10 +726,11 @@ VOID CALLBACK VDAgent::write_completion(DWORD err, DWORD bytes, LPOVERLAPPED ove
 
 uint8_t* VDAgent::write_lock(DWORD bytes)
 {
+    MUTEX_LOCK(_write_mutex);
     if (_pipe_state.write.end + bytes <= sizeof(_pipe_state.write.data)) {
-        MUTEX_LOCK(_write_mutex);
         return &_pipe_state.write.data[_pipe_state.write.end];
     } else {
+        MUTEX_UNLOCK(_write_mutex);
         vd_printf("write buffer is full");
         return NULL;
     }
@@ -581,6 +754,21 @@ LRESULT CALLBACK VDAgent::wnd_proc(HWND hwnd, UINT message, WPARAM wparam, LPARA
     case WM_TIMER:
         a->send_input();
         break;
+    case WM_CHANGECBCHAIN:
+        if (a->_hwnd_next_viewer == (HWND)wparam) {
+            a->_hwnd_next_viewer = (HWND)lparam;
+        } else if (a->_hwnd_next_viewer) {
+            SendMessage(a->_hwnd_next_viewer, message, wparam, lparam);
+        }
+        break;
+    case WM_DRAWCLIPBOARD:
+        if (!a->_clipboard_changer) {
+            a->on_clipboard_change();
+        } else {
+            a->_clipboard_changer = false;
+        }
+        SendMessage(a->_hwnd_next_viewer, message, wparam, lparam);
+        break;
     default:
         return DefWindowProc(hwnd, message, wparam, lparam);
     }
diff --git a/vdservice/vdi_port.cpp b/vdservice/vdi_port.cpp
index 4c0a99d..2de53b0 100644
--- a/vdservice/vdi_port.cpp
+++ b/vdservice/vdi_port.cpp
@@ -78,6 +78,11 @@ bool VDIPort::init()
     return true;
 }
 
+size_t VDIPort::write_ring_free_space()
+{
+    return (BUF_SIZE + _write_start - _write_end - 1) % BUF_SIZE;
+}
+
 size_t VDIPort::ring_write(const void* buf, size_t size)
 {
     size_t free_size = (BUF_SIZE + _write_start - _write_end - 1) % BUF_SIZE;
diff --git a/vdservice/vdi_port.h b/vdservice/vdi_port.h
index 3af3f18..2fbfb19 100644
--- a/vdservice/vdi_port.h
+++ b/vdservice/vdi_port.h
@@ -37,6 +37,7 @@ public:
     ~VDIPort();
     bool init();
     size_t ring_write(const void* buf, size_t size);
+    size_t write_ring_free_space();
     size_t ring_read(void* buf, size_t size);
     size_t read_ring_size();
     int write();
diff --git a/vdservice/vdservice.cpp b/vdservice/vdservice.cpp
index 8139af4..7c6f976 100644
--- a/vdservice/vdservice.cpp
+++ b/vdservice/vdservice.cpp
@@ -414,8 +414,10 @@ bool VDService::execute()
         if (cont_read >= 0 && cont_write >= 0) {
             cont = cont_read || cont_write;
         } else if (cont_read == VDI_PORT_ERROR || cont_write == VDI_PORT_ERROR) {
+            vd_printf("VDI Port error, read %d write %d", cont_read, cont_write);
             _running = false;
         } else if (cont_read == VDI_PORT_RESET || cont_write == VDI_PORT_RESET) {
+            vd_printf("VDI Port reset, read %d write %d", cont_read, cont_write);
             _chunk_size = _chunk_port = 0;
             write_agent_control(VD_AGENT_RESET, ++_connection_id);
             _pending_reset = true;
@@ -423,6 +425,9 @@ bool VDService::execute()
         if (cont) {
             handle_port_data();
         }
+        if (cont_write) {
+            handle_pipe_data(0);
+        }
         if (_running && (!cont || _pending_read || _pending_write)) {
             DWORD events_count = _events[VD_EVENTS_COUNT - 1] ? VD_EVENTS_COUNT :
                                                                 VD_EVENTS_COUNT - 1;
@@ -834,6 +839,7 @@ bool VDService::restart_agent(bool normal_restart)
 
 void VDService::stop()
 {
+    vd_printf("Service stopped");
     _running = false;
     if (_control_event && !SetEvent(_control_event)) {
         vd_printf("SetEvent() failed: %u", GetLastError());
@@ -892,12 +898,15 @@ void VDService::read_pipe()
     }
 }
 
+//FIXME: division to max size chunks should be here, not in the agent
 void VDService::handle_pipe_data(DWORD bytes)
 {
     VDPipeState* ps = &_pipe_state;
     DWORD read_size;
 
-    _pending_read = false;
+    if (bytes) {
+        _pending_read = false;
+    }
     if (!_running) {
         return;
     }
@@ -909,28 +918,28 @@ void VDService::handle_pipe_data(DWORD bytes)
             ps->read.start += sizeof(VDPipeMessage);
             continue;
         }
-        if (read_size < VD_MESSAGE_HEADER_SIZE) {
-            break;
-        }
-        VDAgentMessage* msg = (VDAgentMessage*)pipe_msg->data;
-        DWORD chunk_size = sizeof(VDAgentMessage) + msg->size;
-        VDAgentDataChunk chunk;
-        if (read_size < VD_MESSAGE_HEADER_SIZE + msg->size) {
+        if (read_size < sizeof(VDPipeMessage) + pipe_msg->size ||
+                _vdi_port->write_ring_free_space() < sizeof(VDAgentDataChunk) + pipe_msg->size) {
             break;
         }
         if (!_pending_reset) {
+            VDAgentDataChunk chunk;
             chunk.port = pipe_msg->opaque;
-            chunk.size = chunk_size;
+            chunk.size = pipe_msg->size;
             if (_vdi_port->ring_write(&chunk, sizeof(chunk)) != sizeof(chunk) ||
-                            _vdi_port->ring_write(msg, chunk_size) != chunk_size) {
+                    _vdi_port->ring_write(pipe_msg->data, chunk.size) != chunk.size) {
                 vd_printf("ring_write failed");
                 _running = false;
                 return;
             }
         }
-        ps->read.start += (VD_MESSAGE_HEADER_SIZE + msg->size);
-        if (ps->read.start == ps->read.end) {
-            ps->read.start = ps->read.end = 0;
+        ps->read.start += (sizeof(VDPipeMessage) + pipe_msg->size);
+    }
+    if (ps->read.start == ps->read.end && !_pending_read) {
+        DWORD prev_read_end = ps->read.end;
+        ps->read.start = ps->read.end = 0;
+        if (prev_read_end == sizeof(ps->read.data)) {
+            read_pipe();
         }
     }
 }
@@ -940,7 +949,7 @@ void VDService::handle_port_data()
     VDPipeMessage* pipe_msg;
     VDAgentDataChunk chunk;
     int chunks_count = 0;
-    DWORD count;
+    DWORD count = 0;
 
     while (_running) {
         if (!_chunk_size && _vdi_port->read_ring_size() >= sizeof(chunk)) {
@@ -962,6 +971,7 @@ void VDService::handle_port_data()
             _chunk_port = chunk.port;
         }
         if (_chunk_size && _vdi_port->read_ring_size() >= _chunk_size) {
+            count = sizeof(VDPipeMessage) + _chunk_size;
             ASSERT(_pipe_state.write.end + count <= sizeof(_pipe_state.write.data));
             pipe_msg = (VDPipeMessage*)&_pipe_state.write.data[_pipe_state.write.end];
             if (_vdi_port->ring_read(pipe_msg->data, _chunk_size) != _chunk_size) {
@@ -972,6 +982,7 @@ void VDService::handle_port_data()
             if (_pipe_connected) {
                 pipe_msg->type = VD_AGENT_COMMAND;
                 pipe_msg->opaque = _chunk_port;
+                pipe_msg->size = _chunk_size;
                 _pipe_state.write.end += count;
                 chunks_count++;
             } else {
-- 
1.5.5.6



More information about the Spice-devel mailing list