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