From nobody Wed Feb 11 04:28:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=redhat.com Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1633815214062306.7921103853723; Sat, 9 Oct 2021 14:33:34 -0700 (PDT) Received: from localhost ([::1]:49238 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mZJyG-0001EU-R7 for importer@patchew.org; Sat, 09 Oct 2021 17:33:32 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:38620) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mZJho-0001cj-Qt for qemu-devel@nongnu.org; Sat, 09 Oct 2021 17:16:34 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]:27130) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mZJhm-0002SD-9B for qemu-devel@nongnu.org; Sat, 09 Oct 2021 17:16:32 -0400 Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-84-pJvefdm6N52qOFBmQ3ks2w-1; Sat, 09 Oct 2021 17:16:27 -0400 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id E1187801A93 for ; Sat, 9 Oct 2021 21:16:26 +0000 (UTC) Received: from localhost (unknown [10.39.208.17]) by smtp.corp.redhat.com (Postfix) with ESMTP id 5F90D10016FF; Sat, 9 Oct 2021 21:16:20 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1633814189; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=OJiD4qWq/cNCm47ccbMY/Ya6jANhPeTD4eAANh5Gvbc=; b=DWQEriWjXpSCA86gswHZxy0BPpNOEXipAbfvRNdhpFOWY9N9ewJ0hO7Jk7d/aXaINh9k6v +HRm8GBwrhnV3KOkyhPIDmQB6WjZM056zonpKAaEysgKUvWkzbn3D4eL9LrZXGG4OMxoOV vsHOPWsG3b4uo3vdZKG45MMcKlnDHes= X-MC-Unique: pJvefdm6N52qOFBmQ3ks2w-1 From: marcandre.lureau@redhat.com To: qemu-devel@nongnu.org Subject: [PATCH v2 31/37] ui/dbus: add clipboard interface Date: Sun, 10 Oct 2021 01:08:32 +0400 Message-Id: <20211009210838.2219430-32-marcandre.lureau@redhat.com> In-Reply-To: <20211009210838.2219430-1-marcandre.lureau@redhat.com> References: <20211009210838.2219430-1-marcandre.lureau@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=marcandre.lureau@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=170.10.133.124; envelope-from=marcandre.lureau@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -28 X-Spam_score: -2.9 X-Spam_bar: -- X-Spam_report: (-2.9 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.049, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H2=-0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= , kraxel@redhat.com Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1633815216113100001 From: Marc-Andr=C3=A9 Lureau Expose the clipboard API over D-Bus. See the interface documentation for further details. Signed-off-by: Marc-Andr=C3=A9 Lureau --- ui/dbus.h | 14 ++ ui/dbus-clipboard.c | 457 +++++++++++++++++++++++++++++++++++++++++++ ui/dbus.c | 7 + ui/dbus-display1.xml | 97 +++++++++ ui/meson.build | 1 + ui/trace-events | 3 + 6 files changed, 579 insertions(+) create mode 100644 ui/dbus-clipboard.c diff --git a/ui/dbus.h b/ui/dbus.h index ca1f0f4ab9..3e89eafcab 100644 --- a/ui/dbus.h +++ b/ui/dbus.h @@ -27,9 +27,16 @@ #include "qemu/dbus.h" #include "qom/object.h" #include "ui/console.h" +#include "ui/clipboard.h" =20 #include "dbus-display1.h" =20 +typedef struct DBusClipboardRequest { + GDBusMethodInvocation *invocation; + QemuClipboardType type; + guint timeout_id; +} DBusClipboardRequest; + struct DBusDisplay { Object parent; =20 @@ -44,6 +51,11 @@ struct DBusDisplay { QemuDBusDisplay1VM *iface; GPtrArray *consoles; GCancellable *add_client_cancellable; + + QemuClipboardPeer clipboard_peer; + QemuDBusDisplay1Clipboard *clipboard; + QemuDBusDisplay1Clipboard *clipboard_proxy; + DBusClipboardRequest clipboard_request[QEMU_CLIPBOARD_SELECTION__COUNT= ]; }; =20 #define TYPE_DBUS_DISPLAY "dbus-display" @@ -83,4 +95,6 @@ dbus_display_listener_get_bus_name(DBusDisplayListener *d= dl); extern const DisplayChangeListenerOps dbus_gl_dcl_ops; extern const DisplayChangeListenerOps dbus_dcl_ops; =20 +void dbus_clipboard_init(DBusDisplay *dpy); + #endif /* UI_DBUS_H_ */ diff --git a/ui/dbus-clipboard.c b/ui/dbus-clipboard.c new file mode 100644 index 0000000000..5843d26cd2 --- /dev/null +++ b/ui/dbus-clipboard.c @@ -0,0 +1,457 @@ +/* + * QEMU DBus display + * + * Copyright (c) 2021 Marc-Andr=C3=A9 Lureau + * + * Permission is hereby granted, free of charge, to any person obtaining a= copy + * of this software and associated documentation files (the "Software"), t= o deal + * in the Software without restriction, including without limitation the r= ights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or se= ll + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included= in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS= OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OT= HER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING= FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS = IN + * THE SOFTWARE. + */ +#include "qemu/osdep.h" +#include "qemu/dbus.h" +#include "qemu/main-loop.h" +#include "qom/object_interfaces.h" +#include "sysemu/sysemu.h" +#include "qapi/error.h" +#include "trace.h" + +#include "dbus.h" + +#define MIME_TEXT_PLAIN_UTF8 "text/plain;charset=3Dutf-8" + +static void +dbus_clipboard_complete_request( + DBusDisplay *dpy, + GDBusMethodInvocation *invocation, + QemuClipboardInfo *info, + QemuClipboardType type) +{ + GVariant *v_data =3D g_variant_new_from_data( + G_VARIANT_TYPE("ay"), + info->types[type].data, + info->types[type].size, + TRUE, + (GDestroyNotify)qemu_clipboard_info_unref, + qemu_clipboard_info_ref(info)); + + qemu_dbus_display1_clipboard_complete_request( + dpy->clipboard, invocation, + MIME_TEXT_PLAIN_UTF8, v_data); +} + +static void +dbus_clipboard_update_info(DBusDisplay *dpy, QemuClipboardInfo *info) +{ + bool self_update =3D info->owner =3D=3D &dpy->clipboard_peer; + const char *mime[QEMU_CLIPBOARD_TYPE__COUNT + 1] =3D { 0, }; + DBusClipboardRequest *req; + int i =3D 0; + + if (info->owner =3D=3D NULL) { + if (dpy->clipboard_proxy) { + qemu_dbus_display1_clipboard_call_release( + dpy->clipboard_proxy, + info->selection, + G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); + } + return; + } + + if (self_update || !info->has_serial) { + return; + } + + req =3D &dpy->clipboard_request[info->selection]; + if (req->invocation && info->types[req->type].data) { + dbus_clipboard_complete_request(dpy, req->invocation, info, req->t= ype); + g_clear_object(&req->invocation); + g_source_remove(req->timeout_id); + req->timeout_id =3D 0; + return; + } + + if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) { + mime[i++] =3D MIME_TEXT_PLAIN_UTF8; + } + + if (i > 0) { + if (dpy->clipboard_proxy) { + qemu_dbus_display1_clipboard_call_grab( + dpy->clipboard_proxy, + info->selection, + info->serial, + mime, + G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); + } + } +} + +static void +dbus_clipboard_reset_serial(DBusDisplay *dpy) +{ + if (dpy->clipboard_proxy) { + qemu_dbus_display1_clipboard_call_register( + dpy->clipboard_proxy, + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, NULL, NULL); + } +} + +static void +dbus_clipboard_notify(Notifier *notifier, void *data) +{ + DBusDisplay *dpy =3D + container_of(notifier, DBusDisplay, clipboard_peer.notifier); + QemuClipboardNotify *notify =3D data; + + switch (notify->type) { + case QEMU_CLIPBOARD_UPDATE_INFO: + dbus_clipboard_update_info(dpy, notify->info); + return; + case QEMU_CLIPBOARD_RESET_SERIAL: + dbus_clipboard_reset_serial(dpy); + return; + } +} + +static void +dbus_clipboard_qemu_request(QemuClipboardInfo *info, + QemuClipboardType type) +{ + DBusDisplay *dpy =3D container_of(info->owner, DBusDisplay, clipboard_= peer); + g_autofree char *mime =3D NULL; + g_autoptr(GVariant) v_data =3D NULL; + g_autoptr(GError) err =3D NULL; + const char *data =3D NULL; + const char *mimes[] =3D { MIME_TEXT_PLAIN_UTF8, NULL }; + size_t n; + + if (type !=3D QEMU_CLIPBOARD_TYPE_TEXT) { + /* unsupported atm */ + return; + } + + if (dpy->clipboard_proxy) { + if (!qemu_dbus_display1_clipboard_call_request_sync( + dpy->clipboard_proxy, + info->selection, + mimes, + G_DBUS_CALL_FLAGS_NONE, -1, &mime, &v_data, NULL, &err)) { + error_report("Failed to request clipboard: %s", err->message); + return; + } + + if (g_strcmp0(mime, MIME_TEXT_PLAIN_UTF8)) { + error_report("Unsupported returned MIME: %s", mime); + return; + } + + data =3D g_variant_get_fixed_array(v_data, &n, 1); + qemu_clipboard_set_data(&dpy->clipboard_peer, info, type, + n, data, true); + } +} + +static void +dbus_clipboard_request_cancelled(DBusClipboardRequest *req) +{ + if (!req->invocation) { + return; + } + + g_dbus_method_invocation_return_error( + req->invocation, + DBUS_DISPLAY_ERROR, + DBUS_DISPLAY_ERROR_FAILED, + "Cancelled clipboard request"); + + g_clear_object(&req->invocation); + g_source_remove(req->timeout_id); + req->timeout_id =3D 0; +} + +static void +dbus_clipboard_unregister_proxy(DBusDisplay *dpy) +{ + const char *name =3D NULL; + int i; + + for (i =3D 0; i < G_N_ELEMENTS(dpy->clipboard_request); ++i) { + dbus_clipboard_request_cancelled(&dpy->clipboard_request[i]); + } + + if (!dpy->clipboard_proxy) { + return; + } + + name =3D g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)); + trace_dbus_clipboard_unregister(name); + g_clear_object(&dpy->clipboard_proxy); +} + +static void +dbus_on_clipboard_proxy_name_owner_changed( + DBusDisplay *dpy, + GObject *object, + GParamSpec *pspec) +{ + dbus_clipboard_unregister_proxy(dpy); +} + +static gboolean +dbus_clipboard_register( + DBusDisplay *dpy, + GDBusMethodInvocation *invocation) +{ + g_autoptr(GError) err =3D NULL; + const char *name =3D NULL; + + if (dpy->clipboard_proxy) { + g_dbus_method_invocation_return_error( + invocation, + DBUS_DISPLAY_ERROR, + DBUS_DISPLAY_ERROR_FAILED, + "Clipboard peer already registered!"); + return DBUS_METHOD_INVOCATION_HANDLED; + } + + dpy->clipboard_proxy =3D + qemu_dbus_display1_clipboard_proxy_new_sync( + g_dbus_method_invocation_get_connection(invocation), + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + g_dbus_method_invocation_get_sender(invocation), + "/org/qemu/Display1/Clipboard", + NULL, + &err); + if (!dpy->clipboard_proxy) { + g_dbus_method_invocation_return_error( + invocation, + DBUS_DISPLAY_ERROR, + DBUS_DISPLAY_ERROR_FAILED, + "Failed to setup proxy: %s", err->message); + return DBUS_METHOD_INVOCATION_HANDLED; + } + + name =3D g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)); + trace_dbus_clipboard_register(name); + + g_object_connect(dpy->clipboard_proxy, + "swapped-signal::notify::g-name-owner", + dbus_on_clipboard_proxy_name_owner_changed, dpy, + NULL); + qemu_clipboard_reset_serial(); + + qemu_dbus_display1_clipboard_complete_register(dpy->clipboard, invocat= ion); + return DBUS_METHOD_INVOCATION_HANDLED; +} + +static gboolean +dbus_clipboard_check_caller(DBusDisplay *dpy, GDBusMethodInvocation *invoc= ation) +{ + if (!dpy->clipboard_proxy || + g_strcmp0(g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)= ), + g_dbus_method_invocation_get_sender(invocation))) { + g_dbus_method_invocation_return_error( + invocation, + DBUS_DISPLAY_ERROR, + DBUS_DISPLAY_ERROR_FAILED, + "Unregistered caller"); + return FALSE; + } + + return TRUE; +} + +static gboolean +dbus_clipboard_unregister( + DBusDisplay *dpy, + GDBusMethodInvocation *invocation) +{ + if (!dbus_clipboard_check_caller(dpy, invocation)) { + return DBUS_METHOD_INVOCATION_HANDLED; + } + + dbus_clipboard_unregister_proxy(dpy); + + qemu_dbus_display1_clipboard_complete_unregister( + dpy->clipboard, invocation); + + return DBUS_METHOD_INVOCATION_HANDLED; +} + +static gboolean +dbus_clipboard_grab( + DBusDisplay *dpy, + GDBusMethodInvocation *invocation, + gint arg_selection, + guint arg_serial, + const gchar *const *arg_mimes) +{ + QemuClipboardSelection s =3D arg_selection; + g_autoptr(QemuClipboardInfo) info =3D NULL; + + if (!dbus_clipboard_check_caller(dpy, invocation)) { + return DBUS_METHOD_INVOCATION_HANDLED; + } + + if (s >=3D QEMU_CLIPBOARD_SELECTION__COUNT) { + g_dbus_method_invocation_return_error( + invocation, + DBUS_DISPLAY_ERROR, + DBUS_DISPLAY_ERROR_FAILED, + "Invalid clipboard selection: %d", arg_selection); + return DBUS_METHOD_INVOCATION_HANDLED; + } + + info =3D qemu_clipboard_info_new(&dpy->clipboard_peer, s); + if (g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8)) { + info->types[QEMU_CLIPBOARD_TYPE_TEXT].available =3D true; + } + info->serial =3D arg_serial; + info->has_serial =3D true; + if (qemu_clipboard_check_serial(info, true)) { + qemu_clipboard_update(info); + } else { + trace_dbus_clipboard_grab_failed(); + } + + qemu_dbus_display1_clipboard_complete_grab(dpy->clipboard, invocation); + return DBUS_METHOD_INVOCATION_HANDLED; +} + +static gboolean +dbus_clipboard_release( + DBusDisplay *dpy, + GDBusMethodInvocation *invocation, + gint arg_selection) +{ + if (!dbus_clipboard_check_caller(dpy, invocation)) { + return DBUS_METHOD_INVOCATION_HANDLED; + } + + qemu_clipboard_peer_release(&dpy->clipboard_peer, arg_selection); + + qemu_dbus_display1_clipboard_complete_release(dpy->clipboard, invocati= on); + return DBUS_METHOD_INVOCATION_HANDLED; +} + +static gboolean +dbus_clipboard_request_timeout(gpointer user_data) +{ + dbus_clipboard_request_cancelled(user_data); + return G_SOURCE_REMOVE; +} + +static gboolean +dbus_clipboard_request( + DBusDisplay *dpy, + GDBusMethodInvocation *invocation, + gint arg_selection, + const gchar *const *arg_mimes) +{ + QemuClipboardSelection s =3D arg_selection; + QemuClipboardType type =3D QEMU_CLIPBOARD_TYPE_TEXT; + QemuClipboardInfo *info =3D NULL; + + if (!dbus_clipboard_check_caller(dpy, invocation)) { + return DBUS_METHOD_INVOCATION_HANDLED; + } + + if (s >=3D QEMU_CLIPBOARD_SELECTION__COUNT) { + g_dbus_method_invocation_return_error( + invocation, + DBUS_DISPLAY_ERROR, + DBUS_DISPLAY_ERROR_FAILED, + "Invalid clipboard selection: %d", arg_selection); + return DBUS_METHOD_INVOCATION_HANDLED; + } + + if (dpy->clipboard_request[s].invocation) { + g_dbus_method_invocation_return_error( + invocation, + DBUS_DISPLAY_ERROR, + DBUS_DISPLAY_ERROR_FAILED, + "Pending request"); + return DBUS_METHOD_INVOCATION_HANDLED; + } + + info =3D qemu_clipboard_info(s); + if (!info || !info->owner || info->owner =3D=3D &dpy->clipboard_peer) { + g_dbus_method_invocation_return_error( + invocation, + DBUS_DISPLAY_ERROR, + DBUS_DISPLAY_ERROR_FAILED, + "Empty clipboard"); + return DBUS_METHOD_INVOCATION_HANDLED; + } + + if (!g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8) || + !info->types[type].available) { + g_dbus_method_invocation_return_error( + invocation, + DBUS_DISPLAY_ERROR, + DBUS_DISPLAY_ERROR_FAILED, + "Unhandled MIME types requested"); + return DBUS_METHOD_INVOCATION_HANDLED; + } + + if (info->types[type].data) { + dbus_clipboard_complete_request(dpy, invocation, info, type); + } else { + qemu_clipboard_request(info, type); + + dpy->clipboard_request[s].invocation =3D g_object_ref(invocation); + dpy->clipboard_request[s].type =3D type; + dpy->clipboard_request[s].timeout_id =3D + g_timeout_add_seconds(5, dbus_clipboard_request_timeout, + &dpy->clipboard_request[s]); + } + + return DBUS_METHOD_INVOCATION_HANDLED; +} + +void +dbus_clipboard_init(DBusDisplay *dpy) +{ + g_autoptr(GDBusObjectSkeleton) clipboard =3D NULL; + + assert(!dpy->clipboard); + + clipboard =3D g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Clipboar= d"); + dpy->clipboard =3D qemu_dbus_display1_clipboard_skeleton_new(); + g_object_connect(dpy->clipboard, + "swapped-signal::handle-register", + dbus_clipboard_register, dpy, + "swapped-signal::handle-unregister", + dbus_clipboard_unregister, dpy, + "swapped-signal::handle-grab", + dbus_clipboard_grab, dpy, + "swapped-signal::handle-release", + dbus_clipboard_release, dpy, + "swapped-signal::handle-request", + dbus_clipboard_request, dpy, + NULL); + + g_dbus_object_skeleton_add_interface( + G_DBUS_OBJECT_SKELETON(clipboard), + G_DBUS_INTERFACE_SKELETON(dpy->clipboard)); + g_dbus_object_manager_server_export(dpy->server, clipboard); + dpy->clipboard_peer.name =3D "dbus"; + dpy->clipboard_peer.notifier.notify =3D dbus_clipboard_notify; + dpy->clipboard_peer.request =3D dbus_clipboard_qemu_request; + qemu_clipboard_peer_register(&dpy->clipboard_peer); +} diff --git a/ui/dbus.c b/ui/dbus.c index 7cfbf1a80c..ac34fab3cb 100644 --- a/ui/dbus.c +++ b/ui/dbus.c @@ -24,6 +24,7 @@ #include "qemu/osdep.h" #include "qemu/cutils.h" #include "qemu/dbus.h" +#include "qemu/main-loop.h" #include "qemu/option.h" #include "qom/object_interfaces.h" #include "sysemu/sysemu.h" @@ -70,6 +71,8 @@ dbus_display_init(Object *o) g_dbus_object_skeleton_add_interface( vm, G_DBUS_INTERFACE_SKELETON(dd->iface)); g_dbus_object_manager_server_export(dd->server, vm); + + dbus_clipboard_init(dd); } =20 static void @@ -77,6 +80,9 @@ dbus_display_finalize(Object *o) { DBusDisplay *dd =3D DBUS_DISPLAY(o); =20 + qemu_clipboard_peer_unregister(&dd->clipboard_peer); + g_clear_object(&dd->clipboard); + g_clear_object(&dd->server); g_clear_pointer(&dd->consoles, g_ptr_array_unref); if (dd->add_client_cancellable) { @@ -289,6 +295,7 @@ set_audiodev(Object *o, const char *str, Error **errp) dd->audiodev =3D g_strdup(str); } =20 + static int get_gl_mode(Object *o, Error **errp) { diff --git a/ui/dbus-display1.xml b/ui/dbus-display1.xml index aff645220c..767562ad1e 100644 --- a/ui/dbus-display1.xml +++ b/ui/dbus-display1.xml @@ -376,6 +376,103 @@ =20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +