[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