[Spice-commits] 19 commits - client/application.cpp client/application.h client/platform.h client/red_client.cpp client/red_client.h client/windows client/x11 configure.ac

Hans de Goede jwrdegoede at kemper.freedesktop.org
Mon Oct 4 08:13:15 PDT 2010


 client/application.cpp      |    6 
 client/application.h        |    2 
 client/platform.h           |   22 -
 client/red_client.cpp       |  191 +++++++---
 client/red_client.h         |   42 ++
 client/windows/platform.cpp |  279 ++++++++++++---
 client/x11/Makefile.am      |    2 
 client/x11/platform.cpp     |  780 +++++++++++++++++++++++++++++++++-----------
 configure.ac                |    3 
 9 files changed, 1004 insertions(+), 323 deletions(-)

New commits:
commit 34bfaa302d5da8e08620d31d3f40d55ee9d14b98
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Mon Oct 4 14:13:41 2010 +0200

    spicec-x11: make get_clipboard_type handle the None Atom

diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
index c1c2a5a..29b5f75 100644
--- a/client/x11/platform.cpp
+++ b/client/x11/platform.cpp
@@ -184,6 +184,9 @@ static const char *atom_name(Atom atom)
 static uint32_t get_clipboard_type(Atom target) {
     int i;
 
+    if (target == None)
+        return VD_AGENT_CLIPBOARD_NONE;
+
     for (i = 0; i < utf8_atom_count; i++)
         if (utf8_atoms[i] == target)
             return VD_AGENT_CLIPBOARD_UTF8_TEXT;
commit b74f21ce66ff9d41d16a7d344a4f795aa2dd4297
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Mon Oct 4 14:06:05 2010 +0200

    spicec-x11: protect against recursive incr properties

diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
index c6b2d7d..c1c2a5a 100644
--- a/client/x11/platform.cpp
+++ b/client/x11/platform.cpp
@@ -2373,6 +2373,10 @@ static int get_selection(XEvent &event, Atom type, Atom prop, int format,
 
     if (!incr) {
         if (type_ret == incr_atom) {
+            if (waiting_for_property_notify) {
+                LOG_WARN("received an incr property notify while still reading another incr property");
+                goto exit;
+            }
             XSelectInput(x_display, platform_win, PropertyChangeMask);
             XDeleteProperty(x_display, platform_win, prop);
             XFlush(x_display);
commit ca3d29029455cbb3441a5a7715f43638f3d54d1c
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Mon Oct 4 12:15:45 2010 +0200

    spicec-x11: If the clipboard was large return the memory to the system

diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
index dc0787f..c6b2d7d 100644
--- a/client/x11/platform.cpp
+++ b/client/x11/platform.cpp
@@ -2445,7 +2445,14 @@ exit:
 
 static void get_selection_free(unsigned char *data, bool incr)
 {
-    if (!incr && data)
+    if (incr) {
+        /* If the clipboard was large return the memory to the system */
+        if (clipboard_data_space > 512 * 1024) {
+            free(clipboard_data);
+            clipboard_data = NULL;
+            clipboard_data_space = 0;
+        }
+    } else if (data)
         XFree(data);
 }
 
commit ff434c288f87a1a08a2cf1e58260daea68914769
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Mon Oct 4 12:10:47 2010 +0200

    spicec-x11: use malloc / free / realloc for clipboard data
    
    As we need a realloc function it is better to use malloc / free /
    realloc then to diy, esp. as realloc can grow the buffer without
    needing a memcpy.

diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
index 981bdc0..dc0787f 100644
--- a/client/x11/platform.cpp
+++ b/client/x11/platform.cpp
@@ -2265,28 +2265,13 @@ void Platform::path_append(std::string& path, const std::string& partial_path)
 static void ensure_clipboard_data_space(uint32_t size)
 {
     if (size > clipboard_data_space) {
-        delete clipboard_data;
-        clipboard_data = NULL;
-        clipboard_data = new uint8_t[size];
+        free(clipboard_data);
+        clipboard_data = (uint8_t *)malloc(size);
         assert(clipboard_data);
         clipboard_data_space = size;
     }
 }
 
-static void realloc_clipboard_data_space(uint32_t size)
-{
-    if (size <= clipboard_data_space) {
-        return;
-    }
-    uint32_t old_alloc = clipboard_data_space;
-    clipboard_data_space = size;
-    uint8_t *newbuf = new uint8_t[clipboard_data_space];
-    assert(newbuf);
-    memcpy(newbuf, clipboard_data, old_alloc);
-    delete[] clipboard_data;
-    clipboard_data = newbuf;
-}
-
 static void send_selection_notify(Atom prop, int process_next_req)
 {
     XEvent res, *event = &next_selection_request->event;
@@ -2425,8 +2410,11 @@ static int get_selection(XEvent &event, Atom type, Atom prop, int format,
 
     if (incr) {
         if (len) {
-            if (clipboard_data_size + len > clipboard_data_space)
-                realloc_clipboard_data_space(clipboard_data_size + len);
+            if (clipboard_data_size + len > clipboard_data_space) {
+                clipboard_data_space = clipboard_data_size + len;
+                clipboard_data = (uint8_t *)realloc(clipboard_data, clipboard_data_space);
+                assert(clipboard_data);
+            }
             memcpy(clipboard_data + clipboard_data_size, data, len);
             clipboard_data_size += len;
             LOG_INFO("Appended %d bytes to buffer", len);
commit 348e62bd88f63f5d841fb79d747a340076c408f4
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Mon Oct 4 11:34:09 2010 +0200

    spicec-x11: Use a queue for XSelectionRequest events
    
    XSelectionRequest events must be answered in the order they were
    received. But for TARGETS request we can answer directly, where as
    other requests need to go through the agent. This causes us to handle
    things out of order, and this can cause us to have more then one
    requets outstanding with the agent, which is also not what we want.
    
    So this patch introduces a queue for XSelectionRequest events, causing
    us to handle them 1 at a time and always in order.

diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
index 90b086f..981bdc0 100644
--- a/client/x11/platform.cpp
+++ b/client/x11/platform.cpp
@@ -115,27 +115,35 @@ static const char * const utf8_atom_names[] = {
 
 #define utf8_atom_count (sizeof(utf8_atom_names)/sizeof(utf8_atom_names[0]))
 
+struct selection_request {
+    XEvent event;
+    selection_request *next;
+};
+
 static int expected_targets_notifies = 0;
 static bool waiting_for_property_notify = false;
 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 Atom clipboard_request_target = None;
+static selection_request *next_selection_request = NULL;
 static uint32_t clipboard_type_count = 0;
+/* TODO Add support for more types here */
 /* Warning the size of these 2 needs to be increased each time we add
    support for a new type!! */
 static uint32_t clipboard_agent_types[1];
 static Atom clipboard_x11_targets[1];
-static XEvent clipboard_event;
 static Mutex clipboard_lock;
 static Atom clipboard_prop;
 static Atom incr_atom;
 static Atom utf8_atoms[utf8_atom_count];
 static Atom targets_atom;
+static Atom multiple_atom;
 static Bool handle_x_error = false;
 static int x_error_code;
 
+static void handle_selection_request();
+
 class DefaultEventListener: public Platform::EventListener {
 public:
     virtual void on_app_activated() {}
@@ -829,6 +837,7 @@ static void intern_clipboard_atoms()
     if (interned) return;
     clipboard_prop = XInternAtom(x_display, "CLIPBOARD", False);
     incr_atom = XInternAtom(x_display, "INCR", False);
+    multiple_atom = XInternAtom(x_display, "MULTIPLE", False);
     targets_atom = XInternAtom(x_display, "TARGETS", False);
     for(i = 0; i < utf8_atom_count; i++)
         utf8_atoms[i] = XInternAtom(x_display, utf8_atom_names[i], False);
@@ -2278,28 +2287,27 @@ static void realloc_clipboard_data_space(uint32_t size)
     clipboard_data = newbuf;
 }
 
-// FIXME: use INCR for large data transfers
-static void send_selection_notify(Atom type)
+static void send_selection_notify(Atom prop, int process_next_req)
 {
-    Window requestor_win = clipboard_event.xselectionrequest.requestor;
-    Atom prop = clipboard_event.xselectionrequest.property;
-    XEvent res;
+    XEvent res, *event = &next_selection_request->event;
+    selection_request *old_request;
 
-    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.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);
+    res.xselection.display = event->xselectionrequest.display;
+    res.xselection.requestor = event->xselectionrequest.requestor;
+    res.xselection.selection = event->xselectionrequest.selection;
+    res.xselection.target = event->xselectionrequest.target;
+    res.xselection.time = event->xselectionrequest.time;
+    XSendEvent(x_display, event->xselectionrequest.requestor, 0, 0, &res);
     XFlush(x_display);
+
+    old_request = next_selection_request;
+    next_selection_request = next_selection_request->next;
+    delete old_request;
+
+    if (process_next_req)
+        handle_selection_request();
 }
 
 static void print_targets(const char *action, Atom *atoms, int c)
@@ -2313,7 +2321,7 @@ static void print_targets(const char *action, Atom *atoms, int c)
 
 static void send_targets(XEvent& request_event)
 {
-    XEvent res;
+    /* TODO Add support for more types here */
     /* Warning the size of this needs to be increased each time we add support
        for a new type, or the atom count of an existing type changes */
     Atom targets[4] = { targets_atom, };
@@ -2339,17 +2347,8 @@ static void send_targets(XEvent& request_event)
     XChangeProperty(x_display, requestor_win, prop, XA_ATOM, 32,
                     PropModeReplace, (unsigned char *)&targets,
                     target_count);
-
-    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);
     print_targets("sent", targets, target_count);
+    send_selection_notify(prop, 1);
 }
 
 static int get_selection(XEvent &event, Atom type, Atom prop, int format,
@@ -2547,6 +2546,44 @@ static void handle_selection_notify(XEvent& event, bool incr)
     get_selection_free(data, incr);
 }
 
+static void handle_selection_request()
+{
+    XEvent *event;
+    uint32_t type = VD_AGENT_CLIPBOARD_NONE;
+
+    if (!next_selection_request)
+        return;
+
+    event = &next_selection_request->event;
+
+    if (Platform::get_clipboard_owner() != Platform::owner_guest) {
+        LOG_INFO("received selection request event for target %s, "
+                 "while clipboard not owned by guest",
+                 atom_name(event->xselectionrequest.target));
+        send_selection_notify(None, 1);
+        return;
+    }
+
+    if (event->xselectionrequest.target == multiple_atom) {
+        LOG_WARN("multiple target not supported");
+        send_selection_notify(None, 1);
+        return;
+    }
+
+    if (event->xselectionrequest.target == targets_atom) {
+        send_targets(*event);
+        return;
+    }
+
+    type = get_clipboard_type(event->xselectionrequest.target);
+    if (type == VD_AGENT_CLIPBOARD_NONE) {
+        send_selection_notify(None, 1);
+        return;
+    }
+
+    clipboard_listener->on_clipboard_request(type);
+}
+
 static void root_win_proc(XEvent& event)
 {
 
@@ -2600,25 +2637,26 @@ static void root_win_proc(XEvent& event)
     switch (event.type) {
     case SelectionRequest: {
         Lock lock(clipboard_lock);
-        XSelectionRequestEvent* selection_request = (XSelectionRequestEvent*)&event;
-        
-        if (selection_request->target == targets_atom) {
-            send_targets(event);
-            break;
-        }
-        
-        clipboard_event = event;
-        uint32_t type = get_clipboard_type(selection_request->target);
-        if (!type) {
-            LOG_INFO("Unsupported selection type %s", atom_name(selection_request->target));
-            send_selection_notify(None);
-            break;
-        }
-        if (clipboard_data_size > 0) {
-            send_selection_notify(selection_request->target);
+        struct selection_request *req, *new_req;
+
+        new_req = new selection_request;
+        assert(new_req);
+
+        new_req->event = event;
+        new_req->next = NULL;
+
+        if (!next_selection_request) {
+            next_selection_request = new_req;
+            handle_selection_request();
             break;
         }
-        clipboard_listener->on_clipboard_request(type);
+
+        /* maybe we should limit the selection_request stack depth ? */
+        req = next_selection_request;
+        while (req->next)
+            req = req->next;
+
+        req->next = new_req;
         break;
     }
     case SelectionClear:
@@ -3327,6 +3365,13 @@ void Platform::set_clipboard_owner(int new_owner)
     /* Clear pending requests and clipboard data */
     {
         Lock lock(clipboard_lock);
+
+        if (next_selection_request) {
+            LOG_INFO("selection requests pending upon clipboard owner change, clearing");
+            while (next_selection_request)
+                send_selection_notify(None, 0);
+        }
+
         clipboard_data_size = 0;
         clipboard_request_target = None;
         waiting_for_property_notify = false;
@@ -3351,23 +3396,38 @@ void Platform::set_clipboard_listener(ClipboardListener* listener)
 bool Platform::on_clipboard_notify(uint32_t type, const uint8_t* data, int32_t size)
 {
     Lock lock(clipboard_lock);
-    /* FIXME (requires selection requests queue */
-    uint32_t target = utf8_atoms[0];
+    Atom prop;
+    XEvent *event;
+    uint32_t type_from_event;
+
+    if (!next_selection_request) {
+        LOG_INFO("received clipboard data without an outstanding"
+                 "selection request, ignoring");
+        return true;
+    }
 
     if (type == VD_AGENT_CLIPBOARD_NONE) {
-        send_selection_notify(None);
+        send_selection_notify(None, 1);
         return true;
     }
 
-    if (size > clipboard_data_space) {
-        delete clipboard_data;
-        clipboard_data = new uint8_t[size];
-        clipboard_data_space = size;
+    event = &next_selection_request->event;
+    type_from_event = get_clipboard_type(event->xselectionrequest.target);
+    if (type_from_event != type) {
+        LOG_WARN("expecting type %u clipboard data got %u",
+                 type_from_event, type);
+        send_selection_notify(None, 1);
+        return false;
     }
-    memcpy(clipboard_data, data, size);
-    clipboard_data_size = size;
-    clipboard_data_type = type;
-    send_selection_notify(target);
+
+    prop = event->xselectionrequest.property;
+    if (prop == None)
+        prop = event->xselectionrequest.target;
+    /* FIXME: use INCR for large data transfers */
+    XChangeProperty(x_display, event->xselectionrequest.requestor, prop,
+                    event->xselectionrequest.target, 8, PropModeReplace,
+                    data, size);
+    send_selection_notify(prop, 1);
     return true;
 }
 
commit 252ded1063c4e8f10d0ebf26ae6bc00031fbcae0
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Sun Oct 3 22:48:08 2010 +0200

    spicec-x11: handle multiple types per grab
    
    And also handle many x11 targets (ie utf8 variants) to a single agent
    type mapping.

diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
index 82660a4..90b086f 100644
--- a/client/x11/platform.cpp
+++ b/client/x11/platform.cpp
@@ -107,6 +107,14 @@ static unsigned int caps_lock_mask = 0;
 static unsigned int num_lock_mask = 0;
 
 //FIXME: nicify
+static const char * const utf8_atom_names[] = {
+    "UTF8_STRING",
+    "text/plain;charset=UTF-8",
+    "text/plain;charset=utf-8",
+};
+
+#define utf8_atom_count (sizeof(utf8_atom_names)/sizeof(utf8_atom_names[0]))
+
 static int expected_targets_notifies = 0;
 static bool waiting_for_property_notify = false;
 static uint8_t* clipboard_data = NULL;
@@ -115,24 +123,19 @@ static int32_t clipboard_data_size = 0;
 static int32_t clipboard_data_space = 0;
 static Atom clipboard_request_target = None;
 static uint32_t clipboard_type_count = 0;
+/* Warning the size of these 2 needs to be increased each time we add
+   support for a new type!! */
+static uint32_t clipboard_agent_types[1];
+static Atom clipboard_x11_targets[1];
 static XEvent clipboard_event;
 static Mutex clipboard_lock;
 static Atom clipboard_prop;
 static Atom incr_atom;
-static Atom utf8_atom;
+static Atom utf8_atoms[utf8_atom_count];
 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() {}
@@ -162,26 +165,36 @@ public:
 static DefaultClipboardListener default_clipboard_listener;
 static Platform::ClipboardListener* clipboard_listener = &default_clipboard_listener;
 
-static uint32_t get_clipboard_type(uint32_t format) {
-    ClipboardFormat* iter;
+static const char *atom_name(Atom atom)
+{
+    if (atom == None)
+        return "None";
 
-    for (iter = clipboard_formats; iter->type && iter->format != format; iter++);
-    return iter->type;
+    return XGetAtomName(x_display, atom);
 }
 
-static uint32_t get_clipboard_format(uint32_t type) {
-    ClipboardFormat* iter;
+static uint32_t get_clipboard_type(Atom target) {
+    int i;
+
+    for (i = 0; i < utf8_atom_count; i++)
+        if (utf8_atoms[i] == target)
+            return VD_AGENT_CLIPBOARD_UTF8_TEXT;
+
+    /* TODO Add support for more types here */
 
-    for (iter = clipboard_formats; iter->format && iter->type != type; iter++);
-    return iter->format;
+    LOG_WARN("unexpected selection type %s", atom_name(target));
+    return VD_AGENT_CLIPBOARD_NONE;
 }
 
-static const char *atom_name(Atom atom)
-{
-    if (atom == None)
-        return "None";
+static Atom get_clipboard_target(uint32_t type) {
+    int i;
 
-    return XGetAtomName(x_display, atom);
+    for (i = 0; i < clipboard_type_count; i++)
+        if (clipboard_agent_types[i] == type)
+            return clipboard_x11_targets[i];
+
+    LOG_WARN("client requested unavailable type %u", type);
+    return None;
 }
 
 NamedPipe::ListenerRef NamedPipe::create(const char *name, ListenerInterface& listener_interface)
@@ -811,15 +824,14 @@ private:
 
 static void intern_clipboard_atoms()
 {
+    int i;
     static bool interned = false;
     if (interned) return;
     clipboard_prop = XInternAtom(x_display, "CLIPBOARD", False);
     incr_atom = XInternAtom(x_display, "INCR", False);
-    utf8_atom = XInternAtom(x_display, "UTF8_STRING", False);
     targets_atom = XInternAtom(x_display, "TARGETS", False);
-
-    clipboard_formats[0].format = utf8_atom;
-    clipboard_formats[0].type = VD_AGENT_CLIPBOARD_UTF8_TEXT;
+    for(i = 0; i < utf8_atom_count; i++)
+        utf8_atoms[i] = XInternAtom(x_display, utf8_atom_names[i], False);
 
     interned = true;
 }
@@ -2302,15 +2314,31 @@ static void print_targets(const char *action, Atom *atoms, int c)
 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 };
+    /* Warning the size of this needs to be increased each time we add support
+       for a new type, or the atom count of an existing type changes */
+    Atom targets[4] = { targets_atom, };
+    int i, j, target_count = 1;
+
+    for (i = 0; i < clipboard_type_count; i++) {
+        switch (clipboard_agent_types[i]) {
+            case VD_AGENT_CLIPBOARD_UTF8_TEXT:
+                for (j = 0; j < utf8_atom_count; j++) {
+                    targets[target_count] = utf8_atoms[j];
+                    target_count++;
+                }
+                break;
+            /* TODO Add support for more types here */
+        }
+    }
 
     Window requestor_win = request_event.xselectionrequest.requestor;
     Atom prop = request_event.xselectionrequest.property;
+    if (prop == None)
+        prop = request_event.xselectionrequest.target;
+
     XChangeProperty(x_display, requestor_win, prop, XA_ATOM, 32,
                     PropModeReplace, (unsigned char *)&targets,
-                    sizeof(targets)/sizeof(Atom));
+                    target_count);
 
     res.xselection.property = prop;
     res.xselection.type = SelectionNotify;
@@ -2321,7 +2349,7 @@ static void send_targets(XEvent& request_event)
     res.xselection.time = request_event.xselectionrequest.time;
     XSendEvent(x_display, requestor_win, 0, 0, &res);
     XFlush(x_display);
-    print_targets("sent", targets, sizeof(targets)/sizeof(Atom));
+    print_targets("sent", targets, target_count);
 }
 
 static int get_selection(XEvent &event, Atom type, Atom prop, int format,
@@ -2434,6 +2462,18 @@ static void get_selection_free(unsigned char *data, bool incr)
         XFree(data);
 }
 
+static Atom atom_lists_overlap(Atom *atoms1, Atom *atoms2, int l1, int l2)
+{
+    int i, j;
+
+    for (i = 0; i < l1; i++)
+        for (j = 0; j < l2; j++)
+            if (atoms1[i] == atoms2[j])
+                return atoms1[i];
+
+    return 0;
+}
+
 static void handle_targets_notify(XEvent& event, bool incr)
 {
     int len;
@@ -2460,13 +2500,13 @@ static void handle_targets_notify(XEvent& event, bool incr)
     /* bytes -> atoms */
     len /= sizeof(Atom);
     print_targets("received", atoms, len);
-#if 0 /* FIXME support multiple types */
+
     clipboard_type_count = 0;
-    atom = atom_lists_overlap(utf8_atoms, atoms, utf8_atom_count, len);
+    Atom atom = atom_lists_overlap(utf8_atoms, atoms, utf8_atom_count, len);
     if (atom) {
         clipboard_agent_types[clipboard_type_count] =
             VD_AGENT_CLIPBOARD_UTF8_TEXT;
-        clipboard_x11_targets[x11->clipboard_type_count] = atom;
+        clipboard_x11_targets[clipboard_type_count] = atom;
         clipboard_type_count++;
     }
 
@@ -2475,10 +2515,7 @@ static void handle_targets_notify(XEvent& event, bool incr)
     if (clipboard_type_count)
         clipboard_listener->on_clipboard_grab(clipboard_agent_types,
                                               clipboard_type_count);
-#else
-    uint32_t type = VD_AGENT_CLIPBOARD_UTF8_TEXT;
-    clipboard_listener->on_clipboard_grab(&type, 1);
-#endif
+
     get_selection_free((unsigned char *)atoms, incr);
 }
 
@@ -3258,13 +3295,24 @@ LocalCursor* Platform::create_default_cursor()
 bool Platform::on_clipboard_grab(uint32_t *types, uint32_t type_count)
 {
     Lock lock(clipboard_lock);
-    /* FIXME use all types rather then just the first one */
-    uint32_t format = get_clipboard_format(types[0]);
+    int i;
+
+    clipboard_type_count = 0;
+    for (i = 0; i < type_count; i++) {
+        /* TODO Add support for more types here */
+        /* Check if we support the type */
+        if (types[i] != VD_AGENT_CLIPBOARD_UTF8_TEXT)
+            continue;
+
+        clipboard_agent_types[clipboard_type_count] = types[i];
+        clipboard_type_count++;
+    }
 
-    if (!format) {
-        LOG_INFO("Unsupported clipboard type %u", types[0]);
+    if (!clipboard_type_count) {
+        LOG_INFO("No supported clipboard types in agent grab");
         return false;
     }
+
     XSetSelectionOwner(x_display, clipboard_prop, platform_win, CurrentTime);
     XFlush(x_display);
     return true;
@@ -3303,17 +3351,14 @@ void Platform::set_clipboard_listener(ClipboardListener* listener)
 bool Platform::on_clipboard_notify(uint32_t type, const uint8_t* data, int32_t size)
 {
     Lock lock(clipboard_lock);
-    uint32_t format = get_clipboard_format(type);
+    /* FIXME (requires selection requests queue */
+    uint32_t target = utf8_atoms[0];
 
     if (type == VD_AGENT_CLIPBOARD_NONE) {
         send_selection_notify(None);
         return true;
     }
 
-    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];
@@ -3322,19 +3367,18 @@ bool Platform::on_clipboard_notify(uint32_t type, const uint8_t* data, int32_t s
     memcpy(clipboard_data, data, size);
     clipboard_data_size = size;
     clipboard_data_type = type;
-    send_selection_notify(format);
+    send_selection_notify(target);
     return true;
 }
 
 bool Platform::on_clipboard_request(uint32_t type)
 {
     Lock lock(clipboard_lock);
-    uint32_t format = get_clipboard_format(type);
+    Atom target = get_clipboard_target(type);
 
-    if (!format) {
-        LOG_INFO("Unsupported clipboard type %u", type);
+    if (target == None)
         return false;
-    }
+
     if (XGetSelectionOwner(x_display, clipboard_prop) == None) {
         LOG_INFO("No owner for the selection");
         return false;
@@ -3344,8 +3388,8 @@ bool Platform::on_clipboard_request(uint32_t type)
         LOG_INFO("XConvertSelection request is already pending");
         return false;
     }
-    clipboard_request_target = format;
-    XConvertSelection(x_display, clipboard_prop, format, clipboard_prop, platform_win, CurrentTime);
+    clipboard_request_target = target;
+    XConvertSelection(x_display, clipboard_prop, target, clipboard_prop, platform_win, CurrentTime);
     XFlush(x_display);
     return true;
 }
commit 57bfa782d8406d84e4b52d8d457a9c6f86316960
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Sun Oct 3 22:08:45 2010 +0200

    spicec-x11: Add XFlush calls were needed
    
    Since we do not always "pump" libX11's event loop by calling
    XPending (we only call XPending when there is data to read from the
    display fd), we must always explictly flush any outstanding requests.
    
    This patch adds a whole bunch of missing XFlush calls.

diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
index 75b34c6..82660a4 100644
--- a/client/x11/platform.cpp
+++ b/client/x11/platform.cpp
@@ -506,6 +506,7 @@ void Platform::error_beep()
     }
 
     XBell(x_display, 0);
+    XFlush(x_display);
 }
 
 void Platform::msleep(unsigned int millisec)
@@ -2028,6 +2029,7 @@ void XMonitor::disable()
     for (; iter != _clones.end(); iter++) {
         (*iter)->disable();
     }
+    XFlush(x_display);
     X_DEBUG_SYNC(display);
 }
 
@@ -2049,6 +2051,7 @@ void XMonitor::enable()
     for (; iter != _clones.end(); iter++) {
         (*iter)->enable();
     }
+    XFlush(x_display);
     X_DEBUG_SYNC(display);
 }
 
@@ -2975,11 +2978,13 @@ static void  set_keyboard_led(XLed led, int set)
     case X11_CAPS_LOCK_LED:
         if (caps_lock_mask) {
             XkbLockModifiers(x_display, XkbUseCoreKbd, caps_lock_mask, set ? caps_lock_mask : 0);
+            XFlush(x_display);
         }
         return;
     case X11_NUM_LOCK_LED:
         if (num_lock_mask) {
             XkbLockModifiers(x_display, XkbUseCoreKbd, num_lock_mask, set ? num_lock_mask : 0);
+            XFlush(x_display);
         }
         return;
     case X11_SCROLL_LOCK_LED:
@@ -2987,6 +2992,7 @@ static void  set_keyboard_led(XLed led, int set)
         keyboard_control.led_mode = set ? LedModeOn : LedModeOff;
         keyboard_control.led = led;
         XChangeKeyboardControl(x_display, KBLed | KBLedMode, &keyboard_control);
+        XFlush(x_display);
         return;
     }
 }
@@ -3034,6 +3040,7 @@ void Platform::reset_cursor_pos()
     SpicePoint size =  primary_monitor->get_size();
     Window root_window = RootWindow(x_display, DefaultScreen(x_display));
     XWarpPointer(x_display, None, root_window, 0, 0, 0, 0, pos.x + size.x / 2, pos.y + size.y / 2);
+    XFlush(x_display);
 }
 
 WaveRecordAbstract* Platform::create_recorder(RecordClient& client,
@@ -3080,6 +3087,7 @@ void XBaseLocalCursor::set(Window window)
 {
     if (_handle) {
         XDefineCursor(x_display, window, _handle);
+        XFlush(x_display);
     }
 }
 
@@ -3258,6 +3266,7 @@ bool Platform::on_clipboard_grab(uint32_t *types, uint32_t type_count)
         return false;
     }
     XSetSelectionOwner(x_display, clipboard_prop, platform_win, CurrentTime);
+    XFlush(x_display);
     return true;
 }
 
@@ -3337,6 +3346,7 @@ bool Platform::on_clipboard_request(uint32_t type)
     }
     clipboard_request_target = format;
     XConvertSelection(x_display, clipboard_prop, format, clipboard_prop, platform_win, CurrentTime);
+    XFlush(x_display);
     return true;
 }
 
commit aabca2864d9439bc061feedd8d315b9aaac5c666
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Sun Oct 3 21:45:45 2010 +0200

    spicec-x11: Force processing of ownerchange event when releasing the cb
    
    Make sure we process the XFixesSetSelectionOwnerNotify event caused by
    us setting the clipboard owner to none, directly after setting the owner
    to none. Otherwise we may end up changing the clipboard owner to none, after
    it has already been re-owned because the XFixesSetSelectionOwnerNotify event
    to owner none is event is still pending when we set the new owner, and
    then changes the owner back to none once processed messing up our clipboard
    ownership state tracking.
    
    I saw this happening when doing copy twice in succession inside the guest.

diff --git a/client/red_client.cpp b/client/red_client.cpp
index a022499..8e7dfe0 100644
--- a/client/red_client.cpp
+++ b/client/red_client.cpp
@@ -1133,12 +1133,11 @@ void RedClient::dispatch_agent_message(VDAgentMessage* msg, void* data)
         break;
     case VD_AGENT_CLIPBOARD_RELEASE:
         if (Platform::get_clipboard_owner() != Platform::owner_guest) {
-            LOG_WARN("received clipboard release from guest while clipboard is not owned by guest");
+            LOG_INFO("received clipboard release from guest while clipboard is not owned by guest");
             break;
         }
 
         Platform::on_clipboard_release();
-        Platform::set_clipboard_owner(Platform::owner_none);
         break;
     default:
         DBG(0, "Unsupported message type %u size %u", msg->type, msg->size);
diff --git a/client/windows/platform.cpp b/client/windows/platform.cpp
index db47079..580a40a 100644
--- a/client/windows/platform.cpp
+++ b/client/windows/platform.cpp
@@ -1011,6 +1011,7 @@ bool Platform::on_clipboard_request(uint32_t type)
 void Platform::on_clipboard_release()
 {
     SetEvent(clipboard_event);
+    set_clipboard_owner(owner_none);
 }
 
 static bool has_console = false;
diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
index ea2558d..75b34c6 100644
--- a/client/x11/platform.cpp
+++ b/client/x11/platform.cpp
@@ -3342,5 +3342,18 @@ bool Platform::on_clipboard_request(uint32_t type)
 
 void Platform::on_clipboard_release()
 {
+    XEvent event;
+
     XSetSelectionOwner(x_display, clipboard_prop, None, CurrentTime);
+    /* Make sure we process the XFixesSetSelectionOwnerNotify event caused
+       by this, so we don't end up changing the clipboard owner to none, after
+       it has already been re-owned because this event is still pending. */
+    XSync(x_display, False);
+    while (XCheckTypedEvent(x_display,
+                            XFixesSelectionNotify + xfixes_event_base,
+                            &event))
+        root_win_proc(event);
+
+    /* Note no need to do a set_clipboard_owner(owner_none) here, as that is
+       already done by processing the XFixesSetSelectionOwnerNotify event. */
 }
commit a2492d5ae347941b7a5bc13ad5a4de1c006b4ff0
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Sun Oct 3 21:31:03 2010 +0200

    spicec-x11: Request targets from new clipboard owner
    
    Request targets from new clipboard owner, rather then assuming UTF-8
    (not entirely complete yet, the last pieces will be in another patch).
    
    Atleast as important this code unifies the selection getting code
    for incr and non incr getting of selection data so that it can be
    used for both getting regular selection data and for getting targets
    selection data.
    
    This also fixes a big bug in the (I believe untested sofar) incr support
    code which was interpreting the contents of PropertyNotify events as
    XSelectionEvents rather then as XpropertyEvents which are completely
    differen structs!

diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
index 4385f85..ea2558d 100644
--- a/client/x11/platform.cpp
+++ b/client/x11/platform.cpp
@@ -107,11 +107,14 @@ static unsigned int caps_lock_mask = 0;
 static unsigned int num_lock_mask = 0;
 
 //FIXME: nicify
+static int expected_targets_notifies = 0;
+static bool waiting_for_property_notify = false;
 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 Atom clipboard_request_target = None;
+static uint32_t clipboard_type_count = 0;
 static XEvent clipboard_event;
 static Mutex clipboard_lock;
 static Atom clipboard_prop;
@@ -2260,48 +2263,6 @@ static void realloc_clipboard_data_space(uint32_t size)
     clipboard_data = newbuf;
 }
 
-static void update_clipboard(unsigned long size, uint8_t* data)
-{
-    clipboard_data_size = 0;
-    ensure_clipboard_data_space(size);
-    memcpy(clipboard_data, data, size);
-    clipboard_data_size = size;
-}
-
-// 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);
-    }
-    memcpy((char*)clipboard_data + clipboard_data_size, (char*)value, length);
-    clipboard_data_size += length;
-    LOG_INFO("Appended %d bytes to buffer", length);
-    return true;
-}
-
 // FIXME: use INCR for large data transfers
 static void send_selection_notify(Atom type)
 {
@@ -2326,6 +2287,15 @@ static void send_selection_notify(Atom type)
     XFlush(x_display);
 }
 
+static void print_targets(const char *action, Atom *atoms, int c)
+{
+    int i;
+
+    LOG_INFO("%s %d targets:", action, c);
+    for (i = 0; i < c; i++)
+        LOG_INFO("%s", atom_name(atoms[i]));
+}
+
 static void send_targets(XEvent& request_event)
 {
     XEvent res;
@@ -2348,11 +2318,197 @@ static void send_targets(XEvent& request_event)
     res.xselection.time = request_event.xselectionrequest.time;
     XSendEvent(x_display, requestor_win, 0, 0, &res);
     XFlush(x_display);
+    print_targets("sent", targets, sizeof(targets)/sizeof(Atom));
+}
+
+static int get_selection(XEvent &event, Atom type, Atom prop, int format,
+                         unsigned char **data_ret, bool incr)
+{
+    Bool del = incr ? True: False;
+    Atom type_ret;
+    int format_ret, ret_val = -1;
+    unsigned long len, remain;
+    unsigned char *data = NULL;
+
+    if (incr) {
+        if (event.xproperty.atom != prop) {
+            LOG_WARN("PropertyNotify parameters mismatch");
+            goto exit;
+        }
+    } else {
+        if (event.xselection.property == None) {
+            LOG_INFO("XConvertSelection refused by clipboard owner");
+            goto exit;
+        }
+
+        if (event.xselection.requestor != platform_win ||
+            event.xselection.selection != clipboard_prop ||
+            event.xselection.property  != prop) {
+            LOG_WARN("SelectionNotify parameters mismatch");
+            goto exit;
+        }
+    }
+
+    if (XGetWindowProperty(x_display, platform_win, prop, 0,
+                           LONG_MAX, del, type, &type_ret, &format_ret, &len,
+                           &remain, &data) != Success) {
+        LOG_WARN("XGetWindowProperty failed");
+        goto exit;
+    }
+
+    if (!incr) {
+        if (type_ret == incr_atom) {
+            XSelectInput(x_display, platform_win, PropertyChangeMask);
+            XDeleteProperty(x_display, platform_win, prop);
+            XFlush(x_display);
+            waiting_for_property_notify = true;
+            ensure_clipboard_data_space(*(uint32_t*)data);
+            XFree(data);
+            return 0; /* Wait for more data */
+        }
+        XDeleteProperty(x_display, platform_win, prop);
+        XFlush(x_display);
+    }
+
+    if (type_ret != type) {
+        LOG_WARN("expected property type: %s, got: %s", atom_name(type),
+                 atom_name(type_ret));
+        goto exit;
+    }
+
+    if (format_ret != format) {
+        LOG_WARN("expected %d bit format, got %d bits", format, format_ret);
+        goto exit;
+    }
+
+    /* Convert len to bytes */
+    switch(format) {
+    case 8:
+        break;
+    case 16:
+        len *= sizeof(short);
+        break;
+    case 32:
+        len *= sizeof(long);
+        break;
+    }
+
+    if (incr) {
+        if (len) {
+            if (clipboard_data_size + len > clipboard_data_space)
+                realloc_clipboard_data_space(clipboard_data_size + len);
+            memcpy(clipboard_data + clipboard_data_size, data, len);
+            clipboard_data_size += len;
+            LOG_INFO("Appended %d bytes to buffer", len);
+            XFree(data);
+            return 0; /* Wait for more data */
+        }
+        len = clipboard_data_size;
+        *data_ret = clipboard_data;
+    } else
+        *data_ret = data;
+
+    if (len > 0)
+        ret_val = len;
+    else
+        LOG_WARN("property contains no data (zero length)");
+
+exit:
+    if ((incr || ret_val == -1) && data)
+        XFree(data);
+
+    if (incr) {
+        clipboard_data_size = 0;
+        waiting_for_property_notify = false;
+    }
+
+    return ret_val;
+}
+
+static void get_selection_free(unsigned char *data, bool incr)
+{
+    if (!incr && data)
+        XFree(data);
+}
+
+static void handle_targets_notify(XEvent& event, bool incr)
+{
+    int len;
+    Lock lock(clipboard_lock);
+    Atom *atoms = NULL;
+
+    if (!expected_targets_notifies) {
+        LOG_WARN("unexpected selection notify TARGETS");
+        return;
+    }
+    expected_targets_notifies--;
+
+    /* If we have more targets_notifies pending, ignore this one, we
+       are only interested in the targets list of the current owner
+       (which is the last one we've requested a targets list from) */
+    if (expected_targets_notifies)
+        return;
+
+    len = get_selection(event, XA_ATOM, targets_atom, 32,
+                        (unsigned char **)&atoms, incr);
+    if (len == 0 || len == -1) /* waiting for more data or error? */
+        return;
+
+    /* bytes -> atoms */
+    len /= sizeof(Atom);
+    print_targets("received", atoms, len);
+#if 0 /* FIXME support multiple types */
+    clipboard_type_count = 0;
+    atom = atom_lists_overlap(utf8_atoms, atoms, utf8_atom_count, len);
+    if (atom) {
+        clipboard_agent_types[clipboard_type_count] =
+            VD_AGENT_CLIPBOARD_UTF8_TEXT;
+        clipboard_x11_targets[x11->clipboard_type_count] = atom;
+        clipboard_type_count++;
+    }
+
+    /* TODO Add support for more types here */
+
+    if (clipboard_type_count)
+        clipboard_listener->on_clipboard_grab(clipboard_agent_types,
+                                              clipboard_type_count);
+#else
+    uint32_t type = VD_AGENT_CLIPBOARD_UTF8_TEXT;
+    clipboard_listener->on_clipboard_grab(&type, 1);
+#endif
+    get_selection_free((unsigned char *)atoms, incr);
+}
+
+static void handle_selection_notify(XEvent& event, bool incr)
+{
+    int len = -1;
+    uint32_t type = get_clipboard_type(clipboard_request_target);
+    unsigned char *data = NULL;
+
+    if (clipboard_request_target == None)
+        LOG_INFO("SelectionNotify received without a target");
+    else if (!incr && event.xselection.target != clipboard_request_target)
+        LOG_WARN("Requested %s target got %s",
+                 atom_name(clipboard_request_target),
+                 atom_name(event.xselection.target));
+    else
+        len = get_selection(event, clipboard_request_target, clipboard_prop,
+                            8, &data, incr);
+
+    if (len == 0) /* waiting for more data? */
+        return;
+    if (len == -1) {
+        type = VD_AGENT_CLIPBOARD_NONE;
+        len = 0;
+    }
+
+    clipboard_listener->on_clipboard_notify(type, data, len);
+    clipboard_request_target = None;
+    get_selection_free(data, incr);
 }
 
 static void root_win_proc(XEvent& event)
 {
-    static bool waiting_for_property_notify = false;
 
 #ifdef USE_XRANDR_1_2
     ASSERT(using_xrandr_1_0 || using_xrandr_1_2);
@@ -2394,11 +2550,11 @@ static void root_win_proc(XEvent& event)
         if (selection_event->owner == None)
             return;
 
-        // FIXME: request targets from new owner, then on receiving the
-        // targets see if there are usable types and only if there are
-        // grab the clipboard with the found usable types
-        uint32_t type = VD_AGENT_CLIPBOARD_UTF8_TEXT;
-        clipboard_listener->on_clipboard_grab(&type, 1);
+        /* Request the supported targets from the new owner */
+        XConvertSelection(x_display, clipboard_prop, targets_atom,
+                          targets_atom, platform_win, CurrentTime);
+        XFlush(x_display);
+        expected_targets_notifies++;
         return;
     }
     switch (event.type) {
@@ -2429,79 +2585,21 @@ static void root_win_proc(XEvent& event)
         /* Do nothing the clipboard ownership will get updated through
            the XFixesSetSelectionOwnerNotify event */
         break;
-    case SelectionNotify: {
-        Atom type;
-        int format;
-        unsigned long len;
-        unsigned long size;
-        unsigned long dummy;
-        unsigned char *data;
-
-        XGetWindowProperty(x_display, platform_win, clipboard_prop, 0, 0, False,
-                           event.xselection.target, &type, &format, &len, &size, &data);
-        if (size == 0) {
-            LOG_INFO("XGetWindowProperty(size) failed");
-            break;
-        }
-        if (type != event.xselection.target && type != incr_atom) {
-            LOG_INFO("type %d != %u requested", type, event.xselection.target);
-            break;
-        }
-        {
-            Lock lock(clipboard_lock);
-            clipboard_data_size = 0;
-        }
-        if (type == incr_atom) {
-            Window requestor_win = event.xselection.requestor;
-            Atom prop = event.xselection.property; // is this always "CLIPBOARD"?
-            // According to ICCCM spec 2.7.2 INCR Properties, and xsel reference
-            XSelectInput(x_display, requestor_win, PropertyChangeMask);
-            XDeleteProperty(x_display, requestor_win, prop);
-            waiting_for_property_notify = true;
-            {
-                Lock lock(clipboard_lock);
-                ensure_clipboard_data_space(*(uint32_t*)data);
-            }
-            break;
-        }
-        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);
-        }
-        clipboard_listener->on_clipboard_notify(clipboard_request_type, clipboard_data,
-                                                clipboard_data_size);
-        clipboard_request_type = 0;
-        XFree(data);
+    case SelectionNotify:
+        if (event.xselection.target == targets_atom)
+            handle_targets_notify(event, false);
+        else
+            handle_selection_notify(event, false);
         break;
-    }
-    case PropertyNotify: {
+    case PropertyNotify:
         if (!waiting_for_property_notify || 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_notify(clipboard_request_type, clipboard_data,
-                                                    clipboard_data_size);
-            clipboard_request_type = 0;
-        }
+        if (event.xproperty.atom == targets_atom)
+            handle_targets_notify(event, true);
+        else
+            handle_selection_notify(event, true);
         break;
-    }
     default:
         return;
     }
@@ -3159,7 +3257,6 @@ bool Platform::on_clipboard_grab(uint32_t *types, uint32_t type_count)
         LOG_INFO("Unsupported clipboard type %u", types[0]);
         return false;
     }
-    clipboard_data_size = 0;
     XSetSelectionOwner(x_display, clipboard_prop, platform_win, CurrentTime);
     return true;
 }
@@ -3168,12 +3265,25 @@ int Platform::_clipboard_owner = Platform::owner_none;
 
 void Platform::set_clipboard_owner(int new_owner)
 {
-    if (new_owner == owner_none) {
-        clipboard_listener->on_clipboard_release();
+    const char * const owner_str[] = { "none", "guest", "client" };
+
+    /* Clear pending requests and clipboard data */
+    {
+        Lock lock(clipboard_lock);
+        clipboard_data_size = 0;
+        clipboard_request_target = None;
+        waiting_for_property_notify = false;
 
-        /* FIXME clear cached clipboard type info and data */
+        /* Clear cached clipboard type info when there is no new owner
+           (otherwise the new owner will already have set new type info) */
+        if (new_owner == owner_none)
+            clipboard_type_count = 0;
     }
+    if (new_owner == owner_none)
+        clipboard_listener->on_clipboard_release();
+
     _clipboard_owner = new_owner;
+    LOG_INFO("new clipboard owner: %s", owner_str[new_owner]);
 }
 
 void Platform::set_clipboard_listener(ClipboardListener* listener)
@@ -3186,6 +3296,11 @@ bool Platform::on_clipboard_notify(uint32_t type, const uint8_t* data, int32_t s
     Lock lock(clipboard_lock);
     uint32_t format = get_clipboard_format(type);
 
+    if (type == VD_AGENT_CLIPBOARD_NONE) {
+        send_selection_notify(None);
+        return true;
+    }
+
     if (!format) {
         LOG_INFO("Unsupported clipboard type %u", type);
         return false;
@@ -3204,6 +3319,7 @@ bool Platform::on_clipboard_notify(uint32_t type, const uint8_t* data, int32_t s
 
 bool Platform::on_clipboard_request(uint32_t type)
 {
+    Lock lock(clipboard_lock);
     uint32_t format = get_clipboard_format(type);
 
     if (!format) {
@@ -3214,11 +3330,12 @@ bool Platform::on_clipboard_request(uint32_t type)
         LOG_INFO("No owner for the selection");
         return false;
     }
-    if (clipboard_request_type) {
+
+    if (clipboard_request_target) {
         LOG_INFO("XConvertSelection request is already pending");
         return false;
     }
-    clipboard_request_type = type;
+    clipboard_request_target = format;
     XConvertSelection(x_display, clipboard_prop, format, clipboard_prop, platform_win, CurrentTime);
     return true;
 }
commit a3bf9d331d90ac5acaf0247dd68222976c3a4b1b
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Sun Oct 3 10:42:17 2010 +0200

    spicec-x11: remove clipboard_changer hack
    
    Instead of keeping a flag, we can and should simply check wether the
    new owner reported in the event it us or not. Also check for the
    new owner being none and send a clipboard_release when that is the
    case (through set_clipboard_owner(owner_none)).

diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
index 832374a..4385f85 100644
--- a/client/x11/platform.cpp
+++ b/client/x11/platform.cpp
@@ -112,7 +112,6 @@ 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;
@@ -831,14 +830,13 @@ DynamicScreen::DynamicScreen(Display* display, int screen, int& next_mon_id)
     X_DEBUG_SYNC(display);
     //FIXME: replace RootWindow() in other refs as well?
     platform_win = XCreateSimpleWindow(display, RootWindow(display, screen), 0, 0, 1, 1, 0, 0, 0);
+    LOG_INFO("platform_win: %u", (unsigned int)platform_win);
     intern_clipboard_atoms();
     XSelectInput(display, platform_win, StructureNotifyMask);
     XRRSelectInput(display, platform_win, RRScreenChangeNotifyMask);
     if (using_xfixes_1_0) {
         XFixesSelectSelectionInput(display, platform_win, clipboard_prop,
-                                   XFixesSetSelectionOwnerNotifyMask |
-                                   XFixesSelectionWindowDestroyNotifyMask |
-                                   XFixesSelectionClientCloseNotifyMask);
+                                   XFixesSetSelectionOwnerNotifyMask);
     }
 
     Monitor::self_monitors_change++;
@@ -1107,6 +1105,7 @@ MultyMonScreen::MultyMonScreen(Display* display, int screen, int& next_mon_id)
     }
 
     platform_win = XCreateSimpleWindow(display, RootWindow(display, screen), 0, 0, 1, 1, 0, 0, 0);
+    LOG_INFO("platform_win: %u", (unsigned int)platform_win);
     intern_clipboard_atoms();
     XSelectInput(display, platform_win, StructureNotifyMask);
     X_DEBUG_SYNC(get_display());
@@ -1114,9 +1113,7 @@ MultyMonScreen::MultyMonScreen(Display* display, int screen, int& next_mon_id)
     X_DEBUG_SYNC(get_display());
     if (using_xfixes_1_0) {
         XFixesSelectSelectionInput(display, platform_win, clipboard_prop,
-                                   XFixesSetSelectionOwnerNotifyMask |
-                                   XFixesSelectionWindowDestroyNotifyMask |
-                                   XFixesSelectionClientCloseNotifyMask);
+                                   XFixesSetSelectionOwnerNotifyMask);
     }
 
     XMonitor::inc_change_ref();
@@ -2382,17 +2379,24 @@ static void root_win_proc(XEvent& event)
     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;
+        LOG_INFO("XFixesSetSelectionOwnerNotify %u",
+                 (unsigned int)selection_event->owner);
+
+        /* Ignore becoming the owner ourselves */
+        if (selection_event->owner == platform_win)
             return;
-        }
-        // FIXME: use actual type
+
+        /* If the clipboard owner is changed we no longer own it */
+        Platform::set_clipboard_owner(Platform::owner_none);
+        if (selection_event->owner == None)
+            return;
+
+        // FIXME: request targets from new owner, then on receiving the
+        // targets see if there are usable types and only if there are
+        // grab the clipboard with the found usable types
         uint32_t type = VD_AGENT_CLIPBOARD_UTF8_TEXT;
         clipboard_listener->on_clipboard_grab(&type, 1);
         return;
@@ -2421,11 +2425,10 @@ static void root_win_proc(XEvent& event)
         clipboard_listener->on_clipboard_request(type);
         break;
     }
-    case SelectionClear: {
-        Lock lock(clipboard_lock);
-        clipboard_data_size = 0;
+    case SelectionClear:
+        /* Do nothing the clipboard ownership will get updated through
+           the XFixesSetSelectionOwnerNotify event */
         break;
-    }
     case SelectionNotify: {
         Atom type;
         int format;
@@ -3156,7 +3159,6 @@ bool Platform::on_clipboard_grab(uint32_t *types, uint32_t type_count)
         LOG_INFO("Unsupported clipboard type %u", types[0]);
         return false;
     }
-    clipboard_changer = true;
     clipboard_data_size = 0;
     XSetSelectionOwner(x_display, clipboard_prop, platform_win, CurrentTime);
     return true;
commit 8a160078d0852ff5a2f8ec438cec1b18858e3c5d
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Sat Oct 2 16:29:36 2010 +0200

    Keep track of clipboard ownership
    
    Given that all clipboard handling is async, it is possible to for
    example receive a request for clipboard data from the agent
    while the client no longer owns the clipboard (ie a
    VD_AGENT_CLIPBOARD_RELEASE message is in transit to the agent).
    
    Thus it is necessary to keep track of our notion of clipboard ownership
    and check received clipboard messages (both from other apps on the client
    machine and from the agent) to see if they match our notion and if not
    drop, or in case were a counter message is expected nack the clipboard
    message.

diff --git a/client/platform.h b/client/platform.h
index ff6f38d..af4a0f6 100644
--- a/client/platform.h
+++ b/client/platform.h
@@ -125,6 +125,14 @@ public:
     static bool on_clipboard_notify(uint32_t type, const uint8_t* data, int32_t size);
     static bool on_clipboard_request(uint32_t type);
     static void on_clipboard_release();
+
+    enum { owner_none, owner_guest, owner_client };
+
+    static void set_clipboard_owner(int new_owner);
+    static int  get_clipboard_owner() { return _clipboard_owner; }
+
+private:
+    static int _clipboard_owner;
 };
 
 class Platform::EventListener {
@@ -141,6 +149,7 @@ public:
     virtual void on_clipboard_grab(uint32_t *types, uint32_t type_count) = 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;
+    virtual void on_clipboard_release() = 0;
 };
 
 class Platform::RecordClient {
diff --git a/client/red_client.cpp b/client/red_client.cpp
index 3f01fb1..a022499 100644
--- a/client/red_client.cpp
+++ b/client/red_client.cpp
@@ -85,10 +85,17 @@ void ClipboardGrabEvent::response(AbstractProcessLoop& events_loop)
 {
     static_cast<RedClient*>(events_loop.get_owner())->send_agent_clipboard_message(
         VD_AGENT_CLIPBOARD_GRAB, _type_count * sizeof(uint32_t), _types);
+    Platform::set_clipboard_owner(Platform::owner_client);
 }
 
 void ClipboardRequestEvent::response(AbstractProcessLoop& events_loop)
 {
+    if (Platform::get_clipboard_owner() != Platform::owner_guest) {
+        LOG_WARN("received clipboard req from client while clipboard is not owned by guest");
+        Platform::on_clipboard_notify(VD_AGENT_CLIPBOARD_NONE, NULL, 0);
+        return;
+    }
+
     VDAgentClipboardRequest request = {_type};
     static_cast<RedClient*>(events_loop.get_owner())->send_agent_clipboard_message(
         VD_AGENT_CLIPBOARD_REQUEST, sizeof(request), &request);
@@ -865,6 +872,11 @@ void RedClient::on_clipboard_notify(uint32_t type, uint8_t* data, int32_t size)
         DBG(0, "clipboard change is already pending");
         return;
     }
+    if (Platform::get_clipboard_owner() != Platform::owner_client) {
+        LOG_WARN("received clipboard data from client while clipboard is not owned by client");
+        type = VD_AGENT_CLIPBOARD_NONE;
+        size = 0;
+    }
     _agent_out_msg_pos = 0;
     _agent_out_msg_size = sizeof(VDAgentMessage) + sizeof(VDAgentClipboard) + size;
     _agent_out_msg = (VDAgentMessage*)new uint8_t[_agent_out_msg_size];
@@ -880,6 +892,12 @@ void RedClient::on_clipboard_notify(uint32_t type, uint8_t* data, int32_t size)
     }
 }
 
+void RedClient::on_clipboard_release()
+{
+    if (Platform::get_clipboard_owner() == Platform::owner_client)
+        send_agent_clipboard_message(VD_AGENT_CLIPBOARD_RELEASE, 0, NULL);
+}
+
 void RedClient::set_mouse_mode(uint32_t supported_modes, uint32_t current_mode)
 {
     if (current_mode != _mouse_mode) {
@@ -1086,6 +1104,12 @@ void RedClient::dispatch_agent_message(VDAgentMessage* msg, void* data)
         break;
     }
     case VD_AGENT_CLIPBOARD: {
+        if (Platform::get_clipboard_owner() != Platform::owner_guest) {
+            LOG_WARN("received clipboard data from guest while clipboard is not owned by guest");
+            Platform::on_clipboard_notify(VD_AGENT_CLIPBOARD_NONE, NULL, 0);
+            break;
+        }
+
         VDAgentClipboard* clipboard = (VDAgentClipboard*)data;
         Platform::on_clipboard_notify(clipboard->type, clipboard->data,
                                      msg->size - sizeof(VDAgentClipboard));
@@ -1094,14 +1118,27 @@ void RedClient::dispatch_agent_message(VDAgentMessage* msg, void* data)
     case VD_AGENT_CLIPBOARD_GRAB:
         Platform::on_clipboard_grab((uint32_t *)data,
                                       msg->size / sizeof(uint32_t));
+        Platform::set_clipboard_owner(Platform::owner_guest);
         break;
     case VD_AGENT_CLIPBOARD_REQUEST:
+        if (Platform::get_clipboard_owner() != Platform::owner_client) {
+            LOG_WARN("received clipboard req from guest while clipboard is not owned by client");
+            on_clipboard_notify(VD_AGENT_CLIPBOARD_NONE, NULL, 0);
+            break;
+        }
+
         if (!Platform::on_clipboard_request(((VDAgentClipboardRequest*)data)->type)) {
             on_clipboard_notify(VD_AGENT_CLIPBOARD_NONE, NULL, 0);
         }
         break;
     case VD_AGENT_CLIPBOARD_RELEASE:
+        if (Platform::get_clipboard_owner() != Platform::owner_guest) {
+            LOG_WARN("received clipboard release from guest while clipboard is not owned by guest");
+            break;
+        }
+
         Platform::on_clipboard_release();
+        Platform::set_clipboard_owner(Platform::owner_none);
         break;
     default:
         DBG(0, "Unsupported message type %u size %u", msg->type, msg->size);
diff --git a/client/red_client.h b/client/red_client.h
index 2cfce49..15f0617 100644
--- a/client/red_client.h
+++ b/client/red_client.h
@@ -221,6 +221,7 @@ public:
     void on_clipboard_grab(uint32_t *types, uint32_t type_count);
     void on_clipboard_request(uint32_t type);
     void on_clipboard_notify(uint32_t type, uint8_t* data, int32_t size);
+    void on_clipboard_release();
 
     void for_each_channel(ForEachChannelFunc& func);
     void on_mouse_capture_trigger(RedScreen& screen);
diff --git a/client/windows/platform.cpp b/client/windows/platform.cpp
index db6d18a..db47079 100644
--- a/client/windows/platform.cpp
+++ b/client/windows/platform.cpp
@@ -54,6 +54,7 @@ public:
     virtual void on_clipboard_grab(uint32_t *types, uint32_t type_count) {}
     virtual void on_clipboard_request(uint32_t type) {}
     virtual void on_clipboard_notify(uint32_t type, uint8_t* data, int32_t size) {}
+    virtual void on_clipboard_release() {}
 };
 
 static DefaultClipboardListener default_clipboard_listener;
@@ -856,6 +857,18 @@ void WinPlatform::exit_modal_loop()
     modal_loop_active = false;
 }
 
+int Platform::_clipboard_owner = Platform::owner_none;
+
+void Platform::set_clipboard_owner(int new_owner)
+{
+    if (new_owner == owner_none) {
+        clipboard_listener->on_clipboard_release();
+
+        /* FIXME clear cached clipboard type info and data */
+    }
+    _clipboard_owner = new_owner;
+}
+
 bool Platform::on_clipboard_grab(uint32_t *types, uint32_t type_count)
 {
     /* FIXME use all types rather then just the first one */
diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
index b7216eb..832374a 100644
--- a/client/x11/platform.cpp
+++ b/client/x11/platform.cpp
@@ -154,6 +154,7 @@ public:
     void on_clipboard_grab(uint32_t *types, uint32_t type_count) {}
     void on_clipboard_request(uint32_t type) {}
     void on_clipboard_notify(uint32_t type, uint8_t* data, int32_t size) {}
+    void on_clipboard_release() {}
 };
 
 static DefaultClipboardListener default_clipboard_listener;
@@ -3161,6 +3162,18 @@ bool Platform::on_clipboard_grab(uint32_t *types, uint32_t type_count)
     return true;
 }
 
+int Platform::_clipboard_owner = Platform::owner_none;
+
+void Platform::set_clipboard_owner(int new_owner)
+{
+    if (new_owner == owner_none) {
+        clipboard_listener->on_clipboard_release();
+
+        /* FIXME clear cached clipboard type info and data */
+    }
+    _clipboard_owner = new_owner;
+}
+
 void Platform::set_clipboard_listener(ClipboardListener* listener)
 {
     clipboard_listener = listener ? listener : &default_clipboard_listener;
commit deb849dfd54deafa894692f4e44208c2de9f33aa
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Sat Oct 2 13:03:50 2010 +0200

    Rename platform clipboard handling functions
    
    Rename the 4 platform clipboard functions which get called
    upon receival of an agent clipboard message to on_clipboard_*
    
    The old set_clipboard_* names were confusing as they suggest being
    a class property setter (like set_event_listener) rather then
    event handler, and set_clipboard_owner was causing a name conflict
    with the next patch in this series.

diff --git a/client/platform.h b/client/platform.h
index 48476cf..ff6f38d 100644
--- a/client/platform.h
+++ b/client/platform.h
@@ -121,10 +121,10 @@ public:
     class ClipboardListener;
     static void set_clipboard_listener(ClipboardListener* listener);
 
-    static bool set_clipboard_owner(uint32_t *types, uint32_t type_count);
-    static bool set_clipboard_data(uint32_t type, const uint8_t* data, int32_t size);
-    static bool request_clipboard_notification(uint32_t type);
-    static void release_clipboard();
+    static bool on_clipboard_grab(uint32_t *types, uint32_t type_count);
+    static bool on_clipboard_notify(uint32_t type, const uint8_t* data, int32_t size);
+    static bool on_clipboard_request(uint32_t type);
+    static void on_clipboard_release();
 };
 
 class Platform::EventListener {
diff --git a/client/red_client.cpp b/client/red_client.cpp
index 666de40..3f01fb1 100644
--- a/client/red_client.cpp
+++ b/client/red_client.cpp
@@ -1087,21 +1087,21 @@ void RedClient::dispatch_agent_message(VDAgentMessage* msg, void* data)
     }
     case VD_AGENT_CLIPBOARD: {
         VDAgentClipboard* clipboard = (VDAgentClipboard*)data;
-        Platform::set_clipboard_data(clipboard->type, clipboard->data,
+        Platform::on_clipboard_notify(clipboard->type, clipboard->data,
                                      msg->size - sizeof(VDAgentClipboard));
         break;
     }
     case VD_AGENT_CLIPBOARD_GRAB:
-        Platform::set_clipboard_owner((uint32_t *)data,
+        Platform::on_clipboard_grab((uint32_t *)data,
                                       msg->size / sizeof(uint32_t));
         break;
     case VD_AGENT_CLIPBOARD_REQUEST:
-        if (!Platform::request_clipboard_notification(((VDAgentClipboardRequest*)data)->type)) {
+        if (!Platform::on_clipboard_request(((VDAgentClipboardRequest*)data)->type)) {
             on_clipboard_notify(VD_AGENT_CLIPBOARD_NONE, NULL, 0);
         }
         break;
     case VD_AGENT_CLIPBOARD_RELEASE:
-        Platform::release_clipboard();
+        Platform::on_clipboard_release();
         break;
     default:
         DBG(0, "Unsupported message type %u size %u", msg->type, msg->size);
diff --git a/client/windows/platform.cpp b/client/windows/platform.cpp
index 2a62944..db6d18a 100644
--- a/client/windows/platform.cpp
+++ b/client/windows/platform.cpp
@@ -856,7 +856,7 @@ void WinPlatform::exit_modal_loop()
     modal_loop_active = false;
 }
 
-bool Platform::set_clipboard_owner(uint32_t *types, uint32_t type_count)
+bool Platform::on_clipboard_grab(uint32_t *types, uint32_t type_count)
 {
     /* FIXME use all types rather then just the first one */
     uint32_t format = get_clipboard_format(types[0]);
@@ -880,7 +880,7 @@ 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)
+bool Platform::on_clipboard_notify(uint32_t type, const uint8_t* data, int32_t size)
 {
     HGLOBAL clip_data;
     LPVOID clip_buf;
@@ -946,7 +946,7 @@ bool Platform::set_clipboard_data(uint32_t type, const uint8_t* data, int32_t si
     return ret;
 }
 
-bool Platform::request_clipboard_notification(uint32_t type)
+bool Platform::on_clipboard_request(uint32_t type)
 {
     UINT format = get_clipboard_format(type);
     HANDLE clip_data;
@@ -995,7 +995,7 @@ bool Platform::request_clipboard_notification(uint32_t type)
     return ret;
 }
 
-void Platform::release_clipboard()
+void Platform::on_clipboard_release()
 {
     SetEvent(clipboard_event);
 }
diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
index 6627187..b7216eb 100644
--- a/client/x11/platform.cpp
+++ b/client/x11/platform.cpp
@@ -3145,7 +3145,7 @@ LocalCursor* Platform::create_default_cursor()
     return new XDefaultCursor();
 }
 
-bool Platform::set_clipboard_owner(uint32_t *types, uint32_t type_count)
+bool Platform::on_clipboard_grab(uint32_t *types, uint32_t type_count)
 {
     Lock lock(clipboard_lock);
     /* FIXME use all types rather then just the first one */
@@ -3166,7 +3166,7 @@ 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)
+bool Platform::on_clipboard_notify(uint32_t type, const uint8_t* data, int32_t size)
 {
     Lock lock(clipboard_lock);
     uint32_t format = get_clipboard_format(type);
@@ -3187,7 +3187,7 @@ bool Platform::set_clipboard_data(uint32_t type, const uint8_t* data, int32_t si
     return true;
 }
 
-bool Platform::request_clipboard_notification(uint32_t type)
+bool Platform::on_clipboard_request(uint32_t type)
 {
     uint32_t format = get_clipboard_format(type);
 
@@ -3208,7 +3208,7 @@ bool Platform::request_clipboard_notification(uint32_t type)
     return true;
 }
 
-void Platform::release_clipboard()
+void Platform::on_clipboard_release()
 {
     XSetSelectionOwner(x_display, clipboard_prop, None, CurrentTime);
 }
commit 5781c97a1751e8e889b30dcce4641fb30508e072
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Sat Oct 2 12:46:26 2010 +0200

    Move checking for on demand clipboard cap closer to sending of agent messages
    
    This way the events will always get generated and other things
    (such as clipboard ownership administration, see the next patches)
    can be done in repsonse to the events, even though no message will be send.
    
    This patch also removes the !_agent_caps check from the capability
    checks, this is not needed as VD_AGENT_HAS_CAPABILITY checks _agent_caps_size
    which will be 0 when _agent_caps is NULL.

diff --git a/client/red_client.cpp b/client/red_client.cpp
index 99e79a5..666de40 100644
--- a/client/red_client.cpp
+++ b/client/red_client.cpp
@@ -818,6 +818,13 @@ void RedClient::do_send_agent_clipboard()
 
 void RedClient::send_agent_clipboard_message(uint32_t message_type, uint32_t size, void* data)
 {
+    if (!_agent_connected)
+        return;
+
+    if (!VD_AGENT_HAS_CAPABILITY(_agent_caps, _agent_caps_size,
+                                 VD_AGENT_CAP_CLIPBOARD_BY_DEMAND))
+        return;
+
     Message* message = new Message(SPICE_MSGC_MAIN_AGENT_DATA);
     VDAgentMessage* msg = (VDAgentMessage*)
       spice_marshaller_reserve_space(message->marshaller(), sizeof(VDAgentMessage) + size);
@@ -835,20 +842,12 @@ void RedClient::send_agent_clipboard_message(uint32_t message_type, uint32_t siz
 
 void RedClient::on_clipboard_grab(uint32_t *types, uint32_t type_count)
 {
-    if (!_agent_caps || !VD_AGENT_HAS_CAPABILITY(_agent_caps, _agent_caps_size,
-                                                 VD_AGENT_CAP_CLIPBOARD_BY_DEMAND)) {
-        return;
-    }
     AutoRef<ClipboardGrabEvent> event(new ClipboardGrabEvent(types, type_count));
     get_process_loop().push_event(*event);
 }
 
 void RedClient::on_clipboard_request(uint32_t 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);
 }
@@ -859,6 +858,9 @@ void RedClient::on_clipboard_notify(uint32_t type, uint8_t* data, int32_t size)
     if (!_agent_connected) {
         return;
     }
+    if (!VD_AGENT_HAS_CAPABILITY(_agent_caps, _agent_caps_size,
+                                 VD_AGENT_CAP_CLIPBOARD_BY_DEMAND))
+        return;
     if (_agent_out_msg) {
         DBG(0, "clipboard change is already pending");
         return;
commit bc9f00961fa7aaca8d55204ca4fa04abb0f77c8c
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Fri Oct 1 20:14:16 2010 +0200

    Respond to clipb request with an unsupported type with data with a none type
    
    Currently we send a VD_AGENT_CLIPBOARD_RELEASE when we receive a
    VD_AGENT_CLIPBOARD_REQUEST with a type which we do not support. This is not
    correct, as this means given up clipboard ownership while we may be able
    to answer requests with different types. The correct response is to
    nack the request by sending a VD_AGENT_CLIPBOARD (data) message with a type
    of VD_AGENT_CLIPBOARD_NONE.

diff --git a/client/red_client.cpp b/client/red_client.cpp
index e351900..99e79a5 100644
--- a/client/red_client.cpp
+++ b/client/red_client.cpp
@@ -1095,7 +1095,7 @@ void RedClient::dispatch_agent_message(VDAgentMessage* msg, void* data)
         break;
     case VD_AGENT_CLIPBOARD_REQUEST:
         if (!Platform::request_clipboard_notification(((VDAgentClipboardRequest*)data)->type)) {
-            send_agent_clipboard_message(VD_AGENT_CLIPBOARD_RELEASE);
+            on_clipboard_notify(VD_AGENT_CLIPBOARD_NONE, NULL, 0);
         }
         break;
     case VD_AGENT_CLIPBOARD_RELEASE:
commit a2d645ffe3fa23e8c57950987c93c0fe8822238b
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Fri Oct 1 20:01:47 2010 +0200

    Change VD_AGENT_CLIPBOARD_GRAB to an array of types
    
    A clipboard owner can indicate that it can supply the data the clipboard
    owns in multiple formats, so make the data passed with a
    VD_AGENT_CLIPBOARD_GRAB message an array of types rather then a single
    type.

diff --git a/client/platform.h b/client/platform.h
index 4727889..48476cf 100644
--- a/client/platform.h
+++ b/client/platform.h
@@ -121,7 +121,7 @@ public:
     class ClipboardListener;
     static void set_clipboard_listener(ClipboardListener* listener);
 
-    static bool set_clipboard_owner(uint32_t type);
+    static bool set_clipboard_owner(uint32_t *types, uint32_t type_count);
     static bool set_clipboard_data(uint32_t type, const uint8_t* data, int32_t size);
     static bool request_clipboard_notification(uint32_t type);
     static void release_clipboard();
@@ -138,7 +138,7 @@ public:
 class Platform::ClipboardListener {
 public:
     virtual ~ClipboardListener() {}
-    virtual void on_clipboard_grab(uint32_t type) = 0;
+    virtual void on_clipboard_grab(uint32_t *types, uint32_t type_count) = 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;
 };
diff --git a/client/red_client.cpp b/client/red_client.cpp
index 562b826..e351900 100644
--- a/client/red_client.cpp
+++ b/client/red_client.cpp
@@ -83,9 +83,8 @@ uint32_t default_agent_caps[] = {
 
 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);
+        VD_AGENT_CLIPBOARD_GRAB, _type_count * sizeof(uint32_t), _types);
 }
 
 void ClipboardRequestEvent::response(AbstractProcessLoop& events_loop)
@@ -834,13 +833,13 @@ void RedClient::send_agent_clipboard_message(uint32_t message_type, uint32_t siz
     post_message(message);
 }
 
-void RedClient::on_clipboard_grab(uint32_t type)
+void RedClient::on_clipboard_grab(uint32_t *types, uint32_t type_count)
 {
     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));
+    AutoRef<ClipboardGrabEvent> event(new ClipboardGrabEvent(types, type_count));
     get_process_loop().push_event(*event);
 }
 
@@ -1091,7 +1090,8 @@ void RedClient::dispatch_agent_message(VDAgentMessage* msg, void* data)
         break;
     }
     case VD_AGENT_CLIPBOARD_GRAB:
-        Platform::set_clipboard_owner(((VDAgentClipboardGrab*)data)->type);
+        Platform::set_clipboard_owner((uint32_t *)data,
+                                      msg->size / sizeof(uint32_t));
         break;
     case VD_AGENT_CLIPBOARD_REQUEST:
         if (!Platform::request_clipboard_notification(((VDAgentClipboardRequest*)data)->type)) {
diff --git a/client/red_client.h b/client/red_client.h
index 3ccb8e3..2cfce49 100644
--- a/client/red_client.h
+++ b/client/red_client.h
@@ -146,11 +146,22 @@ public:
 
 class ClipboardGrabEvent : public Event {
 public:
-    ClipboardGrabEvent(uint32_t type) : _type (type) {}
+    ClipboardGrabEvent(uint32_t *types, uint32_t type_count)
+    {
+        _types = new uint32_t [type_count];
+        memcpy(_types, types, type_count * sizeof(uint32_t));
+        _type_count = type_count;
+    }
+    ~ClipboardGrabEvent()
+    {
+        delete[] _types;
+    }
+
     virtual void response(AbstractProcessLoop& events_loop);
 
 private:
-    uint32_t _type;
+    uint32_t *_types;
+    uint32_t _type_count;
 };
 
 class ClipboardRequestEvent : public Event {
@@ -207,7 +218,7 @@ public:
     PixmapCache& get_pixmap_cache() {return _pixmap_cache;}
     uint64_t get_pixmap_cache_size() { return _pixmap_cache_size;}
     void on_display_mode_change();
-    void on_clipboard_grab(uint32_t type);
+    void on_clipboard_grab(uint32_t *types, uint32_t type_count);
     void on_clipboard_request(uint32_t type);
     void on_clipboard_notify(uint32_t type, uint8_t* data, int32_t size);
 
diff --git a/client/windows/platform.cpp b/client/windows/platform.cpp
index 95876dc..2a62944 100644
--- a/client/windows/platform.cpp
+++ b/client/windows/platform.cpp
@@ -51,7 +51,7 @@ static ProcessLoop* main_loop = NULL;
 
 class DefaultClipboardListener: public Platform::ClipboardListener {
 public:
-    virtual void on_clipboard_grab(uint32_t type) {}
+    virtual void on_clipboard_grab(uint32_t *types, uint32_t type_count) {}
     virtual void on_clipboard_request(uint32_t type) {}
     virtual void on_clipboard_notify(uint32_t type, uint8_t* data, int32_t size) {}
 };
@@ -151,7 +151,7 @@ static LRESULT CALLBACK PlatformWinProc(HWND hWnd, UINT message, WPARAM wParam,
         if (!clipboard_changer) {
             uint32_t type = get_available_clipboard_type();            
             if (type) {
-                clipboard_listener->on_clipboard_grab(type);
+                clipboard_listener->on_clipboard_grab(&type, 1);
             } else {
                 LOG_INFO("Unsupported clipboard format");
             }
@@ -856,12 +856,13 @@ void WinPlatform::exit_modal_loop()
     modal_loop_active = false;
 }
 
-bool Platform::set_clipboard_owner(uint32_t type)
+bool Platform::set_clipboard_owner(uint32_t *types, uint32_t type_count)
 {
-    uint32_t format = get_clipboard_format(type);
+    /* FIXME use all types rather then just the first one */
+    uint32_t format = get_clipboard_format(types[0]);
     
     if (!format) {
-        LOG_INFO("Unsupported clipboard type %u", type);
+        LOG_INFO("Unsupported clipboard type %u", types[0]);
         return false;
     }
     if (!OpenClipboard(platform_win)) {
diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
index 8f4ed2d..6627187 100644
--- a/client/x11/platform.cpp
+++ b/client/x11/platform.cpp
@@ -151,7 +151,7 @@ static Platform::DisplayModeListener* display_mode_listener = &default_display_m
 
 class DefaultClipboardListener: public Platform::ClipboardListener {
 public:
-    void on_clipboard_grab(uint32_t type) {}
+    void on_clipboard_grab(uint32_t *types, uint32_t type_count) {}
     void on_clipboard_request(uint32_t type) {}
     void on_clipboard_notify(uint32_t type, uint8_t* data, int32_t size) {}
 };
@@ -2392,7 +2392,8 @@ static void root_win_proc(XEvent& event)
             return;
         }
         // FIXME: use actual type
-        clipboard_listener->on_clipboard_grab(VD_AGENT_CLIPBOARD_UTF8_TEXT);
+        uint32_t type = VD_AGENT_CLIPBOARD_UTF8_TEXT;
+        clipboard_listener->on_clipboard_grab(&type, 1);
         return;
     }
     switch (event.type) {
@@ -3144,13 +3145,14 @@ LocalCursor* Platform::create_default_cursor()
     return new XDefaultCursor();
 }
 
-bool Platform::set_clipboard_owner(uint32_t type)
+bool Platform::set_clipboard_owner(uint32_t *types, uint32_t type_count)
 {
     Lock lock(clipboard_lock);
-    uint32_t format = get_clipboard_format(type);
+    /* FIXME use all types rather then just the first one */
+    uint32_t format = get_clipboard_format(types[0]);
 
     if (!format) {
-        LOG_INFO("Unsupported clipboard type %u", type);
+        LOG_INFO("Unsupported clipboard type %u", types[0]);
         return false;
     }
     clipboard_changer = true;
commit c6e9c52a2a588fa11f516e96f3ba5fea3cd67274
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Fri Oct 1 17:02:12 2010 +0200

    Call intern_atoms() earlier
    
    We call XFixesSelectSelectionInput with the clipboard_atom, so we musr
    initialize the atoms before calling XFixesSelectSelectionInput.

diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
index 9811233..8f4ed2d 100644
--- a/client/x11/platform.cpp
+++ b/client/x11/platform.cpp
@@ -830,6 +830,7 @@ DynamicScreen::DynamicScreen(Display* display, int screen, int& next_mon_id)
     X_DEBUG_SYNC(display);
     //FIXME: replace RootWindow() in other refs as well?
     platform_win = XCreateSimpleWindow(display, RootWindow(display, screen), 0, 0, 1, 1, 0, 0, 0);
+    intern_clipboard_atoms();
     XSelectInput(display, platform_win, StructureNotifyMask);
     XRRSelectInput(display, platform_win, RRScreenChangeNotifyMask);
     if (using_xfixes_1_0) {
@@ -843,7 +844,6 @@ DynamicScreen::DynamicScreen(Display* display, int screen, int& next_mon_id)
     process_monitor_configure_events(platform_win);
     Monitor::self_monitors_change--;
 
-    intern_clipboard_atoms();
     XPlatform::set_win_proc(platform_win, root_win_proc);
     X_DEBUG_SYNC(display);
 }
@@ -1106,6 +1106,7 @@ MultyMonScreen::MultyMonScreen(Display* display, int screen, int& next_mon_id)
     }
 
     platform_win = XCreateSimpleWindow(display, RootWindow(display, screen), 0, 0, 1, 1, 0, 0, 0);
+    intern_clipboard_atoms();
     XSelectInput(display, platform_win, StructureNotifyMask);
     X_DEBUG_SYNC(get_display());
     XRRSelectInput(display, platform_win, RRScreenChangeNotifyMask);
@@ -1121,7 +1122,6 @@ MultyMonScreen::MultyMonScreen(Display* display, int screen, int& next_mon_id)
     process_monitor_configure_events(platform_win);
     XMonitor::dec_change_ref();
 
-    intern_clipboard_atoms();
     XPlatform::set_win_proc(platform_win, root_win_proc);
     X_DEBUG_SYNC(get_display());
 }
commit b8824160707ba276e0fcaed8b68aa9e356b534ba
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Fri Oct 1 17:00:28 2010 +0200

    Set clipboard_event before calling send_selection_notify
    
    send_selection_notify used the clipboard_event, so set it before calling
    send_selection_notify.

diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
index f73f651..9811233 100644
--- a/client/x11/platform.cpp
+++ b/client/x11/platform.cpp
@@ -2405,6 +2405,7 @@ static void root_win_proc(XEvent& event)
             break;
         }
         
+        clipboard_event = event;
         uint32_t type = get_clipboard_type(selection_request->target);
         if (!type) {
             LOG_INFO("Unsupported selection type %s", atom_name(selection_request->target));
@@ -2415,7 +2416,6 @@ static void root_win_proc(XEvent& event)
             send_selection_notify(selection_request->target);
             break;
         }
-        clipboard_event = event;
         clipboard_listener->on_clipboard_request(type);
         break;
     }
commit 1ef6d280d12a264f2a4a5aaa928d305afa38715b
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Fri Oct 1 16:57:08 2010 +0200

    wrap XGetAtomName
    
    XGetAtomName() throws X11 errors when called on a None atom, so wrap
    it catching the None case.

diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
index 302b751..f73f651 100644
--- a/client/x11/platform.cpp
+++ b/client/x11/platform.cpp
@@ -173,6 +173,14 @@ static uint32_t get_clipboard_format(uint32_t type) {
     return iter->format;
 }
 
+static const char *atom_name(Atom atom)
+{
+    if (atom == None)
+        return "None";
+
+    return XGetAtomName(x_display, atom);
+}
+
 NamedPipe::ListenerRef NamedPipe::create(const char *name, ListenerInterface& listener_interface)
 {
     ASSERT(main_loop && main_loop->is_same_thread(pthread_self()));
@@ -2399,7 +2407,7 @@ static void root_win_proc(XEvent& event)
         
         uint32_t type = get_clipboard_type(selection_request->target);
         if (!type) {
-            LOG_INFO("Unsupported selection type %s", XGetAtomName(x_display, selection_request->target));
+            LOG_INFO("Unsupported selection type %s", atom_name(selection_request->target));
             send_selection_notify(None);
             break;
         }
commit c909198eca202f1a2424ce739c541a740dd61281
Author: Arnon Gilboa <agilboa at redhat.com>
Date:   Fri Oct 1 16:06:10 2010 +0200

    client: support clipboard/selection-owner model (v2)
    
    -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

diff --git a/client/application.cpp b/client/application.cpp
index 1258067..c5d34ff 100644
--- a/client/application.cpp
+++ b/client/application.cpp
@@ -1318,12 +1318,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 a992aa1..02aa8eb 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,5 +203,6 @@ spicec_LDADD =						\
 	$(ALSA_LIBS)					\
 	$(GL_LIBS)					\
 	$(XRANDR_LIBS)					\
+	$(XFIXES_LIBS)					\
 	$(MISC_X_LIBS)					\
 	$(CEGUI_LIBS)
diff --git a/client/x11/platform.cpp b/client/x11/platform.cpp
index ca5f1d5..302b751 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>
@@ -58,6 +59,7 @@
 #include "res.h"
 #include "cursor.h"
 #include "process_loop.h"
+#include <spice/vd_agent.h>
 
 #define DWORD uint32_t
 #define BOOL bool
@@ -96,24 +98,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() {}
@@ -134,12 +151,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()));
@@ -771,6 +804,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;
 }
 
@@ -786,13 +824,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);
 }
 
@@ -881,7 +925,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());
@@ -1053,26 +1097,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());
 }
 
@@ -1180,7 +1222,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());
 }
@@ -1742,8 +1784,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;
@@ -2201,7 +2242,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];
@@ -2219,49 +2262,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)
 {
@@ -2289,28 +2370,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: {
@@ -2325,17 +2423,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;
@@ -2344,7 +2442,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;
             {
@@ -2353,33 +2451,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;
     }
@@ -2471,6 +2580,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;
@@ -2681,6 +2799,7 @@ void Platform::init()
     init_kbd();
     init_xrandr();
     init_xrender();
+    init_xfixes();
     init_XIM();
 
     struct sigaction act;
@@ -3017,26 +3136,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;
-    }
-    clipboard_listener = listener;
-    /* Seems platform_win can be NULL, we'll just ignore that for now.
-       This will be fixed when the rest of cut and paste lands */
-    if (platform_win) {
-      XConvertSelection(x_display, XA_PRIMARY, utf8_atom, clipboard_prop,
-			platform_win, CurrentTime);
+    Lock lock(clipboard_lock);
+    uint32_t format = get_clipboard_format(type);
+
+    if (!format) {
+        LOG_INFO("Unsupported clipboard type %u", type);
+        return false;
     }
+    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];
@@ -3044,22 +3172,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 4fea09c..d0ad855 100644
--- a/configure.ac
+++ b/configure.ac
@@ -211,9 +211,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-commits mailing list