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

Hans de Goede hdegoede at redhat.com
Fri Sep 24 08:12:18 PDT 2010


Hi,

I've applied this patch to my local git repo to be able to work
on copy and paste support in the Linux agent, and it needs the following
patch on top to compile the linux client successfully:

diff --git a/client/x11/Makefile.am b/client/x11/Makefile.am
index 2a83473..f570a10 100644
--- a/client/x11/Makefile.am
+++ b/client/x11/Makefile.am
@@ -22,7 +22,7 @@ INCLUDES = \
         $(CELT051_CFLAGS)                               \
         $(SSL_CFLAGS)                                   \
         $(XRANDR_CFLAGS)                                \
-       $(XFIXES_LIBS)                                  \
+       $(XFIXES_CFLAGS)                                \
         $(MISC_X_CFLAGS)                                \
         $(CEGUI_CFLAGS)                                 \
         $(WARN_CFLAGS)                                  \
@@ -203,6 +203,7 @@ spicec_LDADD =
         $(ALSA_LIBS)                                    \
         $(GL_LIBS)                                      \
         $(XRANDR_LIBS)                                  \
+       $(XFIXES_LIBS)                                  \
         $(MISC_X_LIBS)                                  \
         $(CEGUI_LIBS)                                   \
         -lrt


Regards,

Hans


On 09/22/2010 02:16 PM, Arnon Gilboa wrote:
> -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:
> -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
> ---
>   client/application.cpp      |    6 -
>   client/application.h        |    2 -
>   client/platform.h           |   29 ++++-
>   client/red_client.cpp       |  205 +++++++++++++++++++--------
>   client/red_client.h         |   34 ++++-
>   client/windows/platform.cpp |  240 +++++++++++++++++++++++++-------
>   client/x11/Makefile.am      |    1 +
>   client/x11/platform.cpp     |  326 +++++++++++++++++++++++++++----------------
>   configure.ac                |    3 +-
>   9 files changed, 597 insertions(+), 249 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..e68ba8f 100644
> --- a/client/platform.h
> +++ b/client/platform.h
> @@ -123,11 +123,32 @@ public:
>
>       enum {
>           CLIPBOARD_UTF8_TEXT = 1,
> +        CLIPBOARD_BITMAP
>       };
>
> +    typedef struct ClipboardFormat {
> +        uint32_t format;
> +        uint32_t type;
> +    } ClipboardFormat;
> +
> +    static ClipboardFormat clipboard_formats[];
> +
> +    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 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 +162,9 @@ public:
>   class Platform::ClipboardListener {
>   public:
>       virtual ~ClipboardListener() {}
> -    virtual void on_clipboard_change() = 0;
> +    virtual void on_clipboard_change(uint32_t type) = 0;
> +    virtual void on_clipboard_render(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..ba1cb89 100644
> --- a/client/red_client.cpp
> +++ b/client/red_client.cpp
> @@ -81,9 +81,28 @@ uint32_t default_agent_caps[] = {
>       (1<<  VD_AGENT_CAP_REPLY)
>       };
>
> -void ClipboardEvent::response(AbstractProcessLoop&  events_loop)
> +typedef struct ClipboardType {
> +    uint32_t agent_type;
> +    uint32_t platform_type;
> +} ClipboardType;
> +
> +ClipboardType clipboard_types[] = {
> +    {VD_AGENT_CLIPBOARD_UTF8_TEXT, Platform::CLIPBOARD_UTF8_TEXT},
> +    {VD_AGENT_CLIPBOARD_BITMAP, Platform::CLIPBOARD_BITMAP},
> +    {0, 0}};
> +
> +void ClipboardGrabEvent::response(AbstractProcessLoop&  events_loop)
> +{
> +    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)
>   {
> -    static_cast<RedClient*>(events_loop.get_owner())->send_agent_clipboard();
> +    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 +358,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 +562,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();
> @@ -784,16 +805,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 +813,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 +827,39 @@ 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_change(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);
> +    AutoRef<ClipboardGrabEvent>  event(new ClipboardGrabEvent(type));
> +    get_process_loop().push_event(*event);
> +}
> +
> +void RedClient::on_clipboard_render(uint32_t type)
> +{
> +    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 +867,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 = get_agent_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) {
> @@ -886,13 +909,62 @@ 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");
> +    uint32_t type = get_platform_clipboard_type(clipboard->type);
> +
> +    if (!type) {
> +        LOG_INFO("Unsupported clipboard type %u", clipboard->type);
> +        return;
>       }
> +    Platform::set_clipboard_data(type, clipboard->data, size);
> +}
> +
> +void RedClient::on_agent_clipboard_grab(VDAgentClipboardGrab* clipboard_grab)
> +{
> +    uint32_t type = get_platform_clipboard_type(clipboard_grab->type);
> +
> +    if (!type) {
> +        LOG_INFO("Unsupported clipboard type %u", clipboard_grab->type);
> +        return;
> +    }
> +    Platform::set_clipboard_owner(type);
> +}
> +
> +void RedClient::on_agent_clipboard_request(VDAgentClipboardRequest* clipboard_request)
> +{
> +    uint32_t type = get_platform_clipboard_type(clipboard_request->type);
> +
> +    if (!type) {
> +        LOG_INFO("Unsupported clipboard type %u", clipboard_request->type);
> +        return;
> +    }
> +    if (!Platform::request_clipboard_notification(type)) {
> +        send_agent_clipboard_message(VD_AGENT_CLIPBOARD_RELEASE);
> +    }
> +}
> +
> +void RedClient::on_agent_clipboard_release()
> +{
> +    Platform::release_clipboard();
> +}
> +
> +uint32_t RedClient::get_platform_clipboard_type(uint32_t agent_type)
> +{
> +    for (ClipboardType* iter = clipboard_types; iter->agent_type&&  iter->platform_type; iter++) {
> +        if (iter->agent_type == agent_type) {
> +            return iter->platform_type;
> +        }
> +    }
> +    return 0;
> +}
> +
> +uint32_t RedClient::get_agent_clipboard_type(uint32_t platform_type)
> +{
> +    for (ClipboardType* iter = clipboard_types; iter->agent_type&&  iter->platform_type; iter++) {
> +        if (iter->platform_type == platform_type) {
> +            return iter->agent_type;
> +        }
> +    }
> +    return 0;
>   }
>
>   void RedClient::handle_init(RedPeer::InMessage* message)
> @@ -1055,25 +1127,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 +1135,35 @@ 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: {
> +        on_agent_clipboard((VDAgentClipboard*)data, msg->size - sizeof(VDAgentClipboard));
> +        break;
> +    }
> +    case VD_AGENT_CLIPBOARD_GRAB:
> +        on_agent_clipboard_grab((VDAgentClipboardGrab*)data);
> +        break;
> +    case VD_AGENT_CLIPBOARD_REQUEST:
> +        on_agent_clipboard_request((VDAgentClipboardRequest*)data);
> +        break;
> +    case VD_AGENT_CLIPBOARD_RELEASE:
> +        on_agent_clipboard_release();
> +        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..ea9c735 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_change(uint32_t type);
> +    void on_clipboard_render(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,19 @@ 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 on_agent_clipboard_grab(VDAgentClipboardGrab* clipboard_grab);
> +    void on_agent_clipboard_request(VDAgentClipboardRequest* clipboard_request);
> +    void on_agent_clipboard_release();
>       void do_send_agent_clipboard();
> +    void send_agent_clipboard_message(uint32_t message_type, uint32_t size = 0, void* data = NULL);
> +    uint32_t get_platform_clipboard_type(uint32_t agent_type);
> +    uint32_t get_agent_clipboard_type(uint32_t platform_type);
>
>       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..096cd66 100644
> --- a/client/windows/platform.cpp
> +++ b/client/windows/platform.cpp
> @@ -45,9 +45,35 @@ public:
>
>   static DefaultEventListener default_event_listener;
>   static Platform::EventListener* event_listener =&default_event_listener;
> -static HWND paltform_win;
> +static HWND paltform_win = NULL;
>   static ProcessLoop* main_loop = NULL;
>
> +class DefaultClipboardListener: public Platform::ClipboardListener {
> +public:
> +    virtual void on_clipboard_change(uint32_t type) {}
> +    virtual void on_clipboard_render(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;
> +
> +Platform::ClipboardFormat Platform::clipboard_formats[] = {
> +    {CF_UNICODETEXT, Platform::CLIPBOARD_UTF8_TEXT},
> +    {CF_DIB, Platform::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 +85,19 @@ void Platform::send_quit_request()
>       main_loop->quit(0);
>   }
>
> +static uint32_t get_available_clipboard_type()
> +{
> +    uint32_t type = 0;
> +
> +    for (Platform::ClipboardFormat* iter = Platform::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 +121,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_change(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 = Platform::get_clipboard_type(wParam);
> +        if (!type) {
> +            LOG_INFO("Unsupported clipboard format %u", wParam);
> +            break;
> +        }
> +        clipboard_listener->on_clipboard_render(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 +169,6 @@ static void create_message_wind()
>   {
>       WNDCLASSEX wclass;
>       ATOM class_atom;
> -    HWND window;
>
>       const LPCWSTR class_name = L"spicec_platform_wclass";
>
> @@ -113,11 +189,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 (!(paltform_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(paltform_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 +541,16 @@ void Platform::path_append(std::string&  path, const std::string&  partial_path)
>       path += partial_path;
>   }
>
> +static void cleanup()
> +{
> +    ChangeClipboardChain(paltform_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)
> @@ -749,62 +837,101 @@ void WinPlatform::exit_modal_loop()
>       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(paltform_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;
> -
> -    //LOG_INFO("type %u size %d %s", type, size, data);
> -    if (!format || !OpenClipboard(paltform_win)) {
> +    int clip_len;
> +    UINT format;
> +    bool ret = false;
> +
> +    // Get the required clipboard size
> +    switch (format = get_clipboard_format(type)) {
> +    case CF_UNICODETEXT:
> +        // 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 CF_DIB:
> +        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 (format) {
> +    case CF_UNICODETEXT:
> +        ret = !!MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)data, size, (LPWSTR)clip_buf, clip_len);
> +        ((LPWSTR)clip_buf)[clip_len - 1] = L'\0';
> +        break;
> +    case CF_DIB:
> +        memcpy(clip_buf, data, size);
> +        ret = true;
> +        break;
> +    }
>       GlobalUnlock(clip_data);
> -    if (ret) {
> -        EmptyClipboard();
> -        ret = !!SetClipboardData(format, clip_data);
> +    if (!ret) {
> +        return false;
> +    }
> +    if (SetClipboardData(format, clip_data)) {
> +        SetEvent(clipboard_event);
> +        return true;
> +    }
> +    // We retry clipboard open-empty-set-close only when there is a timeout in WM_RENDERFORMAT
> +    if (!OpenClipboard(paltform_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)) {
>           return false;
>       }
> @@ -812,32 +939,45 @@ bool Platform::get_clipboard_data(uint32_t type, uint8_t* data, int32_t size)
>           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 (format) {
> +    case CF_UNICODETEXT: {
> +        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 CF_DIB: {
> +        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;
> +    }
> +    default:
> +        LOG_INFO("Unsupported clipboard type %u", type);
>       }
> -    clip_size = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)clip_buf, -1, NULL, 0, NULL, NULL);
> +
>       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..2a83473 100644
> --- a/client/x11/Makefile.am
> +++ b/client/x11/Makefile.am
> @@ -22,6 +22,7 @@ INCLUDES = \
>   	$(CELT051_CFLAGS)				\
>   	$(SSL_CFLAGS)					\
>   	$(XRANDR_CFLAGS)				\
> +	$(XFIXES_LIBS)					\
>   	$(MISC_X_CFLAGS)				\
>   	$(CEGUI_CFLAGS)					\
>   	$(WARN_CFLAGS)                                  \
> diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
> index cc1502b..625af2a 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>
> @@ -64,7 +65,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 +95,33 @@ 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 Bool handle_x_error = false;
>   static int x_error_code;
>
> +Platform::ClipboardFormat Platform::clipboard_formats[] = {
> +    {0, 0},
> +    {0, 0}};
> +
>   class DefaultEventListener: public Platform::EventListener {
>   public:
>       virtual void on_app_activated() {}
> @@ -132,7 +142,9 @@ static Platform::DisplayModeListener* display_mode_listener =&default_display_m
>
>   class DefaultClipboardListener: public Platform::ClipboardListener {
>   public:
> -    void on_clipboard_change() {}
> +    void on_clipboard_change(uint32_t type) {}
> +    void on_clipboard_render(uint32_t type) {}
> +    void on_clipboard_notify(uint32_t type, uint8_t* data, int32_t size) {}
>   };
>
>   static DefaultClipboardListener default_clipboard_listener;
> @@ -763,6 +775,10 @@ 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);
> +
> +    Platform::clipboard_formats[0].format = utf8_atom;
> +    Platform::clipboard_formats[0].type = Platform::CLIPBOARD_UTF8_TEXT;
> +
>       interned = true;
>   }
>
> @@ -778,13 +794,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, XA_PRIMARY,
> +                                   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 +895,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 +1067,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, XA_PRIMARY,
> +                                   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 +1192,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 +1754,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 +2212,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 +2232,58 @@ 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);
> +    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;
> +    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;
> +    XChangeProperty(x_display, requestor_win, prop, type, 8, PropModeReplace,
> +                    (unsigned char *)clipboard_data, clipboard_data_size);
> +    XEvent res;
> +    res.xselection.property = prop;
> +    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 root_win_proc(XEvent&  event)
>   {
> @@ -2281,28 +2311,38 @@ 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_change(Platform::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;
> +        uint32_t type = Platform::get_clipboard_type(selection_request->target);
> +        if (!type) {
> +            LOG_INFO("Unsupported selection type %u", selection_request->target);
> +            break;
> +        }
> +        if (clipboard_data_size>  0) {
> +            send_selection_notify(selection_request->target);
> +            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);
> +        clipboard_event = event;
> +        clipboard_listener->on_clipboard_render(type);
>           break;
>       }
>       case SelectionClear: {
> @@ -2317,17 +2357,26 @@ 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 (type != event.xselection.target) {
> +            LOG_INFO("type %d != %u requested", type, event.xselection.target);
>               break;
>           }
>           if (XGetWindowProperty(x_display, platform_win, clipboard_prop, 0, size,
> -            False, AnyPropertyType,&type,&format,&len,&dummy,&data) != Success) {
> -            LOG_INFO("XGetWindowProperty failed");
> +            False, 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;
>           }
> -        LOG_INFO("data: %s len: %u", data, len);
>           {
>               Lock lock(clipboard_lock);
>               clipboard_data_size = 0;
> @@ -2336,7 +2385,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;
>               {
> @@ -2347,31 +2396,32 @@ static void root_win_proc(XEvent&  event)
>           }
>           {
>               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 +2513,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 +2732,7 @@ void Platform::init()
>       init_kbd();
>       init_xrandr();
>       init_xrender();
> +    init_xfixes();
>       init_XIM();
>
>       struct sigaction act;
> @@ -3009,22 +3069,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, XA_PRIMARY, 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 +3105,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, XA_PRIMARY) == 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, XA_PRIMARY, 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, XA_PRIMARY, 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