[Spice-devel] [PATCH] client: support clipboard/selection-owner model (v2)

Hans de Goede hdegoede at redhat.com
Fri Oct 1 07:49:02 PDT 2010


Hi,

There still is quite some work to do, but I think that this can be
committed as is, and then build from there. I believe fixing up things
incrementally is better then doing one monster patch. So ack.

Note I'm working on this now, I will post set of patches on top of this
one before sunday.

Regards,

Hans


On 09/29/2010 02:17 PM, Arnon Gilboa wrote:
> -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     |  387 +++++++++++++++++++++++++++++--------------
>   configure.ac                |    3 +-
>   9 files changed, 603 insertions(+), 263 deletions(-)
>
> diff --git a/client/application.cpp b/client/application.cpp
> index 490cd8c..a13d5e8 100644
> --- a/client/application.cpp
> +++ b/client/application.cpp
> @@ -1325,12 +1325,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 dde37fc..36ae86e 100644
> --- a/client/application.h
> +++ b/client/application.h
> @@ -141,7 +141,6 @@ typedef std::list<GUIBarrier*>  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 6288380..4727889 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 79f5e6d..562b826 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<RedClient*>(events_loop.get_owner())->send_agent_clipboard();
> +    VDAgentClipboardGrab grab = {_type};
> +    static_cast<RedClient*>(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<RedClient*>(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<MainChannelLoop*>(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<ClipboardGrabEvent>  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<ClipboardRequestEvent>  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<ClipboardEvent>  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 ba8b4ee..3ccb8e3 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 ae49d02..95876dc 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<spice/vd_agent.h>
>
>   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 101f6dd..f570a10 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,6 +203,7 @@ spicec_LDADD =						\
>   	$(ALSA_LIBS)					\
>   	$(GL_LIBS)					\
>   	$(XRANDR_LIBS)					\
> +	$(XFIXES_LIBS)					\
>   	$(MISC_X_LIBS)					\
>   	$(CEGUI_LIBS)					\
>   	-lrt
> diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
> index cc1502b..9d1112d 100644
> --- a/client/x11/platform.cpp
> +++ b/client/x11/platform.cpp
> @@ -28,6 +28,7 @@
>   #include<X11/extensions/XKB.h>
>   #include<X11/extensions/Xrender.h>
>   #include<X11/extensions/XShm.h>
> +#include<X11/extensions/Xfixes.h>
>   #include<unistd.h>
>   #include<sys/socket.h>
>   #include<sys/epoll.h>
> @@ -56,6 +57,7 @@
>   #include "res.h"
>   #include "cursor.h"
>   #include "process_loop.h"
> +#include<spice/vd_agent.h>
>
>   #define DWORD uint32_t
>   #define BOOL bool
> @@ -64,7 +66,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;
> @@ -94,24 +96,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() {}
> @@ -132,12 +149,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()));
> @@ -763,6 +796,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;
>   }
>
> @@ -778,13 +816,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);
>   }
>
> @@ -873,7 +917,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());
> @@ -1045,26 +1089,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());
>   }
>
> @@ -1172,7 +1214,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());
>   }
> @@ -1734,8 +1776,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;
> @@ -2193,7 +2234,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];
> @@ -2211,49 +2254,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)
>   {
> @@ -2281,28 +2362,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: {
> @@ -2317,17 +2415,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;
> @@ -2336,7 +2434,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;
>               {
> @@ -2345,33 +2443,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;
>       }
> @@ -2463,6 +2572,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;
> @@ -2673,6 +2791,7 @@ void Platform::init()
>       init_kbd();
>       init_xrandr();
>       init_xrender();
> +    init_xfixes();
>       init_XIM();
>
>       struct sigaction act;
> @@ -3009,22 +3128,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;
> +    Lock lock(clipboard_lock);
> +    uint32_t format = get_clipboard_format(type);
> +
> +    if (!format) {
> +        LOG_INFO("Unsupported clipboard type %u", type);
> +        return false;
>       }
> -    clipboard_listener = listener;
> -    XConvertSelection(x_display, XA_PRIMARY, utf8_atom, clipboard_prop,
> -        platform_win, CurrentTime);
> +    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];
> @@ -3032,22 +3164,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);
>   }
> -
> diff --git a/configure.ac b/configure.ac
> index 3369dde..77fdf6e 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -189,9 +189,10 @@ AC_SUBST(GL_LIBS)
>   SPICE_NONPKGCONFIG_LIBS+=" $GL_LIBS"
>
>   PKG_CHECK_MODULES(XRANDR, xrandr)
> +PKG_CHECK_MODULES(XFIXES, xfixes)
>   AC_SUBST(XRANDR_CFLAGS)
>   AC_SUBST(XRANDR_LIBS)
> -SPICE_REQUIRES+=" xrandr"
> +SPICE_REQUIRES+=" xrandr xfixes"
>
>   PKG_CHECK_MODULES(XRANDR12,
>           xrandr>= 1.2,


More information about the Spice-devel mailing list