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 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 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 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 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 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 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 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 151 QemuClipboardInfo *qemu_clipboard_info(QemuClipboardSelection selection) 152 { 153 assert(selection < QEMU_CLIPBOARD_SELECTION__COUNT); 154 155 return cbinfo[selection]; 156 } 157 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 170 QemuClipboardInfo *qemu_clipboard_info_ref(QemuClipboardInfo *info) 171 { 172 info->refcount++; 173 return info; 174 } 175 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 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 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 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