重要的函数:
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); 请求剪切板
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);
}