xref: /qemu/ui/gtk-clipboard.c (revision 0f9668e0)
1d11ebe2cSGerd Hoffmann /*
2d11ebe2cSGerd Hoffmann  * GTK UI -- clipboard support
3d11ebe2cSGerd Hoffmann  *
4d11ebe2cSGerd Hoffmann  * Copyright (C) 2021 Gerd Hoffmann <kraxel@redhat.com>
5d11ebe2cSGerd Hoffmann  *
6d11ebe2cSGerd Hoffmann  * This program is free software; you can redistribute it and/or modify
7d11ebe2cSGerd Hoffmann  * it under the terms of the GNU General Public License as published by
8d11ebe2cSGerd Hoffmann  * the Free Software Foundation; either version 2 of the License, or
9d11ebe2cSGerd Hoffmann  * (at your option) any later version.
10d11ebe2cSGerd Hoffmann  *
11d11ebe2cSGerd Hoffmann  * This program is distributed in the hope that it will be useful,
12d11ebe2cSGerd Hoffmann  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13d11ebe2cSGerd Hoffmann  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14d11ebe2cSGerd Hoffmann  * General Public License for more details.
15d11ebe2cSGerd Hoffmann  *
16d11ebe2cSGerd Hoffmann  * You should have received a copy of the GNU General Public License
17d11ebe2cSGerd Hoffmann  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18d11ebe2cSGerd Hoffmann  *
19d11ebe2cSGerd Hoffmann  */
20d11ebe2cSGerd Hoffmann 
21d11ebe2cSGerd Hoffmann #include "qemu/osdep.h"
22d11ebe2cSGerd Hoffmann #include "qemu/main-loop.h"
23d11ebe2cSGerd Hoffmann 
24d11ebe2cSGerd Hoffmann #include "ui/gtk.h"
25d11ebe2cSGerd Hoffmann 
gd_find_selection(GtkDisplayState * gd,GtkClipboard * clipboard)26d11ebe2cSGerd Hoffmann static QemuClipboardSelection gd_find_selection(GtkDisplayState *gd,
27d11ebe2cSGerd Hoffmann                                                 GtkClipboard *clipboard)
28d11ebe2cSGerd Hoffmann {
29d11ebe2cSGerd Hoffmann     QemuClipboardSelection s;
30d11ebe2cSGerd Hoffmann 
31d11ebe2cSGerd Hoffmann     for (s = 0; s < QEMU_CLIPBOARD_SELECTION__COUNT; s++) {
32d11ebe2cSGerd Hoffmann         if (gd->gtkcb[s] == clipboard) {
33d11ebe2cSGerd Hoffmann             return s;
34d11ebe2cSGerd Hoffmann         }
35d11ebe2cSGerd Hoffmann     }
36d11ebe2cSGerd Hoffmann     return QEMU_CLIPBOARD_SELECTION_CLIPBOARD;
37d11ebe2cSGerd Hoffmann }
38d11ebe2cSGerd Hoffmann 
gd_clipboard_get_data(GtkClipboard * clipboard,GtkSelectionData * selection_data,guint selection_info,gpointer data)39d11ebe2cSGerd Hoffmann static void gd_clipboard_get_data(GtkClipboard     *clipboard,
40d11ebe2cSGerd Hoffmann                                   GtkSelectionData *selection_data,
41d11ebe2cSGerd Hoffmann                                   guint             selection_info,
42d11ebe2cSGerd Hoffmann                                   gpointer          data)
43d11ebe2cSGerd Hoffmann {
44d11ebe2cSGerd Hoffmann     GtkDisplayState *gd = data;
45d11ebe2cSGerd Hoffmann     QemuClipboardSelection s = gd_find_selection(gd, clipboard);
46d11ebe2cSGerd Hoffmann     QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT;
47b702c863SMarc-André Lureau     g_autoptr(QemuClipboardInfo) info = NULL;
48b702c863SMarc-André Lureau 
49b702c863SMarc-André Lureau     info = qemu_clipboard_info_ref(qemu_clipboard_info(s));
50d11ebe2cSGerd Hoffmann 
51d11ebe2cSGerd Hoffmann     qemu_clipboard_request(info, type);
52b702c863SMarc-André Lureau     while (info == qemu_clipboard_info(s) &&
53d11ebe2cSGerd Hoffmann            info->types[type].available &&
54d11ebe2cSGerd Hoffmann            info->types[type].data == NULL) {
55d11ebe2cSGerd Hoffmann         main_loop_wait(false);
56d11ebe2cSGerd Hoffmann     }
57d11ebe2cSGerd Hoffmann 
58b702c863SMarc-André Lureau     if (info == qemu_clipboard_info(s) && gd->cbowner[s]) {
59d11ebe2cSGerd Hoffmann         gtk_selection_data_set_text(selection_data,
60d11ebe2cSGerd Hoffmann                                     info->types[type].data,
61d11ebe2cSGerd Hoffmann                                     info->types[type].size);
62d11ebe2cSGerd Hoffmann     } else {
63d11ebe2cSGerd Hoffmann         /* clipboard owner changed while waiting for the data */
64d11ebe2cSGerd Hoffmann     }
65d11ebe2cSGerd Hoffmann }
66d11ebe2cSGerd Hoffmann 
gd_clipboard_clear(GtkClipboard * clipboard,gpointer data)67d11ebe2cSGerd Hoffmann static void gd_clipboard_clear(GtkClipboard *clipboard,
68d11ebe2cSGerd Hoffmann                                gpointer data)
69d11ebe2cSGerd Hoffmann {
70d11ebe2cSGerd Hoffmann     GtkDisplayState *gd = data;
71d11ebe2cSGerd Hoffmann     QemuClipboardSelection s = gd_find_selection(gd, clipboard);
72d11ebe2cSGerd Hoffmann 
73d11ebe2cSGerd Hoffmann     gd->cbowner[s] = false;
74d11ebe2cSGerd Hoffmann }
75d11ebe2cSGerd Hoffmann 
gd_clipboard_update_info(GtkDisplayState * gd,QemuClipboardInfo * info)761b17f1e9SMarc-André Lureau static void gd_clipboard_update_info(GtkDisplayState *gd,
771b17f1e9SMarc-André Lureau                                      QemuClipboardInfo *info)
78d11ebe2cSGerd Hoffmann {
79d11ebe2cSGerd Hoffmann     QemuClipboardSelection s = info->selection;
80d11ebe2cSGerd Hoffmann     bool self_update = info->owner == &gd->cbpeer;
81d11ebe2cSGerd Hoffmann 
82b702c863SMarc-André Lureau     if (info != qemu_clipboard_info(s)) {
83d11ebe2cSGerd Hoffmann         gd->cbpending[s] = 0;
84d11ebe2cSGerd Hoffmann         if (!self_update) {
85*87800d94SMarc-André Lureau             g_autoptr(GtkTargetList) list = NULL;
86d11ebe2cSGerd Hoffmann             GtkTargetEntry *targets;
87d11ebe2cSGerd Hoffmann             gint n_targets;
88d11ebe2cSGerd Hoffmann 
89d11ebe2cSGerd Hoffmann             list = gtk_target_list_new(NULL, 0);
90d11ebe2cSGerd Hoffmann             if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
91d11ebe2cSGerd Hoffmann                 gtk_target_list_add_text_targets(list, 0);
92d11ebe2cSGerd Hoffmann             }
93d11ebe2cSGerd Hoffmann             targets = gtk_target_table_new_from_list(list, &n_targets);
94d11ebe2cSGerd Hoffmann 
95d11ebe2cSGerd Hoffmann             gtk_clipboard_clear(gd->gtkcb[s]);
96*87800d94SMarc-André Lureau             if (targets) {
97d11ebe2cSGerd Hoffmann                 gd->cbowner[s] = true;
98d11ebe2cSGerd Hoffmann                 gtk_clipboard_set_with_data(gd->gtkcb[s],
99d11ebe2cSGerd Hoffmann                                             targets, n_targets,
100d11ebe2cSGerd Hoffmann                                             gd_clipboard_get_data,
101d11ebe2cSGerd Hoffmann                                             gd_clipboard_clear,
102d11ebe2cSGerd Hoffmann                                             gd);
103d11ebe2cSGerd Hoffmann 
104d11ebe2cSGerd Hoffmann                 gtk_target_table_free(targets, n_targets);
105*87800d94SMarc-André Lureau             }
106d11ebe2cSGerd Hoffmann         }
107d11ebe2cSGerd Hoffmann         return;
108d11ebe2cSGerd Hoffmann     }
109d11ebe2cSGerd Hoffmann 
110d11ebe2cSGerd Hoffmann     if (self_update) {
111d11ebe2cSGerd Hoffmann         return;
112d11ebe2cSGerd Hoffmann     }
113d11ebe2cSGerd Hoffmann 
114d11ebe2cSGerd Hoffmann     /*
115d11ebe2cSGerd Hoffmann      * Clipboard got updated, with data probably.  No action here, we
116d11ebe2cSGerd Hoffmann      * are waiting for updates in gd_clipboard_get_data().
117d11ebe2cSGerd Hoffmann      */
118d11ebe2cSGerd Hoffmann }
119d11ebe2cSGerd Hoffmann 
gd_clipboard_notify(Notifier * notifier,void * data)1201b17f1e9SMarc-André Lureau static void gd_clipboard_notify(Notifier *notifier, void *data)
1211b17f1e9SMarc-André Lureau {
1221b17f1e9SMarc-André Lureau     GtkDisplayState *gd =
1231b17f1e9SMarc-André Lureau         container_of(notifier, GtkDisplayState, cbpeer.notifier);
1241b17f1e9SMarc-André Lureau     QemuClipboardNotify *notify = data;
1251b17f1e9SMarc-André Lureau 
1261b17f1e9SMarc-André Lureau     switch (notify->type) {
1271b17f1e9SMarc-André Lureau     case QEMU_CLIPBOARD_UPDATE_INFO:
1281b17f1e9SMarc-André Lureau         gd_clipboard_update_info(gd, notify->info);
1291b17f1e9SMarc-André Lureau         return;
130505dbf9bSMarc-André Lureau     case QEMU_CLIPBOARD_RESET_SERIAL:
131505dbf9bSMarc-André Lureau         /* ignore */
132505dbf9bSMarc-André Lureau         return;
1331b17f1e9SMarc-André Lureau     }
1341b17f1e9SMarc-André Lureau }
1351b17f1e9SMarc-André Lureau 
gd_clipboard_request(QemuClipboardInfo * info,QemuClipboardType type)136d11ebe2cSGerd Hoffmann static void gd_clipboard_request(QemuClipboardInfo *info,
137d11ebe2cSGerd Hoffmann                                  QemuClipboardType type)
138d11ebe2cSGerd Hoffmann {
139d11ebe2cSGerd Hoffmann     GtkDisplayState *gd = container_of(info->owner, GtkDisplayState, cbpeer);
140d11ebe2cSGerd Hoffmann     char *text;
141d11ebe2cSGerd Hoffmann 
142d11ebe2cSGerd Hoffmann     switch (type) {
143d11ebe2cSGerd Hoffmann     case QEMU_CLIPBOARD_TYPE_TEXT:
144d11ebe2cSGerd Hoffmann         text = gtk_clipboard_wait_for_text(gd->gtkcb[info->selection]);
145d11ebe2cSGerd Hoffmann         if (text) {
146d11ebe2cSGerd Hoffmann             qemu_clipboard_set_data(&gd->cbpeer, info, type,
147d11ebe2cSGerd Hoffmann                                     strlen(text), text, true);
148d11ebe2cSGerd Hoffmann             g_free(text);
149d11ebe2cSGerd Hoffmann         }
150d11ebe2cSGerd Hoffmann         break;
151d11ebe2cSGerd Hoffmann     default:
152d11ebe2cSGerd Hoffmann         break;
153d11ebe2cSGerd Hoffmann     }
154d11ebe2cSGerd Hoffmann }
155d11ebe2cSGerd Hoffmann 
gd_owner_change(GtkClipboard * clipboard,GdkEvent * event,gpointer data)156d11ebe2cSGerd Hoffmann static void gd_owner_change(GtkClipboard *clipboard,
157d11ebe2cSGerd Hoffmann                             GdkEvent *event,
158d11ebe2cSGerd Hoffmann                             gpointer data)
159d11ebe2cSGerd Hoffmann {
160d11ebe2cSGerd Hoffmann     GtkDisplayState *gd = data;
161d11ebe2cSGerd Hoffmann     QemuClipboardSelection s = gd_find_selection(gd, clipboard);
162d11ebe2cSGerd Hoffmann     QemuClipboardInfo *info;
163d11ebe2cSGerd Hoffmann 
164d11ebe2cSGerd Hoffmann     if (gd->cbowner[s]) {
165d11ebe2cSGerd Hoffmann         /* ignore notifications about our own grabs */
166d11ebe2cSGerd Hoffmann         return;
167d11ebe2cSGerd Hoffmann     }
168d11ebe2cSGerd Hoffmann 
169d11ebe2cSGerd Hoffmann 
170d11ebe2cSGerd Hoffmann     switch (event->owner_change.reason) {
1716b32aef0SMarc-André Lureau     case GDK_OWNER_CHANGE_NEW_OWNER:
172d11ebe2cSGerd Hoffmann         info = qemu_clipboard_info_new(&gd->cbpeer, s);
173d11ebe2cSGerd Hoffmann         if (gtk_clipboard_wait_is_text_available(clipboard)) {
174d11ebe2cSGerd Hoffmann             info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
175d11ebe2cSGerd Hoffmann         }
176d11ebe2cSGerd Hoffmann 
177d11ebe2cSGerd Hoffmann         qemu_clipboard_update(info);
178d11ebe2cSGerd Hoffmann         qemu_clipboard_info_unref(info);
179d11ebe2cSGerd Hoffmann         break;
180d11ebe2cSGerd Hoffmann     default:
1818038c5b6SMarc-André Lureau         qemu_clipboard_peer_release(&gd->cbpeer, s);
1828038c5b6SMarc-André Lureau         gd->cbowner[s] = false;
183d11ebe2cSGerd Hoffmann         break;
184d11ebe2cSGerd Hoffmann     }
185d11ebe2cSGerd Hoffmann }
186d11ebe2cSGerd Hoffmann 
gd_clipboard_init(GtkDisplayState * gd)187d11ebe2cSGerd Hoffmann void gd_clipboard_init(GtkDisplayState *gd)
188d11ebe2cSGerd Hoffmann {
189d11ebe2cSGerd Hoffmann     gd->cbpeer.name = "gtk";
1901b17f1e9SMarc-André Lureau     gd->cbpeer.notifier.notify = gd_clipboard_notify;
191d11ebe2cSGerd Hoffmann     gd->cbpeer.request = gd_clipboard_request;
192d11ebe2cSGerd Hoffmann     qemu_clipboard_peer_register(&gd->cbpeer);
193d11ebe2cSGerd Hoffmann 
194d11ebe2cSGerd Hoffmann     gd->gtkcb[QEMU_CLIPBOARD_SELECTION_CLIPBOARD] =
195c311e8d7SMarc-André Lureau         gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
196d11ebe2cSGerd Hoffmann     gd->gtkcb[QEMU_CLIPBOARD_SELECTION_PRIMARY] =
197c311e8d7SMarc-André Lureau         gtk_clipboard_get(GDK_SELECTION_PRIMARY);
198d11ebe2cSGerd Hoffmann     gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECONDARY] =
199c311e8d7SMarc-André Lureau         gtk_clipboard_get(GDK_SELECTION_SECONDARY);
200d11ebe2cSGerd Hoffmann 
201d11ebe2cSGerd Hoffmann     g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_CLIPBOARD],
202d11ebe2cSGerd Hoffmann                      "owner-change", G_CALLBACK(gd_owner_change), gd);
203d11ebe2cSGerd Hoffmann     g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_PRIMARY],
204d11ebe2cSGerd Hoffmann                      "owner-change", G_CALLBACK(gd_owner_change), gd);
205d11ebe2cSGerd Hoffmann     g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECONDARY],
206d11ebe2cSGerd Hoffmann                      "owner-change", G_CALLBACK(gd_owner_change), gd);
207d11ebe2cSGerd Hoffmann }
208