1 #include "qemu/osdep.h"
2 #include "system/runstate.h"
3 #include "ui/clipboard.h"
4 #include "trace.h"
5
6 static NotifierList clipboard_notifiers =
7 NOTIFIER_LIST_INITIALIZER(clipboard_notifiers);
8
9 static QemuClipboardInfo *cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT];
10
11 static VMChangeStateEntry *cb_change_state_entry = NULL;
12
13 static bool cb_reset_serial_on_resume = false;
14
15 static const VMStateDescription vmstate_cbcontent = {
16 .name = "clipboard/content",
17 .version_id = 0,
18 .minimum_version_id = 0,
19 .fields = (const VMStateField[]) {
20 VMSTATE_BOOL(available, QemuClipboardContent),
21 VMSTATE_BOOL(requested, QemuClipboardContent),
22 VMSTATE_UINT32(size, QemuClipboardContent),
23 VMSTATE_VBUFFER_ALLOC_UINT32(data, QemuClipboardContent, 0, 0, size),
24 VMSTATE_END_OF_LIST()
25 }
26 };
27
28 const VMStateDescription vmstate_cbinfo = {
29 .name = "clipboard",
30 .version_id = 0,
31 .minimum_version_id = 0,
32 .fields = (const VMStateField[]) {
33 VMSTATE_INT32(selection, QemuClipboardInfo),
34 VMSTATE_BOOL(has_serial, QemuClipboardInfo),
35 VMSTATE_UINT32(serial, QemuClipboardInfo),
36 VMSTATE_STRUCT_ARRAY(types, QemuClipboardInfo, QEMU_CLIPBOARD_TYPE__COUNT, 0, vmstate_cbcontent, QemuClipboardContent),
37 VMSTATE_END_OF_LIST()
38 }
39 };
40
qemu_clipboard_change_state(void * opaque,bool running,RunState state)41 static void qemu_clipboard_change_state(void *opaque, bool running, RunState state)
42 {
43 int i;
44
45 if (!running) {
46 return;
47 }
48
49 if (cb_reset_serial_on_resume) {
50 qemu_clipboard_reset_serial();
51 }
52
53 for (i = 0; i < QEMU_CLIPBOARD_SELECTION__COUNT; i++) {
54 if (cbinfo[i]) {
55 qemu_clipboard_update(cbinfo[i]);
56 }
57 }
58
59 }
60
qemu_clipboard_peer_register(QemuClipboardPeer * peer)61 void qemu_clipboard_peer_register(QemuClipboardPeer *peer)
62 {
63 if (cb_change_state_entry == NULL) {
64 cb_change_state_entry = qemu_add_vm_change_state_handler(qemu_clipboard_change_state, NULL);
65 }
66
67 notifier_list_add(&clipboard_notifiers, &peer->notifier);
68 }
69
qemu_clipboard_peer_unregister(QemuClipboardPeer * peer)70 void qemu_clipboard_peer_unregister(QemuClipboardPeer *peer)
71 {
72 int i;
73
74 for (i = 0; i < QEMU_CLIPBOARD_SELECTION__COUNT; i++) {
75 qemu_clipboard_peer_release(peer, i);
76 }
77 notifier_remove(&peer->notifier);
78 }
79
qemu_clipboard_peer_owns(QemuClipboardPeer * peer,QemuClipboardSelection selection)80 bool qemu_clipboard_peer_owns(QemuClipboardPeer *peer,
81 QemuClipboardSelection selection)
82 {
83 QemuClipboardInfo *info = qemu_clipboard_info(selection);
84
85 return info && info->owner == peer;
86 }
87
qemu_clipboard_peer_release(QemuClipboardPeer * peer,QemuClipboardSelection selection)88 void qemu_clipboard_peer_release(QemuClipboardPeer *peer,
89 QemuClipboardSelection selection)
90 {
91 g_autoptr(QemuClipboardInfo) info = NULL;
92
93 if (qemu_clipboard_peer_owns(peer, selection)) {
94 /* set empty clipboard info */
95 info = qemu_clipboard_info_new(NULL, selection);
96 qemu_clipboard_update(info);
97 }
98 }
99
qemu_clipboard_check_serial(QemuClipboardInfo * info,bool client)100 bool qemu_clipboard_check_serial(QemuClipboardInfo *info, bool client)
101 {
102 bool ok;
103
104 if (!info->has_serial ||
105 !cbinfo[info->selection] ||
106 !cbinfo[info->selection]->has_serial) {
107 trace_clipboard_check_serial(-1, -1, true);
108 return true;
109 }
110
111 if (client) {
112 ok = info->serial >= cbinfo[info->selection]->serial;
113 } else {
114 ok = info->serial > cbinfo[info->selection]->serial;
115 }
116
117 trace_clipboard_check_serial(cbinfo[info->selection]->serial, info->serial, ok);
118 return ok;
119 }
120
qemu_clipboard_update(QemuClipboardInfo * info)121 void qemu_clipboard_update(QemuClipboardInfo *info)
122 {
123 uint32_t type;
124 QemuClipboardNotify notify = {
125 .type = QEMU_CLIPBOARD_UPDATE_INFO,
126 .info = info,
127 };
128 assert(info->selection < QEMU_CLIPBOARD_SELECTION__COUNT);
129
130 for (type = 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) {
131 /*
132 * If data is missing, the clipboard owner's 'request' callback needs to
133 * be set. Otherwise, there is no way to get the clipboard data and
134 * qemu_clipboard_request() cannot be called.
135 */
136 if (info->types[type].available && !info->types[type].data) {
137 assert(info->owner && info->owner->request);
138 }
139 }
140
141 if (runstate_is_running() || runstate_check(RUN_STATE_SUSPENDED)) {
142 notifier_list_notify(&clipboard_notifiers, ¬ify);
143 }
144
145 if (cbinfo[info->selection] != info) {
146 qemu_clipboard_info_unref(cbinfo[info->selection]);
147 cbinfo[info->selection] = qemu_clipboard_info_ref(info);
148 }
149 }
150
qemu_clipboard_info(QemuClipboardSelection selection)151 QemuClipboardInfo *qemu_clipboard_info(QemuClipboardSelection selection)
152 {
153 assert(selection < QEMU_CLIPBOARD_SELECTION__COUNT);
154
155 return cbinfo[selection];
156 }
157
qemu_clipboard_info_new(QemuClipboardPeer * owner,QemuClipboardSelection selection)158 QemuClipboardInfo *qemu_clipboard_info_new(QemuClipboardPeer *owner,
159 QemuClipboardSelection selection)
160 {
161 QemuClipboardInfo *info = g_new0(QemuClipboardInfo, 1);
162
163 info->owner = owner;
164 info->selection = selection;
165 info->refcount = 1;
166
167 return info;
168 }
169
qemu_clipboard_info_ref(QemuClipboardInfo * info)170 QemuClipboardInfo *qemu_clipboard_info_ref(QemuClipboardInfo *info)
171 {
172 info->refcount++;
173 return info;
174 }
175
qemu_clipboard_info_unref(QemuClipboardInfo * info)176 void qemu_clipboard_info_unref(QemuClipboardInfo *info)
177 {
178 uint32_t type;
179
180 if (!info) {
181 return;
182 }
183
184 info->refcount--;
185 if (info->refcount > 0) {
186 return;
187 }
188
189 for (type = 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) {
190 g_free(info->types[type].data);
191 }
192 g_free(info);
193 }
194
qemu_clipboard_request(QemuClipboardInfo * info,QemuClipboardType type)195 void qemu_clipboard_request(QemuClipboardInfo *info,
196 QemuClipboardType type)
197 {
198 if (info->types[type].data ||
199 info->types[type].requested ||
200 !info->types[type].available ||
201 !info->owner)
202 return;
203
204 assert(info->owner->request);
205
206 info->types[type].requested = true;
207 info->owner->request(info, type);
208 }
209
qemu_clipboard_reset_serial(void)210 void qemu_clipboard_reset_serial(void)
211 {
212 QemuClipboardNotify notify = { .type = QEMU_CLIPBOARD_RESET_SERIAL };
213 int i;
214
215 trace_clipboard_reset_serial();
216
217 for (i = 0; i < QEMU_CLIPBOARD_SELECTION__COUNT; i++) {
218 QemuClipboardInfo *info = qemu_clipboard_info(i);
219 if (info) {
220 info->serial = 0;
221 }
222 }
223
224 if (runstate_is_running() || runstate_check(RUN_STATE_SUSPENDED)) {
225 notifier_list_notify(&clipboard_notifiers, ¬ify);
226 } else {
227 cb_reset_serial_on_resume = true;
228 }
229 }
230
qemu_clipboard_set_data(QemuClipboardPeer * peer,QemuClipboardInfo * info,QemuClipboardType type,uint32_t size,const void * data,bool update)231 void qemu_clipboard_set_data(QemuClipboardPeer *peer,
232 QemuClipboardInfo *info,
233 QemuClipboardType type,
234 uint32_t size,
235 const void *data,
236 bool update)
237 {
238 if (!info ||
239 info->owner != peer) {
240 return;
241 }
242
243 g_free(info->types[type].data);
244 if (size) {
245 info->types[type].data = g_memdup2(data, size);
246 info->types[type].size = size;
247 info->types[type].available = true;
248 } else {
249 info->types[type].data = NULL;
250 info->types[type].size = 0;
251 info->types[type].available = false;
252 }
253
254 if (update) {
255 qemu_clipboard_update(info);
256 }
257 }
258