Hi Christophe, On Wed, Feb 7, 2018 at 5:12 PM, Christophe de Dinechin <christophe.de.dinechin@xxxxxxxxx> wrote: > Hi Jakub, > > > I have not looked at everything, but here are a few quick notes just from glancing… > >> On 21 Jan 2018, at 21:03, Jakub Janků <janku.jakub.jj@xxxxxxxxx> wrote: >> >> From: Jakub Janků <jjanku@xxxxxxxxxx> >> >> Place the code that handles clipboard >> into a separate file - clipboard.c >> --- >> Makefile.am | 2 + >> src/vdagent/clipboard.c | 401 ++++++++++++++++++++++++++++++++++++++++++++++++ >> src/vdagent/clipboard.h | 42 +++++ >> src/vdagent/vdagent.c | 31 +++- >> 4 files changed, 471 insertions(+), 5 deletions(-) >> create mode 100644 src/vdagent/clipboard.c >> create mode 100644 src/vdagent/clipboard.h >> >> diff --git a/Makefile.am b/Makefile.am >> index c4bd3dd..88550c6 100644 >> --- a/Makefile.am >> +++ b/Makefile.am >> @@ -33,6 +33,8 @@ src_spice_vdagent_SOURCES = \ >> $(common_sources) \ >> src/vdagent/audio.c \ >> src/vdagent/audio.h \ >> + src/vdagent/clipboard.c \ >> + src/vdagent/clipboard.h \ >> src/vdagent/file-xfers.c \ >> src/vdagent/file-xfers.h \ >> src/vdagent/x11-priv.h \ >> diff --git a/src/vdagent/clipboard.c b/src/vdagent/clipboard.c >> new file mode 100644 >> index 0000000..657a6b4 >> --- /dev/null >> +++ b/src/vdagent/clipboard.c >> @@ -0,0 +1,401 @@ >> +/* clipboard.c - vdagent clipboard handling code >> + >> + Copyright 2017 Red Hat, Inc. >> + >> + This program is free software: you can redistribute it and/or modify >> + it under the terms of the GNU General Public License as published by >> + the Free Software Foundation, either version 3 of the License, or >> + (at your option) any later version. >> + >> + This program is distributed in the hope that it will be useful, >> + but WITHOUT ANY WARRANTY; without even the implied warranty of >> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >> + GNU General Public License for more details. >> + >> + You should have received a copy of the GNU General Public License >> + along with this program. If not, see <http://www.gnu.org/licenses/>. >> +*/ >> + >> +#ifdef HAVE_CONFIG_H >> +# include <config.h> >> +#endif >> + >> +#include <gtk/gtk.h> >> +#include <syslog.h> >> + >> +#include "vdagentd-proto.h" >> +#include "spice/vd_agent.h" >> +#include "udscs.h" >> +#include "clipboard.h" >> + >> +/* 2 selections supported - _SELECTION_CLIPBOARD = 0, _SELECTION_PRIMARY = 1 */ >> +#define SELECTION_COUNT (VD_AGENT_CLIPBOARD_SELECTION_PRIMARY + 1) >> +#define TYPE_COUNT (VD_AGENT_CLIPBOARD_IMAGE_JPG + 1) >> + >> +#define sel_from_clip(clipboard) \ >> + GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(clipboard), "vdagent-selection")) >> + >> +enum { >> + OWNER_NONE, >> + OWNER_GUEST, >> + OWNER_CLIENT >> +}; >> + >> +typedef struct { >> + GMainLoop *loop; >> + GtkSelectionData *sel_data; >> +} DataRetrieval; >> + >> +struct VDAgentClipboards { > > That looks C++-y. Any reason to not use a typedef struct here? Does this compile in strict C mode? > The typedef is included in clipboard.h since VDAgentClipboards is also used in vdagent.c >> + struct udscs_connection *conn; >> + >> + GtkClipboard *clipboard[SELECTION_COUNT]; >> + guint owner[SELECTION_COUNT]; >> + >> + GList *data_retrievals[SELECTION_COUNT]; >> + GList *data_requests[SELECTION_COUNT]; >> + gpointer *last_targets_req[SELECTION_COUNT]; >> + >> + GdkAtom targets[SELECTION_COUNT][TYPE_COUNT]; >> +}; >> + >> +static const struct { >> + guint type; >> + const gchar *atom_name; >> +} atom2agent[] = { >> + {VD_AGENT_CLIPBOARD_UTF8_TEXT, "UTF8_STRING"}, >> + {VD_AGENT_CLIPBOARD_UTF8_TEXT, "text/plain;charset=utf-8"}, >> + {VD_AGENT_CLIPBOARD_UTF8_TEXT, "STRING"}, >> + {VD_AGENT_CLIPBOARD_UTF8_TEXT, "TEXT"}, >> + {VD_AGENT_CLIPBOARD_UTF8_TEXT, "text/plain"}, >> + {VD_AGENT_CLIPBOARD_IMAGE_PNG, "image/png"}, >> + {VD_AGENT_CLIPBOARD_IMAGE_BMP, "image/bmp"}, >> + {VD_AGENT_CLIPBOARD_IMAGE_BMP, "image/x-bmp"}, >> + {VD_AGENT_CLIPBOARD_IMAGE_BMP, "image/x-MS-bmp"}, >> + {VD_AGENT_CLIPBOARD_IMAGE_BMP, "image/x-win-bitmap"}, >> + {VD_AGENT_CLIPBOARD_IMAGE_TIFF,"image/tiff"}, >> + {VD_AGENT_CLIPBOARD_IMAGE_JPG, "image/jpeg"}, >> +}; >> + >> +static guint get_type_from_atom(GdkAtom atom) >> +{ >> + int i; >> + gchar *name = gdk_atom_name(atom); >> + for (i = 0; i < G_N_ELEMENTS(atom2agent); i++) >> + if (!g_ascii_strcasecmp(name, atom2agent[i].atom_name)) >> + break; >> + g_free(name); >> + return i < G_N_ELEMENTS(atom2agent) ? atom2agent[i].type >> + : VD_AGENT_CLIPBOARD_NONE; >> +} > > Maybe a minor style point, but I’d find it more readable with the test not repeated: > > static guint get_type_from_atom(GdkAtom atom) > { > int i; > gchar *name = gdk_atom_name(atom); > for (i = 0; i < G_N_ELEMENTS(atom2agent); i++) { > if (!g_ascii_strcasecmp(name, atom2agent[i].atom_name)) { > g_free(name); > return atom2agent[i].type; > } > } > g_free(name); > return VD_AGENT_CLIPBOARD_NONE; > } > OK, I'll change this in the next version. >> + >> +/* gtk_clipboard_request_(, callback, user_data) cannot be cancelled. Instead, >> + gpointer *ref = get_weak_ref(VDAgentClipboards *) is passed to the callback. >> + The callback returns if free_weak_ref(ref) == NULL, which can be done >> + by setting *ref = NULL. This substitutes cancellation of the request. >> + */ >> +static gpointer *get_weak_ref(gpointer data) >> +{ >> + gpointer *ref = g_new(gpointer, 1); >> + *ref = data; >> + return ref; >> +} >> + >> +static gpointer free_weak_ref(gpointer *ref) >> +{ >> + gpointer data = *ref; >> + g_free(ref); >> + return data; >> +} >> + >> +static void clipboard_new_owner(VDAgentClipboards *c, guint sel, guint new_owner) >> +{ >> + GList *l; >> + /* let the other apps know no data is coming */ >> + for (l = c->data_retrievals[sel]; l != NULL; l= l->next) { >> + DataRetrieval *retrv = l->data; >> + g_main_loop_quit(retrv->loop); >> + } >> + g_clear_pointer(&c->data_retrievals[sel], g_list_free); >> + >> + /* respond to pending client's data requests */ >> + for (l = c->data_requests[sel]; l != NULL; l = l->next) { >> + gpointer *ref = l->data; >> + *ref = NULL; >> + if (c->conn) >> + udscs_write(c->conn, VDAGENTD_CLIPBOARD_DATA, >> + sel, VD_AGENT_CLIPBOARD_NONE, NULL, 0); >> + } >> + g_clear_pointer(&c->data_requests[sel], g_list_free); >> + >> + c->owner[sel] = new_owner; >> +} >> + >> +static void clipboard_targets_received_cb(GtkClipboard *clipboard, >> + GdkAtom *atoms, >> + gint n_atoms, >> + gpointer user_data) >> +{ >> + VDAgentClipboards *c = free_weak_ref(user_data); >> + guint32 types[G_N_ELEMENTS(atom2agent)]; >> + guint sel, type, n_types = 0, a; > > I don’t like the lonely initialization in the middle of the decls. > Should I put all the inits on separate lines (including the c = free_weak_ref...)? Or is it enough to put the "n_types = 0" at the end of line? >> + >> + if (c == NULL) /* request was cancelled */ >> + return; >> + >> + sel = sel_from_clip(clipboard); >> + c->last_targets_req[sel] = NULL; >> + >> + if (atoms == NULL) >> + return; >> + >> + for (type = 0; type < TYPE_COUNT; type++) >> + c->targets[sel][type] = GDK_NONE; >> + >> + for (a = 0; a < n_atoms; a++) { >> + type = get_type_from_atom(atoms[a]); >> + if (type == VD_AGENT_CLIPBOARD_NONE || c->targets[sel][type] != GDK_NONE) >> + continue; >> + >> + c->targets[sel][type] = atoms[a]; >> + types[n_types] = type; >> + n_types++; >> + } >> + >> + if (n_types == 0) { >> + syslog(LOG_WARNING, "%s: sel=%u: no target supported", __func__, sel); >> + return; >> + } >> + >> + clipboard_new_owner(c, sel, OWNER_GUEST); >> + >> + udscs_write(c->conn, VDAGENTD_CLIPBOARD_GRAB, sel, 0, >> + (guint8 *)types, n_types * sizeof(guint32)); >> +} >> + >> +static void clipboard_owner_change_cb(GtkClipboard *clipboard, >> + GdkEventOwnerChange *event, >> + gpointer user_data) >> +{ >> + VDAgentClipboards *c = user_data; >> + guint sel = sel_from_clip(clipboard); >> + >> + /* if the event was caused by gtk_clipboard_set_with_data(), ignore it */ >> + if (c->owner[sel] == OWNER_CLIENT) >> + return; >> + >> + if (c->owner[sel] == OWNER_GUEST) { >> + clipboard_new_owner(c, sel, OWNER_NONE); >> + udscs_write(c->conn, VDAGENTD_CLIPBOARD_RELEASE, sel, 0, NULL, 0); >> + } >> + >> + if (event->reason != GDK_OWNER_CHANGE_NEW_OWNER) >> + return; >> + >> + /* if there's a pending request for clipboard targets, cancel it */ >> + if (c->last_targets_req[sel]) >> + *(c->last_targets_req[sel]) = NULL; >> + >> + c->last_targets_req[sel] = get_weak_ref(c); >> + gtk_clipboard_request_targets(clipboard, clipboard_targets_received_cb, >> + c->last_targets_req[sel]); >> +} >> + >> +static void clipboard_contents_received_cb(GtkClipboard *clipboard, >> + GtkSelectionData *sel_data, >> + gpointer user_data) >> +{ >> + guint sel, type, target; >> + VDAgentClipboards *c = free_weak_ref(user_data); >> + if (c == NULL) /* request was cancelled */ >> + return; >> + >> + sel = sel_from_clip(clipboard); >> + c->data_requests[sel] = g_list_remove(c->data_requests[sel], user_data); >> + >> + type = get_type_from_atom(gtk_selection_data_get_data_type(sel_data)); >> + target = get_type_from_atom(gtk_selection_data_get_target(sel_data)); >> + >> + if (type == target) { >> + udscs_write(c->conn, VDAGENTD_CLIPBOARD_DATA, sel, type, >> + gtk_selection_data_get_data(sel_data), >> + gtk_selection_data_get_length(sel_data)); >> + } else { >> + syslog(LOG_WARNING, "%s: sel=%u: expected type %u, recieved %u, " >> + "skipping", __func__, sel, target, type); >> + udscs_write(c->conn, VDAGENTD_CLIPBOARD_DATA, sel, >> + VD_AGENT_CLIPBOARD_NONE, NULL, 0); >> + } >> +} >> + >> +static void clipboard_get_cb(GtkClipboard *clipboard, >> + GtkSelectionData *sel_data, >> + guint info, >> + gpointer user_data) >> +{ >> + DataRetrieval retrv; >> + VDAgentClipboards *c = user_data; >> + guint sel, type; >> + >> + sel = sel_from_clip(clipboard); >> + g_return_if_fail(c->owner[sel] == OWNER_CLIENT); >> + >> + type = get_type_from_atom(gtk_selection_data_get_target(sel_data)); >> + g_return_if_fail(type != VD_AGENT_CLIPBOARD_NONE); >> + >> + retrv.sel_data = sel_data; >> + retrv.loop = g_main_loop_new(NULL, FALSE); >> + c->data_retrievals[sel] = g_list_prepend(c->data_retrievals[sel], &retrv); >> + >> + udscs_write(c->conn, VDAGENTD_CLIPBOARD_REQUEST, sel, type, NULL, 0); >> + >> +G_GNUC_BEGIN_IGNORE_DEPRECATIONS >> + gdk_threads_leave(); >> + g_main_loop_run(retrv.loop); >> + gdk_threads_enter(); >> +G_GNUC_END_IGNORE_DEPRECATIONS >> + >> + g_main_loop_unref(retrv.loop); >> +} >> + >> +static void clipboard_clear_cb(GtkClipboard *clipboard, gpointer user_data) >> +{ >> + VDAgentClipboards *c = user_data; >> + clipboard_new_owner(c, sel_from_clip(clipboard), OWNER_NONE); >> +} >> + >> +void vdagent_clipboard_grab(VDAgentClipboards *c, guint sel, >> + guint32 *types, guint n_types) >> +{ >> + GtkTargetEntry targets[G_N_ELEMENTS(atom2agent)]; >> + guint n_targets = 0, i, t; >> + >> + g_return_if_fail(sel < SELECTION_COUNT); >> + >> + for (i = 0; i < G_N_ELEMENTS(atom2agent); i++) >> + for (t = 0; t < n_types; t++) >> + if (atom2agent[i].type == types[t]) { >> + targets[n_targets].target = (gchar *)atom2agent[i].atom_name; >> + n_targets++; >> + break; >> + } >> + >> + if (n_targets == 0) { >> + syslog(LOG_WARNING, "%s: sel=%u: no type supported", __func__, sel); >> + return; >> + } >> + >> + if (gtk_clipboard_set_with_data(c->clipboard[sel], targets, n_targets, >> + clipboard_get_cb, clipboard_clear_cb, c)) >> + clipboard_new_owner(c, sel, OWNER_CLIENT); >> + else { >> + syslog(LOG_ERR, "%s: sel=%u: clipboard grab failed", __func__, sel); >> + clipboard_new_owner(c, sel, OWNER_NONE); >> + } >> +} >> + >> +void vdagent_clipboard_data(VDAgentClipboards *c, guint sel, >> + guint type, const guchar *data, guint size) >> +{ >> + g_return_if_fail(sel < SELECTION_COUNT); >> + >> + DataRetrieval *retrv; >> + GList *l; >> + for (l = c->data_retrievals[sel]; l != NULL; l = l->next) { >> + retrv = l->data; >> + if (get_type_from_atom(gtk_selection_data_get_target(retrv->sel_data)) == type) >> + break; >> + } >> + if (l == NULL) { >> + syslog(LOG_WARNING, "%s: sel=%u: no corresponding request found for " >> + "type=%u, skipping", __func__, sel, type); >> + return; >> + } >> + c->data_retrievals[sel] = g_list_delete_link(c->data_retrievals[sel], l); >> + >> + gtk_selection_data_set(retrv->sel_data, >> + gtk_selection_data_get_target(retrv->sel_data), >> + 8, data, size); >> + >> + g_main_loop_quit(retrv->loop); >> +} >> + >> +void vdagent_clipboard_release(VDAgentClipboards *c, guint sel) >> +{ >> + g_return_if_fail(sel < SELECTION_COUNT); >> + if (c->owner[sel] != OWNER_CLIENT) >> + return; >> + >> + clipboard_new_owner(c, sel, OWNER_NONE); >> + gtk_clipboard_clear(c->clipboard[sel]); >> +} >> + >> +void vdagent_clipboard_release_all(VDAgentClipboards *c) >> +{ >> + guint sel, owner; >> + >> + for (sel = 0; sel < SELECTION_COUNT; sel++) { >> + owner = c->owner[sel]; >> + clipboard_new_owner(c, sel, OWNER_NONE); >> + if (owner == OWNER_CLIENT) >> + gtk_clipboard_clear(c->clipboard[sel]); >> + else if (owner == OWNER_GUEST && c->conn) >> + udscs_write(c->conn, VDAGENTD_CLIPBOARD_RELEASE, sel, 0, NULL, 0); >> + } >> +} >> + >> +void vdagent_clipboard_request(VDAgentClipboards *c, guint sel, guint type) >> +{ >> + if (sel >= SELECTION_COUNT || c->owner[sel] != OWNER_GUEST) >> + goto err; >> + if (type >= TYPE_COUNT || c->targets[sel][type] == GDK_NONE) { >> + syslog(LOG_WARNING, "%s: sel=%d: unadvertised data type requested", >> + __func__, sel); >> + goto err; >> + } >> + >> + gpointer *ref = get_weak_ref(c); >> + c->data_requests[sel] = g_list_prepend(c->data_requests[sel], ref); >> + gtk_clipboard_request_contents(c->clipboard[sel], c->targets[sel][type], >> + clipboard_contents_received_cb, ref); >> + return; >> +err: >> + udscs_write(c->conn, VDAGENTD_CLIPBOARD_DATA, sel, >> + VD_AGENT_CLIPBOARD_NONE, NULL, 0); >> +} >> + >> +VDAgentClipboards *vdagent_clipboard_init(struct udscs_connection *conn) >> +{ >> + const GdkAtom sel_atom[SELECTION_COUNT] = { >> + GDK_SELECTION_CLIPBOARD, /* VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD */ >> + GDK_SELECTION_PRIMARY, /* VD_AGENT_CLIPBOARD_SELECTION_PRIMARY */ >> + }; >> + >> + VDAgentClipboards *c; >> + c = g_new0(VDAgentClipboards, 1); >> + c->conn = conn; >> + >> + for (guint sel = 0; sel < SELECTION_COUNT; sel++) { >> + c->clipboard[sel] = gtk_clipboard_get(sel_atom[sel]); >> + /* enables the use of sel_from_clipboard(clipboard) macro */ >> + g_object_set_data(G_OBJECT(c->clipboard[sel]), "vdagent-selection", >> + GUINT_TO_POINTER(sel)); >> + g_signal_connect(G_OBJECT(c->clipboard[sel]), "owner-change", >> + G_CALLBACK(clipboard_owner_change_cb), c); >> + } >> + >> + return c; >> +} >> + >> +void vdagent_clipboard_finalize(VDAgentClipboards *c, >> + struct udscs_connection *conn) >> +{ >> + for (guint sel = 0; sel < SELECTION_COUNT; sel++) >> + g_signal_handlers_disconnect_by_func(c->clipboard[sel], >> + G_CALLBACK(clipboard_owner_change_cb), c); >> + >> + c->conn = conn; >> + vdagent_clipboard_release_all(c); >> + >> + g_free(c); >> +} >> diff --git a/src/vdagent/clipboard.h b/src/vdagent/clipboard.h >> new file mode 100644 >> index 0000000..f02d8a4 >> --- /dev/null >> +++ b/src/vdagent/clipboard.h >> @@ -0,0 +1,42 @@ >> +/* clipboard.h - vdagent clipboard handling header >> + >> + Copyright 2017 Red Hat, Inc. >> + >> + This program is free software: you can redistribute it and/or modify >> + it under the terms of the GNU General Public License as published by >> + the Free Software Foundation, either version 3 of the License, or >> + (at your option) any later version. >> + >> + This program is distributed in the hope that it will be useful, >> + but WITHOUT ANY WARRANTY; without even the implied warranty of >> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >> + GNU General Public License for more details. >> + >> + You should have received a copy of the GNU General Public License >> + along with this program. If not, see <http://www.gnu.org/licenses/>. >> +*/ >> + >> +#ifndef __VDAGENT_CLIPBOARD_H >> +#define __VDAGENT_CLIPBOARD_H >> + >> +#include "udscs.h" >> + >> +typedef struct VDAgentClipboards VDAgentClipboards; >> + >> +VDAgentClipboards *vdagent_clipboard_init(struct udscs_connection *conn); >> +void vdagent_clipboard_finalize(VDAgentClipboards *c, >> + struct udscs_connection *conn); >> + >> +void vdagent_clipboard_request(VDAgentClipboards *c, guint sel, guint type); >> + >> +void vdagent_clipboard_release(VDAgentClipboards *c, guint sel); >> + >> +void vdagent_clipboard_release_all(VDAgentClipboards *c); >> + >> +void vdagent_clipboard_data(VDAgentClipboards *c, guint sel, >> + guint type, const guchar *data, guint size); >> + >> +void vdagent_clipboard_grab(VDAgentClipboards *c, guint sel, >> + guint32 *types, guint n_types); >> + >> +#endif >> diff --git a/src/vdagent/vdagent.c b/src/vdagent/vdagent.c >> index 92ffcf3..81b7a5a 100644 >> --- a/src/vdagent/vdagent.c >> +++ b/src/vdagent/vdagent.c >> @@ -44,8 +44,10 @@ >> #include "audio.h" >> #include "x11.h" >> #include "file-xfers.h" >> +#include "clipboard.h" >> >> typedef struct VDAgent { >> + VDAgentClipboards *clipboards; >> struct vdagent_x11 *x11; >> struct vdagent_file_xfers *xfers; >> struct udscs_connection *conn; >> @@ -154,6 +156,17 @@ static gboolean vdagent_finalize_file_xfer(VDAgent *agent) >> return TRUE; >> } >> >> +static void vdagent_quit_loop(VDAgent *agent) >> +{ >> + /* other GMainLoop(s) might be running, quit them before agent->loop */ >> + if (agent->clipboards) { >> + vdagent_clipboard_finalize(agent->clipboards, agent->conn); >> + agent->clipboards = NULL; >> + } >> + if (agent->loop) >> + g_main_loop_quit(agent->loop); >> +} >> + >> static void daemon_read_complete(struct udscs_connection **connp, >> struct udscs_message_header *header, uint8_t *data) >> { >> @@ -164,18 +177,24 @@ static void daemon_read_complete(struct udscs_connection **connp, >> vdagent_x11_set_monitor_config(agent->x11, (VDAgentMonitorsConfig *)data, 0); >> break; >> case VDAGENTD_CLIPBOARD_REQUEST: >> + vdagent_clipboard_request(agent->clipboards, header->arg1, header->arg2); >> break; >> case VDAGENTD_CLIPBOARD_GRAB: >> + vdagent_clipboard_grab(agent->clipboards, header->arg1, >> + (guint32 *)data, header->size / sizeof(guint32)); >> break; >> case VDAGENTD_CLIPBOARD_DATA: >> + vdagent_clipboard_data(agent->clipboards, header->arg1, header->arg2, >> + data, header->size); >> break; >> case VDAGENTD_CLIPBOARD_RELEASE: >> + vdagent_clipboard_release(agent->clipboards, header->arg1); >> break; >> case VDAGENTD_VERSION: >> if (strcmp((char *)data, VERSION) != 0) { >> syslog(LOG_INFO, "vdagentd version mismatch: got %s expected %s", >> data, VERSION); >> - g_main_loop_quit(agent->loop); >> + vdagent_quit_loop(agent); >> version_mismatch = 1; >> } >> break; >> @@ -222,6 +241,7 @@ static void daemon_read_complete(struct udscs_connection **connp, >> } >> break; >> case VDAGENTD_CLIENT_DISCONNECTED: >> + vdagent_clipboard_release_all(agent->clipboards); >> if (vdagent_finalize_file_xfer(agent)) { >> vdagent_init_file_xfer(agent); >> } >> @@ -236,8 +256,7 @@ static void daemon_disconnect_cb(struct udscs_connection *conn) >> { >> VDAgent *agent = udscs_get_user_data(conn); >> agent->conn = NULL; >> - if (agent->loop) >> - g_main_loop_quit(agent->loop); >> + vdagent_quit_loop(agent); >> } >> >> /* When we daemonize, it is useful to have the main process >> @@ -309,7 +328,7 @@ gboolean vdagent_signal_handler(gpointer user_data) >> { >> VDAgent *agent = user_data; >> quit = TRUE; >> - g_main_loop_quit(agent->loop); >> + vdagent_quit_loop(agent); >> return G_SOURCE_REMOVE; >> } >> >> @@ -368,6 +387,8 @@ static gboolean vdagent_init_async_cb(gpointer user_data) >> if (!vdagent_init_file_xfer(agent)) >> syslog(LOG_WARNING, "File transfer is disabled"); >> >> + agent->clipboards = vdagent_clipboard_init(agent->conn); >> + >> if (parent_socket != -1) { >> if (write(parent_socket, "OK", 2) != 2) >> syslog(LOG_WARNING, "Parent already gone."); >> @@ -378,7 +399,7 @@ static gboolean vdagent_init_async_cb(gpointer user_data) >> return G_SOURCE_REMOVE; >> >> err_init: >> - g_main_loop_quit(agent->loop); >> + vdagent_quit_loop(agent); >> quit = TRUE; >> return G_SOURCE_REMOVE; >> } >> -- >> 2.14.3 >> >> _______________________________________________ >> Spice-devel mailing list >> Spice-devel@xxxxxxxxxxxxxxxxxxxxx >> https://lists.freedesktop.org/mailman/listinfo/spice-devel > Thanks for the review, Jakub _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel