diff options
author | Arnon Gilboa <agilboa@redhat.com> | 2010-07-19 10:29:47 +0300 |
---|---|---|
committer | Alon Levy <alevy@redhat.com> | 2010-07-19 10:30:19 +0300 |
commit | ce03f5449d68b2f0201dce409a81280300120069 (patch) | |
tree | 62f0877b9fd7f695253aa26f0ed046e873efbe66 | |
parent | 4f8545ed628fbb89a893297c5fdf276511284b33 (diff) |
client: add clipboard support
* windows - untested
* linux - small strings both ways, large implemented differently:
* client to guest - support INCR
* guest to client - we supply a single possibly very large property
* requires server changes in next patch to work with spice-vmc
-rw-r--r-- | client/application.cpp | 6 | ||||
-rw-r--r-- | client/application.h | 2 | ||||
-rw-r--r-- | client/platform.h | 17 | ||||
-rw-r--r-- | client/red_client.cpp | 91 | ||||
-rw-r--r-- | client/red_client.h | 7 | ||||
-rw-r--r-- | client/windows/platform.cpp | 90 | ||||
-rw-r--r-- | client/x11/platform.cpp | 274 |
7 files changed, 476 insertions, 11 deletions
diff --git a/client/application.cpp b/client/application.cpp index 14ac743..e986475 100644 --- a/client/application.cpp +++ b/client/application.cpp @@ -1325,6 +1325,12 @@ 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 36ae86e..dde37fc 100644 --- a/client/application.h +++ b/client/application.h @@ -141,6 +141,7 @@ typedef std::list<GUIBarrier*> GUIBarriers; class Application : public ProcessLoop, public Platform::EventListener, public Platform::DisplayModeListener, + public Platform::ClipboardListener, public CommandTarget { public: @@ -190,6 +191,7 @@ 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 d2fdd48..6288380 100644 --- a/client/platform.h +++ b/client/platform.h @@ -117,6 +117,17 @@ public: class DisplayModeListener; static void set_display_mode_listner(DisplayModeListener* listener); + + class ClipboardListener; + static void set_clipboard_listener(ClipboardListener* listener); + + enum { + CLIPBOARD_UTF8_TEXT = 1, + }; + + 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); }; class Platform::EventListener { @@ -127,6 +138,12 @@ public: virtual void on_monitors_change() = 0; }; +class Platform::ClipboardListener { +public: + virtual ~ClipboardListener() {} + virtual void on_clipboard_change() = 0; +}; + class Platform::RecordClient { public: virtual ~RecordClient() {} diff --git a/client/red_client.cpp b/client/red_client.cpp index 0268e75..3f4f8bf 100644 --- a/client/red_client.cpp +++ b/client/red_client.cpp @@ -320,6 +320,9 @@ RedClient::RedClient(Application& application) , _agent_msg (new VDAgentMessage) , _agent_msg_data (NULL) , _agent_msg_pos (0) + , _agent_out_msg (NULL) + , _agent_out_msg_size (0) + , _agent_out_msg_pos (0) , _agent_tokens (0) , _agent_timer (new AgentTimer()) , _migrate (*this) @@ -736,6 +739,73 @@ 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::post_agent_clipboard() +{ + uint32_t size; + + while (_agent_tokens && + (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); + memcpy(data, (uint8_t*)_agent_out_msg + _agent_out_msg_pos, size); + _agent_tokens--; + post_message(message); + _agent_out_msg_pos += size; + if (_agent_out_msg_pos == _agent_out_msg_size) { + delete[] (uint8_t *)_agent_out_msg; + _agent_out_msg = NULL; + _agent_out_msg_size = 0; + _agent_out_msg_pos = 0; + } + } +} + +//FIXME: currently supports text only; better name - poll_clipboard? +void RedClient::on_clipboard_change() +{ + //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 (!clip_size || !_agent_connected) { + return; + } + if (_agent_out_msg) { + DBG(0, "clipboard change is already pending"); + return; + } + _agent_out_msg_pos = 0; + _agent_out_msg_size = sizeof(VDAgentMessage) + sizeof(VDAgentClipboard) + clip_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; + 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; + } + if (_agent_tokens) { + post_agent_clipboard(); + } +} + void RedClient::set_mouse_mode(uint32_t supported_modes, uint32_t current_mode) { if (current_mode != _mouse_mode) { @@ -762,6 +832,17 @@ 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(); @@ -776,7 +857,7 @@ void RedClient::handle_init(RedPeer::InMessage* message) Message* msg = new Message(SPICE_MSGC_MAIN_AGENT_START); SpiceMsgcMainAgentStart agent_start; agent_start.num_tokens = ~0; - _marshallers->msgc_main_agent_start(msg->marshaller(), &agent_start); + _marshallers->msgc_main_agent_start(msg->marshaller(), &agent_start); post_message(msg); } @@ -900,6 +981,11 @@ void RedClient::handle_agent_data(RedPeer::InMessage* message) 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); } @@ -914,6 +1000,9 @@ void RedClient::handle_agent_tokens(RedPeer::InMessage* message) { SpiceMsgMainAgentTokens *token = (SpiceMsgMainAgentTokens *)message->data(); _agent_tokens += token->num_tokens; + if (_agent_out_msg_pos < _agent_out_msg_size) { + post_agent_clipboard(); + } } void RedClient::handle_migrate_switch_host(RedPeer::InMessage* message) diff --git a/client/red_client.h b/client/red_client.h index 9603bfe..fd1a9b4 100644 --- a/client/red_client.h +++ b/client/red_client.h @@ -186,6 +186,7 @@ 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 for_each_channel(ForEachChannelFunc& func); void on_mouse_capture_trigger(RedScreen& screen); @@ -222,6 +223,8 @@ private: void handle_migrate_switch_host(RedPeer::InMessage* message); void on_agent_reply(VDAgentReply* reply); + void on_agent_clipboard(VDAgentClipboard* clipboard, uint32_t size); + void post_agent_clipboard(); ChannelFactory* find_factory(uint32_t type); void create_channel(uint32_t type, uint32_t id); @@ -250,9 +253,13 @@ private: bool _agent_connected; bool _agent_mon_config_sent; bool _agent_disp_config_sent; + //FIXME: rename to in/out, extract all agent stuff? VDAgentMessage* _agent_msg; uint8_t* _agent_msg_data; uint32_t _agent_msg_pos; + VDAgentMessage* _agent_out_msg; + uint32_t _agent_out_msg_size; + uint32_t _agent_out_msg_pos; uint32_t _agent_tokens; AutoRef<AgentTimer> _agent_timer; diff --git a/client/windows/platform.cpp b/client/windows/platform.cpp index 81eb787..ae49d02 100644 --- a/client/windows/platform.cpp +++ b/client/windows/platform.cpp @@ -749,6 +749,96 @@ void WinPlatform::exit_modal_loop() modal_loop_active = false; } +void Platform::set_clipboard_listener(ClipboardListener* listener) +{ + //FIXME: call only on change, use statics + listener->on_clipboard_change(); +} + +UINT get_format(uint32_t type) +{ + switch (type) { + case Platform::CLIPBOARD_UTF8_TEXT: + return CF_UNICODETEXT; + default: + return 0; + } +} + +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; + + //LOG_INFO("type %u size %d %s", type, size, data); + if (!format || !OpenClipboard(paltform_win)) { + 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(); + 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); + GlobalUnlock(clip_data); + if (ret) { + EmptyClipboard(); + ret = !!SetClipboardData(format, clip_data); + } + CloseClipboard(); + return ret; +} + +bool Platform::get_clipboard_data(uint32_t type, uint8_t* data, int32_t size) +{ + UINT format = get_format(type); + HANDLE clip_data; + LPVOID clip_buf; + bool ret; + + LOG_INFO("type %u size %d", type, size); + if (!format || !IsClipboardFormatAvailable(format) || !OpenClipboard(paltform_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; + } + if (!(clip_data = GetClipboardData(format)) || !(clip_buf = GlobalLock(clip_data))) { + CloseClipboard(); + return 0; + } + clip_size = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)clip_buf, -1, NULL, 0, NULL, NULL); + GlobalUnlock(clip_data); + CloseClipboard(); + return clip_size; +} + + static bool has_console = false; static void create_console() diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp index af5a65e..3b9f4af 100644 --- a/client/x11/platform.cpp +++ b/client/x11/platform.cpp @@ -19,6 +19,7 @@ #include <X11/Xlib.h> #include <X11/Xutil.h> +#include <X11/Xatom.h> #include <X11/XKBlib.h> #include <X11/Xresource.h> #include <X11/cursorfont.h> @@ -63,7 +64,7 @@ //#define X_DEBUG_SYNC(display) XSync(display, False) #define X_DEBUG_SYNC(display) #ifdef HAVE_XRANDR12 -#define USE_XRANDR_1_2 +//#define USE_XRANDR_1_2 #endif static Display* x_display = NULL; @@ -76,6 +77,7 @@ static GLXFBConfig **fb_config = NULL; static XIM x_input_method = NULL; static XIC x_input_context = NULL; +static Window platform_win; static XContext win_proc_context; static ProcessLoop* main_loop = NULL; static int focus_count = 0; @@ -95,6 +97,19 @@ static bool using_xrender_0_5 = false; 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_size = 0; +static int32_t clipboard_data_space = 0; +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 + class DefaultEventListener: public Platform::EventListener { public: virtual void on_app_activated() {} @@ -113,6 +128,13 @@ public: static DefaultDisplayModeListener default_display_mode_listener; static Platform::DisplayModeListener* display_mode_listener = &default_display_mode_listener; +class DefaultClipboardListener: public Platform::ClipboardListener { +public: + void on_clipboard_change() {} +}; + +static DefaultClipboardListener default_clipboard_listener; +static Platform::ClipboardListener* clipboard_listener = &default_clipboard_listener; NamedPipe::ListenerRef NamedPipe::create(const char *name, ListenerInterface& listener_interface) { @@ -689,6 +711,16 @@ private: bool _out_of_sync; }; +static void intern_clipboard_atoms() +{ + static bool interned = false; + if (interned) return; + clipboard_prop = XInternAtom(x_display, "CLIPBOARD", False); + incr_atom = XInternAtom(x_display, "INCR", False); + utf8_atom = XInternAtom(x_display, "UTF8_STRING", False); + interned = true; +} + DynamicScreen::DynamicScreen(Display* display, int screen, int& next_mon_id) : XScreen(display, screen) , Monitor(next_mon_id++) @@ -697,10 +729,12 @@ DynamicScreen::DynamicScreen(Display* display, int screen, int& next_mon_id) , _out_of_sync (false) { X_DEBUG_SYNC(display); - Window root_window = RootWindow(display, screen); - XSelectInput(display, root_window, StructureNotifyMask); - XRRSelectInput(display, root_window, RRScreenChangeNotifyMask); - XPlatform::set_win_proc(root_window, root_win_proc); + //FIXME: replace RootWindow() in other refs as well? + 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); + intern_clipboard_atoms(); X_DEBUG_SYNC(display); } @@ -962,9 +996,21 @@ MultyMonScreen::MultyMonScreen(Display* display, int screen, int& next_mon_id) } XSelectInput(display, root_window, StructureNotifyMask); + X_DEBUG_SYNC(get_display()); XRRSelectInput(display, root_window, RRScreenChangeNotifyMask); + X_DEBUG_SYNC(get_display()); + intern_clipboard_atoms(); XPlatform::set_win_proc(root_window, root_win_proc); 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; + X_DEBUG_SYNC(get_display()); } MultyMonScreen::~MultyMonScreen() @@ -2079,8 +2125,85 @@ void Platform::path_append(std::string& path, const std::string& partial_path) path += partial_path; } +static void ensure_clipboard_data_space(uint32_t size) +{ + if (size > clipboard_data_space) { + delete clipboard_data; + clipboard_data = NULL; + clipboard_data = new uint8_t[size]; + assert(clipboard_data); + clipboard_data_space = size; + } +} + +static void realloc_clipboard_data_space(uint32_t size) +{ + 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]; + assert(newbuf); + memcpy(newbuf, clipboard_data, old_alloc); + delete[] clipboard_data; + clipboard_data = newbuf; +} + +static void update_clipboard(unsigned long size, uint8_t* data) +{ + clipboard_data_size = 0; + ensure_clipboard_data_space(size); + memcpy(clipboard_data, data, size); + 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) { + if (clipboard_data_size + length > clipboard_data_space) { + realloc_clipboard_data_space(clipboard_data_size + length); + } + strncpy ((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; +} + + static void root_win_proc(XEvent& event) { + static bool waiting_for_property_notify = false; + #ifdef USE_XRANDR_1_2 ASSERT(using_xrandr_1_0 || using_xrandr_1_2); #else @@ -2101,14 +2224,102 @@ static void root_win_proc(XEvent& event) (*iter)->set_broken(); } event_listener->on_monitors_change(); + return; } - /*switch (event.type - xrandr_event_base) { - case RRScreenChangeNotify: - //XRRScreenChangeNotifyEvent * = (XRRScreenChangeNotifyEvent *) &event; - XRRUpdateConfiguration(&event); + switch (event.type) { + case SelectionRequest: { + //FIXME: support multi-chunk + Lock lock(clipboard_lock); + if (clipboard_data_size == 0) { + return; + } + 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); break; - }*/ + } + case SelectionClear: { + Lock lock(clipboard_lock); + clipboard_data_size = 0; + break; + } + case SelectionNotify: { + Atom type; + int format; + unsigned long len; + 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); + if (size == 0) { + break; + } + if (XGetWindowProperty(x_display, platform_win, clipboard_prop, 0, size, + False, AnyPropertyType, &type, &format, &len, &dummy, &data) != Success) { + LOG_INFO("XGetWindowProperty failed"); + break; + } + LOG_INFO("data: %s len: %u", data, len); + { + Lock lock(clipboard_lock); + clipboard_data_size = 0; + } + if (type == incr_atom) { + 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); + XDeleteProperty(x_display, requestor_win, prop); + waiting_for_property_notify = true; + { + Lock lock(clipboard_lock); + ensure_clipboard_data_space(*(uint32_t*)data); + } + break; + } + { + Lock lock(clipboard_lock); + update_clipboard(++size, data); + } + XFree(data); + clipboard_listener->on_clipboard_change(); + break; + } + case PropertyNotify: + if (!waiting_for_property_notify) { + break; + } + { + 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(); + } + } + break; + default: + return; + } } static void process_monitor_configure_events(Window root) @@ -2735,3 +2946,46 @@ LocalCursor* Platform::create_default_cursor() { return new XDefaultCursor(); } + +void Platform::set_clipboard_listener(ClipboardListener* listener) +{ + //FIXME: XA_CLIPBOARD(x_display) + if (XGetSelectionOwner(x_display, XA_PRIMARY) == None) { + return; + } + clipboard_listener = listener; + XConvertSelection(x_display, XA_PRIMARY, utf8_atom, clipboard_prop, + platform_win, CurrentTime); +} + +bool Platform::set_clipboard_data(uint32_t type, const uint8_t* data, int32_t size) +{ + Lock lock(clipboard_lock); + + LOG_INFO("type %u size %u data %s", type, size, data); + if (size > clipboard_data_space) { + delete clipboard_data; + clipboard_data = new uint8_t[size]; + clipboard_data_space = size; + } + 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"); + return true; +} + +bool Platform::get_clipboard_data(uint32_t type, uint8_t* data, int32_t size) +{ + //FIXME: check type + memcpy(data, clipboard_data, size); + return true; +} + +int32_t Platform::get_clipboard_data_size(uint32_t type) +{ + //FIXME: check type + return clipboard_data_size; +} + |