gtk实现spice剪切板-LMLPHP
重要的函数:
void spice_main_channel_clipboard_selection_grab(SpiceMainChannel *channel, guint selection,
guint32 *types, int ntypes);  抓取剪切板
void spice_main_channel_clipboard_selection_release(SpiceMainChannel *channel, guint selection);  剪切板释放
void spice_main_channel_clipboard_selection_notify(SpiceMainChannel *channel, guint selection,guint32 type, const guchar *data, size_t size);   通知剪切板内容
void spice_main_channel_clipboard_selection_request(SpiceMainChannel *channel, guint selection,guint32 type); 请求剪切板
1、host--> guest复制粘贴
1.1、宿主机复制:监听剪切板变化,获取剪切板内容
1.1.1、连接剪切板内容变化的回调函数
void spice_gtk_session_copy_to_guest(SpiceGtkSession *self)
{
    g_return_if_fail(SPICE_IS_GTK_SESSION(self));
    g_return_if_fail(read_only(self) == FALSE);
    SpiceGtkSessionPrivate *s = self->priv;
    int selection = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
    if (s->clip_hasdata[selection] && !s->clip_grabbed[selection]) {
        gtk_clipboard_request_targets(s->clipboard, clipboard_get_targets,get_weak_ref(self)); //剪贴板收到支持类型的内容之后时,callback将被调用。
    }
}
1.1.2、剪切板内容处理
static void clipboard_get_targets(GtkClipboard *clipboard,GdkAtom *atoms,gint n_atoms, gpointer user_data)
{
    SpiceGtkSession *self = free_weak_ref(user_data);
    SPICE_DEBUG("%s:", __FUNCTION__);
    if (self == NULL)
        return;

    g_return_if_fail(SPICE_IS_GTK_SESSION(self));
    if (atoms == NULL) {
        SPICE_DEBUG("Retrieving the clipboard data has failed");
        return;
    }

    SpiceGtkSessionPrivate *s = self->priv;
    guint32 types[SPICE_N_ELEMENTS(atom2agent)] = { 0 };
    gint num_types;
    int a;
    int selection;
    if (s->main == NULL)
        return;

    selection = get_selection_from_clipboard(s, clipboard);
    g_return_if_fail(selection != -1);

    /* GTK+ does seem to cache atoms, but not for Wayland */
    g_free(s->atoms[selection]);
    s->atoms[selection] = g_memdup(atoms, n_atoms * sizeof(GdkAtom));
    s->n_atoms[selection] = n_atoms;
    if (s->clip_grabbed[selection]) {
        SPICE_DEBUG("Clipboard is already grabbed, re-grab: %d atoms", n_atoms);
    }

    /* Set all Atoms that matches our current protocol implementation */
    num_types = 0;
    for (a = 0; a < n_atoms; a++) {
        guint m;
        gchar *name = gdk_atom_name(atoms[a]);
        SPICE_DEBUG(" \"%s\"", name);
        for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
            guint t;
            if (strcasecmp(name, atom2agent[m].xatom) != 0) {
                continue;
            }

            if (atom2agent[m].vdagent == VD_AGENT_CLIPBOARD_FILE_LIST) {
#ifdef HAVE_PHODAV_VIRTUAL
                if (!clipboard_get_open_webdav(s->session)) {
                    SPICE_DEBUG("Received %s target, but the clipboard webdav channel isn't available, skipping", atom2agent[m].xatom);
                    break;
                }
#else
                break;
#endif
            }

            /* check if type is already in list */
            for (t = 0; t < num_types; t++) {
                if (types[t] == atom2agent[m].vdagent) {
                    break;
                }
            }
            if (t == num_types) {
                /* add type to empty slot */
                types[t] = atom2agent[m].vdagent;
                num_types++;
            }
        }
        g_free(name);
    }

    if (num_types == 0) {
        SPICE_DEBUG("No GdkAtoms will be sent from %d", n_atoms);
        return;
    }

    s->clip_grabbed[selection] = TRUE;
    if (spice_main_channel_agent_test_capability(s->main, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND))
        spice_main_channel_clipboard_selection_grab(s->main, selection, types, num_types);//
    /* Sending a grab causes the agent to do an implicit release */
    s->nclip_targets[selection] = 0;
}
1.2、guest粘贴:发送request请求获取内容
1.2.1、连接回调函数
static void channel_new(SpiceSession *session, SpiceChannel *channel,
                        gpointer user_data)
{
    g_return_if_fail(SPICE_IS_GTK_SESSION(user_data));

    SpiceGtkSession *self = user_data;
    SpiceGtkSessionPrivate *s = self->priv;

    if (SPICE_IS_MAIN_CHANNEL(channel)) {
        SPICE_DEBUG("Changing main channel from %p to %p", s->main, channel);
        s->main = SPICE_MAIN_CHANNEL(channel);
        g_signal_connect(channel, "main-clipboard-selection-grab",
                         G_CALLBACK(clipboard_grab), self);
        g_signal_connect(channel, "main-clipboard-selection-request",
                         G_CALLBACK(clipboard_request), self);
        g_signal_connect(channel, "main-clipboard-selection-release",
                         G_CALLBACK(clipboard_release_delay), self);
    }
    if (SPICE_IS_INPUTS_CHANNEL(channel)) {
        spice_g_signal_connect_object(channel, "inputs-modifiers",G_CALLBACK(guest_modifiers_changed), self, 0);
        spice_gtk_session_sync_keyboard_modifiers_for_channel(self, SPICE_INPUTS_CHANNEL(channel), TRUE);
    }
}
1.2.2、收到服务器的request请求剪切板内容
static gboolean clipboard_request(SpiceMainChannel *main, guint selection,
                                  guint type, gpointer user_data)
{
    g_return_val_if_fail(SPICE_IS_GTK_SESSION(user_data), FALSE);
    SpiceGtkSession *self = user_data;
    SpiceGtkSessionPrivate *s = self->priv;
    GdkAtom atom;
    GtkClipboard* cb;
    int m;
    cb = get_clipboard_from_selection(s, selection);
    g_return_val_if_fail(cb != NULL, FALSE);
    g_return_val_if_fail(s->clipboard_by_guest[selection] == FALSE, FALSE);
    g_return_val_if_fail(s->clip_grabbed[selection], FALSE);

    if (read_only(self))
        return FALSE;

    if (type == VD_AGENT_CLIPBOARD_UTF8_TEXT) {
        gtk_clipboard_request_text(cb, clipboard_received_text_cb,get_weak_ref(self)); //发送文本
    } else if (type == VD_AGENT_CLIPBOARD_FILE_LIST) {
#ifdef HAVE_PHODAV_VIRTUAL
        atom = clipboard_select_uris_atom(s, selection);
        if (atom == GDK_NONE) {
            return FALSE;
        }
        gtk_clipboard_request_contents(cb, atom, clipboard_received_uri_contents_cb, get_weak_ref(self));//发视其他内容
#else
        return FALSE;
#endif
    } else {
        for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
            if (atom2agent[m].vdagent == type)
                break;
        }
        g_return_val_if_fail(m < SPICE_N_ELEMENTS(atom2agent), FALSE);
        atom = gdk_atom_intern_static_string(atom2agent[m].xatom);
        gtk_clipboard_request_contents(cb, atom, clipboard_received_cb,get_weak_ref(self));
    }
    return TRUE;
}
static gboolean clipboard_grab(SpiceMainChannel *main, guint selection,
                               guint32* types, guint32 ntypes,
                               gpointer user_data)
{
    g_return_val_if_fail(SPICE_IS_GTK_SESSION(user_data), FALSE);
    SpiceGtkSession *self = user_data;
    SpiceGtkSessionPrivate *s = self->priv;
    GtkTargetEntry targets[SPICE_N_ELEMENTS(atom2agent)];
    gboolean target_selected[SPICE_N_ELEMENTS(atom2agent)] = { FALSE, };
    gboolean found;
    GtkClipboard* cb;
    int m, n;
    int num_targets = 0;
    clipboard_release_delay_remove(self, selection, false);

    cb = get_clipboard_from_selection(s, selection);
    g_return_val_if_fail(cb != NULL, FALSE);

    for (n = 0; n < ntypes; ++n) {
        found = FALSE;
        for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
            if (atom2agent[m].vdagent == types[n] && !target_selected[m]) {
                found = TRUE;
                g_return_val_if_fail(num_targets < SPICE_N_ELEMENTS(atom2agent), FALSE);
                targets[num_targets].target = (gchar*)atom2agent[m].xatom;
                targets[num_targets].info = m;
                target_selected[m] = TRUE;
                num_targets++;
            }
        }
        if (!found) {
            g_warning("clipboard: couldn't find a matching type for: %u",
                      types[n]);
        }
    }

    g_free(s->clip_targets[selection]);
    s->nclip_targets[selection] = num_targets;
    s->clip_targets[selection] = g_memdup(targets, sizeof(GtkTargetEntry) * num_targets);
    /* Receiving a grab implies we've released our own grab */
    s->clip_grabbed[selection] = FALSE;
    if (read_only(self) ||
        !s->auto_clipboard_enable ||
        s->nclip_targets[selection] == 0) {
        return TRUE;
    }
    if (!gtk_clipboard_set_with_owner(cb,
                                      targets,
                                      num_targets,
                                      clipboard_get,
                                      clipboard_clear,
                                      G_OBJECT(self))) {
        g_warning("clipboard grab failed");
        return FALSE;
    }
    s->clipboard_by_guest[selection] = TRUE;
    s->clip_hasdata[selection] = FALSE;

    return TRUE;
}
2、guest --> host复制粘贴
2.1、剪切板粘贴时调用函数 clipboard_get
void spice_gtk_session_paste_from_guest(SpiceGtkSession *self)
{
    g_return_if_fail(SPICE_IS_GTK_SESSION(self));
    g_return_if_fail(read_only(self) == FALSE);
    SpiceGtkSessionPrivate *s = self->priv;
    int selection = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;

    if (s->nclip_targets[selection] == 0) {
        g_warning("Guest clipboard is not available.");
        return;
    }

    if (!gtk_clipboard_set_with_owner(s->clipboard, s->clip_targets[selection], s->nclip_targets[selection], clipboard_get, clipboard_clear, G_OBJECT(self))) {
        g_warning("Clipboard grab failed");
        return;
    }
    s->clipboard_by_guest[selection] = TRUE;
    s->clip_hasdata[selection] = FALSE;
}
2.2、连接主通道信号 main-clipboard-selection处理粘贴内容
static void clipboard_get(GtkClipboard *clipboard, GtkSelectionData *selection_data, guint info, gpointer user_data)
{
    g_return_if_fail(SPICE_IS_GTK_SESSION(user_data));
    RunInfo ri = { NULL, };
    SpiceGtkSession *self = user_data;
    SpiceGtkSessionPrivate *s = self->priv;
    gboolean agent_connected = FALSE;
    gulong clipboard_handler;
    gulong agent_handler;
    int selection;
    SPICE_DEBUG("clipboard get");

    selection = get_selection_from_clipboard(s, clipboard);
    g_return_if_fail(selection != -1);
    g_return_if_fail(info < SPICE_N_ELEMENTS(atom2agent));
    g_return_if_fail(s->main != NULL);

    if (s->clipboard_release_delay[selection]) {
        SPICE_DEBUG("not requesting data from guest during delayed release");
        return;
    }

    ri.selection_data = selection_data;
    ri.info = info;
    ri.loop = g_main_loop_new(NULL, FALSE);
    ri.selection = selection;
    ri.self = self;
    clipboard_handler = g_signal_connect(s->main, "main-clipboard-selection",G_CALLBACK(clipboard_got_from_guest),&ri);
    agent_handler = g_signal_connect_swapped(s->main, "notify::agent-connected",G_CALLBACK(clipboard_agent_connected),&ri);
    spice_main_channel_clipboard_selection_request(s->main, selection,atom2agent[info].vdagent);

    g_object_get(s->main, "agent-connected", &agent_connected, NULL);
    if (!agent_connected) {
        SPICE_DEBUG("canceled clipboard_get, before running loop");
        goto cleanup;
    }

    /* This is modeled on the implementation of gtk_dialog_run() even though
     * these thread functions are deprecated and appears to be needed to avoid
     * dead-lock from gtk_dialog_run().
     */
    G_GNUC_BEGIN_IGNORE_DEPRECATIONS
    gdk_threads_leave();
    g_main_loop_run(ri.loop);
    gdk_threads_enter();
    G_GNUC_END_IGNORE_DEPRECATIONS

cleanup:
    g_clear_pointer(&ri.loop, g_main_loop_unref);
    g_signal_handler_disconnect(s->main, clipboard_handler);
    g_signal_handler_disconnect(s->main, agent_handler);
}
2.3、将内容设置到剪切板上面
static void clipboard_got_from_guest(SpiceMainChannel *main, guint selection,
                                     guint type, const guchar *data, guint size,
                                     gpointer user_data)
{
    RunInfo *ri = user_data;
    SpiceGtkSessionPrivate *s = ri->self->priv;
    gchar *conv = NULL;

    g_return_if_fail(selection == ri->selection);
    SPICE_DEBUG("clipboard got data");

    if (atom2agent[ri->info].vdagent == VD_AGENT_CLIPBOARD_UTF8_TEXT) {
        /* on windows, gtk+ would already convert to LF endings, but
           not on unix */
        if (spice_main_channel_agent_test_capability(s->main, VD_AGENT_CAP_GUEST_LINEEND_CRLF)) {
            conv = spice_dos2unix((gchar*)data, size);
            size = strlen(conv);
        }
        gtk_selection_data_set_text(ri->selection_data, conv ?: (gchar*)data, size);
    } else {
        gtk_selection_data_set(ri->selection_data,
            gdk_atom_intern_static_string(atom2agent[ri->info].xatom),
            8, data, size);
    }

    if (g_main_loop_is_running (ri->loop))
        g_main_loop_quit (ri->loop);

    g_free(conv);
}
06-14 20:54