mirror of
https://github.com/qemu/qemu.git
synced 2026-07-08 17:46:10 +00:00
cb_reset_serial_on_resume defers a clipboard serial reset until the
machine runs again. qemu_clipboard_change_state() performs that reset
on resume, but leaves the flag set.
As a result, every later transition back to RUNNING triggers another
QEMU_CLIPBOARD_RESET_SERIAL notification even when no reset is pending.
That causes unnecessary reset handling in clipboard backends such as
vdagent and dbus.
Clear the deferred flag before resetting so the reset is performed only
once for each deferred request.
Fixes: c967ff606b ("ui/clipboard: delay clipboard update when not running")
Signed-off-by: GuoHan Zhao <zhaoguohan@kylinos.cn>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-ID: <20260416070403.1683648-1-zhaoguohan@kylinos.cn>
259 lines
6.8 KiB
C
259 lines
6.8 KiB
C
#include "qemu/osdep.h"
|
|
#include "system/runstate.h"
|
|
#include "ui/clipboard.h"
|
|
#include "trace.h"
|
|
|
|
static NotifierList clipboard_notifiers =
|
|
NOTIFIER_LIST_INITIALIZER(clipboard_notifiers);
|
|
|
|
static QemuClipboardInfo *cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT];
|
|
|
|
static VMChangeStateEntry *cb_change_state_entry = NULL;
|
|
|
|
static bool cb_reset_serial_on_resume = false;
|
|
|
|
static const VMStateDescription vmstate_cbcontent = {
|
|
.name = "clipboard/content",
|
|
.version_id = 0,
|
|
.minimum_version_id = 0,
|
|
.fields = (const VMStateField[]) {
|
|
VMSTATE_BOOL(available, QemuClipboardContent),
|
|
VMSTATE_BOOL(requested, QemuClipboardContent),
|
|
VMSTATE_UINT32(size, QemuClipboardContent),
|
|
VMSTATE_VBUFFER_ALLOC_UINT32(data, QemuClipboardContent, 0, 0, size),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
const VMStateDescription vmstate_cbinfo = {
|
|
.name = "clipboard",
|
|
.version_id = 0,
|
|
.minimum_version_id = 0,
|
|
.fields = (const VMStateField[]) {
|
|
VMSTATE_INT32(selection, QemuClipboardInfo),
|
|
VMSTATE_BOOL(has_serial, QemuClipboardInfo),
|
|
VMSTATE_UINT32(serial, QemuClipboardInfo),
|
|
VMSTATE_STRUCT_ARRAY(types, QemuClipboardInfo, QEMU_CLIPBOARD_TYPE__COUNT, 0, vmstate_cbcontent, QemuClipboardContent),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static void qemu_clipboard_change_state(void *opaque, bool running, RunState state)
|
|
{
|
|
int i;
|
|
|
|
if (!running) {
|
|
return;
|
|
}
|
|
|
|
if (cb_reset_serial_on_resume) {
|
|
cb_reset_serial_on_resume = false;
|
|
qemu_clipboard_reset_serial();
|
|
}
|
|
|
|
for (i = 0; i < QEMU_CLIPBOARD_SELECTION__COUNT; i++) {
|
|
if (cbinfo[i]) {
|
|
qemu_clipboard_update(cbinfo[i]);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void qemu_clipboard_peer_register(QemuClipboardPeer *peer)
|
|
{
|
|
if (cb_change_state_entry == NULL) {
|
|
cb_change_state_entry = qemu_add_vm_change_state_handler(qemu_clipboard_change_state, NULL);
|
|
}
|
|
|
|
notifier_list_add(&clipboard_notifiers, &peer->notifier);
|
|
}
|
|
|
|
void qemu_clipboard_peer_unregister(QemuClipboardPeer *peer)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < QEMU_CLIPBOARD_SELECTION__COUNT; i++) {
|
|
qemu_clipboard_peer_release(peer, i);
|
|
}
|
|
notifier_remove(&peer->notifier);
|
|
}
|
|
|
|
bool qemu_clipboard_peer_owns(QemuClipboardPeer *peer,
|
|
QemuClipboardSelection selection)
|
|
{
|
|
QemuClipboardInfo *info = qemu_clipboard_info(selection);
|
|
|
|
return info && info->owner == peer;
|
|
}
|
|
|
|
void qemu_clipboard_peer_release(QemuClipboardPeer *peer,
|
|
QemuClipboardSelection selection)
|
|
{
|
|
g_autoptr(QemuClipboardInfo) info = NULL;
|
|
|
|
if (qemu_clipboard_peer_owns(peer, selection)) {
|
|
/* set empty clipboard info */
|
|
info = qemu_clipboard_info_new(NULL, selection);
|
|
qemu_clipboard_update(info);
|
|
}
|
|
}
|
|
|
|
bool qemu_clipboard_check_serial(QemuClipboardInfo *info, bool client)
|
|
{
|
|
bool ok;
|
|
|
|
if (!info->has_serial ||
|
|
!cbinfo[info->selection] ||
|
|
!cbinfo[info->selection]->has_serial) {
|
|
trace_clipboard_check_serial(-1, -1, true);
|
|
return true;
|
|
}
|
|
|
|
if (client) {
|
|
ok = info->serial >= cbinfo[info->selection]->serial;
|
|
} else {
|
|
ok = info->serial > cbinfo[info->selection]->serial;
|
|
}
|
|
|
|
trace_clipboard_check_serial(cbinfo[info->selection]->serial, info->serial, ok);
|
|
return ok;
|
|
}
|
|
|
|
void qemu_clipboard_update(QemuClipboardInfo *info)
|
|
{
|
|
uint32_t type;
|
|
QemuClipboardNotify notify = {
|
|
.type = QEMU_CLIPBOARD_UPDATE_INFO,
|
|
.info = info,
|
|
};
|
|
assert(info->selection < QEMU_CLIPBOARD_SELECTION__COUNT);
|
|
|
|
for (type = 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) {
|
|
/*
|
|
* If data is missing, the clipboard owner's 'request' callback needs to
|
|
* be set. Otherwise, there is no way to get the clipboard data and
|
|
* qemu_clipboard_request() cannot be called.
|
|
*/
|
|
if (info->types[type].available && !info->types[type].data) {
|
|
assert(info->owner && info->owner->request);
|
|
}
|
|
}
|
|
|
|
if (runstate_is_running() || runstate_check(RUN_STATE_SUSPENDED)) {
|
|
notifier_list_notify(&clipboard_notifiers, ¬ify);
|
|
}
|
|
|
|
if (cbinfo[info->selection] != info) {
|
|
qemu_clipboard_info_unref(cbinfo[info->selection]);
|
|
cbinfo[info->selection] = qemu_clipboard_info_ref(info);
|
|
}
|
|
}
|
|
|
|
QemuClipboardInfo *qemu_clipboard_info(QemuClipboardSelection selection)
|
|
{
|
|
assert(selection < QEMU_CLIPBOARD_SELECTION__COUNT);
|
|
|
|
return cbinfo[selection];
|
|
}
|
|
|
|
QemuClipboardInfo *qemu_clipboard_info_new(QemuClipboardPeer *owner,
|
|
QemuClipboardSelection selection)
|
|
{
|
|
QemuClipboardInfo *info = g_new0(QemuClipboardInfo, 1);
|
|
|
|
info->owner = owner;
|
|
info->selection = selection;
|
|
info->refcount = 1;
|
|
|
|
return info;
|
|
}
|
|
|
|
QemuClipboardInfo *qemu_clipboard_info_ref(QemuClipboardInfo *info)
|
|
{
|
|
info->refcount++;
|
|
return info;
|
|
}
|
|
|
|
void qemu_clipboard_info_unref(QemuClipboardInfo *info)
|
|
{
|
|
uint32_t type;
|
|
|
|
if (!info) {
|
|
return;
|
|
}
|
|
|
|
info->refcount--;
|
|
if (info->refcount > 0) {
|
|
return;
|
|
}
|
|
|
|
for (type = 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) {
|
|
g_free(info->types[type].data);
|
|
}
|
|
g_free(info);
|
|
}
|
|
|
|
void qemu_clipboard_request(QemuClipboardInfo *info,
|
|
QemuClipboardType type)
|
|
{
|
|
if (info->types[type].data ||
|
|
info->types[type].requested ||
|
|
!info->types[type].available ||
|
|
!info->owner)
|
|
return;
|
|
|
|
assert(info->owner->request);
|
|
|
|
info->types[type].requested = true;
|
|
info->owner->request(info, type);
|
|
}
|
|
|
|
void qemu_clipboard_reset_serial(void)
|
|
{
|
|
QemuClipboardNotify notify = { .type = QEMU_CLIPBOARD_RESET_SERIAL };
|
|
int i;
|
|
|
|
trace_clipboard_reset_serial();
|
|
|
|
for (i = 0; i < QEMU_CLIPBOARD_SELECTION__COUNT; i++) {
|
|
QemuClipboardInfo *info = qemu_clipboard_info(i);
|
|
if (info) {
|
|
info->serial = 0;
|
|
}
|
|
}
|
|
|
|
if (runstate_is_running() || runstate_check(RUN_STATE_SUSPENDED)) {
|
|
notifier_list_notify(&clipboard_notifiers, ¬ify);
|
|
} else {
|
|
cb_reset_serial_on_resume = true;
|
|
}
|
|
}
|
|
|
|
void qemu_clipboard_set_data(QemuClipboardPeer *peer,
|
|
QemuClipboardInfo *info,
|
|
QemuClipboardType type,
|
|
uint32_t size,
|
|
const void *data,
|
|
bool update)
|
|
{
|
|
if (!info ||
|
|
info->owner != peer) {
|
|
return;
|
|
}
|
|
|
|
g_free(info->types[type].data);
|
|
if (size) {
|
|
info->types[type].data = g_memdup2(data, size);
|
|
info->types[type].size = size;
|
|
info->types[type].available = true;
|
|
} else {
|
|
info->types[type].data = NULL;
|
|
info->types[type].size = 0;
|
|
info->types[type].available = false;
|
|
}
|
|
|
|
if (update) {
|
|
qemu_clipboard_update(info);
|
|
}
|
|
}
|