aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Henderson <richard.henderson@linaro.org>2021-12-21 08:00:26 -0800
committerRichard Henderson <richard.henderson@linaro.org>2021-12-21 08:00:26 -0800
commit5316e12bb2b4408a1597b283ef4bb4794dd7b4f7 (patch)
tree7433951bf002780d937f18539156d97af13d5bc7
parent2bf40d0841b942e7ba12953d515e62a436f0af84 (diff)
parent89f4df9595e162ce4cc65f31a994a31e3e45ff3a (diff)
Merge tag 'dbus-pull-request' of https://gitlab.com/marcandre.lureau/qemu into staging
Add D-Bus display backend # gpg: Signature made Mon 20 Dec 2021 10:57:18 PM PST # gpg: using RSA key 87A9BD933F87C606D276F62DDAE8E10975969CE5 # gpg: issuer "marcandre.lureau@redhat.com" # gpg: Good signature from "Marc-André Lureau <marcandre.lureau@redhat.com>" [unknown] # gpg: aka "Marc-André Lureau <marcandre.lureau@gmail.com>" [unknown] # gpg: WARNING: This key is not certified with a trusted signature! # gpg: There is no indication that the signature belongs to the owner. # Primary key fingerprint: 87A9 BD93 3F87 C606 D276 F62D DAE8 E109 7596 9CE5 * tag 'dbus-pull-request' of https://gitlab.com/marcandre.lureau/qemu: (36 commits) MAINTAINERS: update D-Bus section ui/dbus: register D-Bus VC handler ui/dbus: add chardev backend & interface option: add g_auto for QemuOpts chardev: make socket derivable chardev: teach socket to accept no addresses ui/dbus: add clipboard interface audio: add "dbus" audio backend tests: start dbus-display-test tests/qtests: add qtest_qmp_add_client() ui/dbus: add p2p=on/off option ui: add a D-Bus display backend build-sys: set glib dependency version docs: add dbus-display documentation docs: move D-Bus VMState documentation to source XML backends: move dbus-vmstate1.xml to backends/ docs/sphinx: add sphinx modules to include D-Bus documentation scripts: teach modinfo to skip non-C sources console: save current scanout details ui: move qemu_spice_fill_device_address to ui/util.c ... Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
-rw-r--r--MAINTAINERS10
-rw-r--r--audio/audio.c1
-rw-r--r--audio/audio_int.h7
-rw-r--r--audio/audio_template.h2
-rw-r--r--audio/dbusaudio.c654
-rw-r--r--audio/meson.build6
-rw-r--r--audio/trace-events5
-rw-r--r--backends/dbus-vmstate1.xml52
-rw-r--r--chardev/char-socket.c72
-rwxr-xr-xconfigure1
-rw-r--r--docs/conf.py8
-rw-r--r--docs/interop/dbus-display.rst31
-rw-r--r--docs/interop/dbus-vmstate.rst52
-rw-r--r--docs/interop/dbus.rst2
-rw-r--r--docs/interop/index.rst1
-rw-r--r--docs/sphinx/dbusdoc.py166
-rw-r--r--docs/sphinx/dbusdomain.py406
-rw-r--r--docs/sphinx/dbusparser.py373
-rw-r--r--docs/sphinx/fakedbusdoc.py25
-rw-r--r--hw/display/qxl.c7
-rw-r--r--hw/display/vhost-user-gpu.c2
-rw-r--r--hw/display/virtio-gpu-base.c5
-rw-r--r--hw/display/virtio-gpu-virgl.c3
-rw-r--r--hw/display/virtio-vga.c11
-rw-r--r--include/chardev/char-socket.h86
-rw-r--r--include/qemu/cutils.h5
-rw-r--r--include/qemu/dbus.h24
-rw-r--r--include/qemu/option.h2
-rw-r--r--include/ui/clipboard.h55
-rw-r--r--include/ui/console.h70
-rw-r--r--include/ui/dbus-display.h17
-rw-r--r--include/ui/dbus-module.h11
-rw-r--r--include/ui/egl-context.h6
-rw-r--r--include/ui/gtk.h11
-rw-r--r--include/ui/sdl2.h7
-rw-r--r--include/ui/spice-display.h5
-rw-r--r--meson.build22
-rw-r--r--meson_options.txt2
-rw-r--r--monitor/qmp-cmds.c13
-rw-r--r--qapi/audio.json3
-rw-r--r--qapi/char.json27
-rw-r--r--qapi/misc.json4
-rw-r--r--qapi/ui.json34
-rw-r--r--qemu-options.hx20
-rw-r--r--scripts/meson-buildoptions.sh3
-rwxr-xr-xscripts/modinfo-collect.py3
-rw-r--r--tests/qtest/dbus-display-test.c257
-rw-r--r--tests/qtest/dbus-vmstate1.xml12
-rw-r--r--tests/qtest/libqos/libqtest.h10
-rw-r--r--tests/qtest/libqtest.c19
-rw-r--r--tests/qtest/meson.build10
-rw-r--r--ui/clipboard.c34
-rw-r--r--ui/cocoa.m22
-rw-r--r--ui/console.c305
-rw-r--r--ui/dbus-chardev.c296
-rw-r--r--ui/dbus-clipboard.c457
-rw-r--r--ui/dbus-console.c497
-rw-r--r--ui/dbus-display1.xml761
-rw-r--r--ui/dbus-error.c48
-rw-r--r--ui/dbus-listener.c486
-rw-r--r--ui/dbus-module.c35
-rw-r--r--ui/dbus.c482
-rw-r--r--ui/dbus.h144
-rw-r--r--ui/egl-context.c6
-rw-r--r--ui/egl-headless.c20
-rw-r--r--ui/gtk-clipboard.c23
-rw-r--r--ui/gtk-egl.c12
-rw-r--r--ui/gtk-gl-area.c10
-rw-r--r--ui/gtk.c28
-rw-r--r--ui/meson.build28
-rw-r--r--ui/sdl2-gl.c12
-rw-r--r--ui/sdl2.c16
-rw-r--r--ui/spice-core.c50
-rw-r--r--ui/spice-display.c27
-rw-r--r--ui/trace-events15
-rw-r--r--ui/util.c75
-rw-r--r--ui/vdagent.c94
-rw-r--r--ui/vnc-clipboard.c23
-rw-r--r--ui/vnc.c4
79 files changed, 6249 insertions, 401 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 1de6ce6e44..dc4b6f7c1e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2873,11 +2873,15 @@ D-Bus
M: Marc-André Lureau <marcandre.lureau@redhat.com>
S: Maintained
F: backends/dbus-vmstate.c
-F: tests/dbus-vmstate*
+F: ui/dbus*
+F: audio/dbus*
F: util/dbus.c
+F: include/ui/dbus*
F: include/qemu/dbus.h
-F: docs/interop/dbus.rst
-F: docs/interop/dbus-vmstate.rst
+F: docs/interop/dbus*
+F: docs/sphinx/dbus*
+F: docs/sphinx/fakedbusdoc.py
+F: tests/qtest/dbus*
Seccomp
M: Eduardo Otubo <otubo@redhat.com>
diff --git a/audio/audio.c b/audio/audio.c
index 54a153c0ef..dc28685d22 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -2000,6 +2000,7 @@ void audio_create_pdos(Audiodev *dev)
CASE(NONE, none, );
CASE(ALSA, alsa, Alsa);
CASE(COREAUDIO, coreaudio, Coreaudio);
+ CASE(DBUS, dbus, );
CASE(DSOUND, dsound, );
CASE(JACK, jack, Jack);
CASE(OSS, oss, Oss);
diff --git a/audio/audio_int.h b/audio/audio_int.h
index 6d685e24a3..428a091d05 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -31,6 +31,10 @@
#endif
#include "mixeng.h"
+#ifdef CONFIG_GIO
+#include <gio/gio.h>
+#endif
+
struct audio_pcm_ops;
struct audio_callback {
@@ -140,6 +144,9 @@ struct audio_driver {
const char *descr;
void *(*init) (Audiodev *);
void (*fini) (void *);
+#ifdef CONFIG_GIO
+ void (*set_dbus_server) (AudioState *s, GDBusObjectManagerServer *manager);
+#endif
struct audio_pcm_ops *pcm_ops;
int can_be_default;
int max_voices_out;
diff --git a/audio/audio_template.h b/audio/audio_template.h
index c6714946aa..d2d348638b 100644
--- a/audio/audio_template.h
+++ b/audio/audio_template.h
@@ -327,6 +327,8 @@ AudiodevPerDirectionOptions *glue(audio_get_pdo_, TYPE)(Audiodev *dev)
case AUDIODEV_DRIVER_COREAUDIO:
return qapi_AudiodevCoreaudioPerDirectionOptions_base(
dev->u.coreaudio.TYPE);
+ case AUDIODEV_DRIVER_DBUS:
+ return dev->u.dbus.TYPE;
case AUDIODEV_DRIVER_DSOUND:
return dev->u.dsound.TYPE;
case AUDIODEV_DRIVER_JACK:
diff --git a/audio/dbusaudio.c b/audio/dbusaudio.c
new file mode 100644
index 0000000000..f178b47dee
--- /dev/null
+++ b/audio/dbusaudio.c
@@ -0,0 +1,654 @@
+/*
+ * QEMU DBus audio
+ *
+ * Copyright (c) 2021 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * 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 OTHER
+ * 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/error-report.h"
+#include "qemu/host-utils.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "qemu/dbus.h"
+
+#include <gio/gunixfdlist.h>
+#include "ui/dbus-display1.h"
+
+#define AUDIO_CAP "dbus"
+#include "audio.h"
+#include "audio_int.h"
+#include "trace.h"
+
+#define DBUS_DISPLAY1_AUDIO_PATH DBUS_DISPLAY1_ROOT "/Audio"
+
+#define DBUS_AUDIO_NSAMPLES 1024 /* could be configured? */
+
+typedef struct DBusAudio {
+ GDBusObjectManagerServer *server;
+ GDBusObjectSkeleton *audio;
+ QemuDBusDisplay1Audio *iface;
+ GHashTable *out_listeners;
+ GHashTable *in_listeners;
+} DBusAudio;
+
+typedef struct DBusVoiceOut {
+ HWVoiceOut hw;
+ bool enabled;
+ RateCtl rate;
+
+ void *buf;
+ size_t buf_pos;
+ size_t buf_size;
+
+ bool has_volume;
+ Volume volume;
+} DBusVoiceOut;
+
+typedef struct DBusVoiceIn {
+ HWVoiceIn hw;
+ bool enabled;
+ RateCtl rate;
+
+ bool has_volume;
+ Volume volume;
+} DBusVoiceIn;
+
+static void *dbus_get_buffer_out(HWVoiceOut *hw, size_t *size)
+{
+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+
+ if (!vo->buf) {
+ vo->buf_size = hw->samples * hw->info.bytes_per_frame;
+ vo->buf = g_malloc(vo->buf_size);
+ vo->buf_pos = 0;
+ }
+
+ *size = MIN(vo->buf_size - vo->buf_pos, *size);
+ *size = audio_rate_get_bytes(&hw->info, &vo->rate, *size);
+
+ return vo->buf + vo->buf_pos;
+
+}
+
+static size_t dbus_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioOutListener *listener = NULL;
+ g_autoptr(GBytes) bytes = NULL;
+ g_autoptr(GVariant) v_data = NULL;
+
+ assert(buf == vo->buf + vo->buf_pos && vo->buf_pos + size <= vo->buf_size);
+ vo->buf_pos += size;
+
+ trace_dbus_audio_put_buffer_out(size);
+
+ if (vo->buf_pos < vo->buf_size) {
+ return size;
+ }
+
+ bytes = g_bytes_new_take(g_steal_pointer(&vo->buf), vo->buf_size);
+ v_data = g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes, TRUE);
+ g_variant_ref_sink(v_data);
+
+ g_hash_table_iter_init(&iter, da->out_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ qemu_dbus_display1_audio_out_listener_call_write(
+ listener,
+ (uintptr_t)hw,
+ v_data,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ }
+
+ return size;
+}
+
+#ifdef HOST_WORDS_BIGENDIAN
+#define AUDIO_HOST_BE TRUE
+#else
+#define AUDIO_HOST_BE FALSE
+#endif
+
+static void
+dbus_init_out_listener(QemuDBusDisplay1AudioOutListener *listener,
+ HWVoiceOut *hw)
+{
+ qemu_dbus_display1_audio_out_listener_call_init(
+ listener,
+ (uintptr_t)hw,
+ hw->info.bits,
+ hw->info.is_signed,
+ hw->info.is_float,
+ hw->info.freq,
+ hw->info.nchannels,
+ hw->info.bytes_per_frame,
+ hw->info.bytes_per_second,
+ hw->info.swap_endianness ? !AUDIO_HOST_BE : AUDIO_HOST_BE,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static int
+dbus_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioOutListener *listener = NULL;
+
+ audio_pcm_init_info(&hw->info, as);
+ hw->samples = DBUS_AUDIO_NSAMPLES;
+ audio_rate_start(&vo->rate);
+
+ g_hash_table_iter_init(&iter, da->out_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ dbus_init_out_listener(listener, hw);
+ }
+ return 0;
+}
+
+static void
+dbus_fini_out(HWVoiceOut *hw)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioOutListener *listener = NULL;
+
+ g_hash_table_iter_init(&iter, da->out_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ qemu_dbus_display1_audio_out_listener_call_fini(
+ listener,
+ (uintptr_t)hw,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ }
+
+ g_clear_pointer(&vo->buf, g_free);
+}
+
+static void
+dbus_enable_out(HWVoiceOut *hw, bool enable)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioOutListener *listener = NULL;
+
+ vo->enabled = enable;
+ if (enable) {
+ audio_rate_start(&vo->rate);
+ }
+
+ g_hash_table_iter_init(&iter, da->out_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ qemu_dbus_display1_audio_out_listener_call_set_enabled(
+ listener, (uintptr_t)hw, enable,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ }
+}
+
+static void
+dbus_volume_out_listener(HWVoiceOut *hw,
+ QemuDBusDisplay1AudioOutListener *listener)
+{
+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+ Volume *vol = &vo->volume;
+ g_autoptr(GBytes) bytes = NULL;
+ GVariant *v_vol = NULL;
+
+ if (!vo->has_volume) {
+ return;
+ }
+
+ assert(vol->channels < sizeof(vol->vol));
+ bytes = g_bytes_new(vol->vol, vol->channels);
+ v_vol = g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes, TRUE);
+ qemu_dbus_display1_audio_out_listener_call_set_volume(
+ listener, (uintptr_t)hw, vol->mute, v_vol,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void
+dbus_volume_out(HWVoiceOut *hw, Volume *vol)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioOutListener *listener = NULL;
+
+ vo->has_volume = true;
+ vo->volume = *vol;
+
+ g_hash_table_iter_init(&iter, da->out_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ dbus_volume_out_listener(hw, listener);
+ }
+}
+
+static void
+dbus_init_in_listener(QemuDBusDisplay1AudioInListener *listener, HWVoiceIn *hw)
+{
+ qemu_dbus_display1_audio_in_listener_call_init(
+ listener,
+ (uintptr_t)hw,
+ hw->info.bits,
+ hw->info.is_signed,
+ hw->info.is_float,
+ hw->info.freq,
+ hw->info.nchannels,
+ hw->info.bytes_per_frame,
+ hw->info.bytes_per_second,
+ hw->info.swap_endianness ? !AUDIO_HOST_BE : AUDIO_HOST_BE,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static int
+dbus_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioInListener *listener = NULL;
+
+ audio_pcm_init_info(&hw->info, as);
+ hw->samples = DBUS_AUDIO_NSAMPLES;
+ audio_rate_start(&vo->rate);
+
+ g_hash_table_iter_init(&iter, da->in_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ dbus_init_in_listener(listener, hw);
+ }
+ return 0;
+}
+
+static void
+dbus_fini_in(HWVoiceIn *hw)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioInListener *listener = NULL;
+
+ g_hash_table_iter_init(&iter, da->in_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ qemu_dbus_display1_audio_in_listener_call_fini(
+ listener,
+ (uintptr_t)hw,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ }
+}
+
+static void
+dbus_volume_in_listener(HWVoiceIn *hw,
+ QemuDBusDisplay1AudioInListener *listener)
+{
+ DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+ Volume *vol = &vo->volume;
+ g_autoptr(GBytes) bytes = NULL;
+ GVariant *v_vol = NULL;
+
+ if (!vo->has_volume) {
+ return;
+ }
+
+ assert(vol->channels < sizeof(vol->vol));
+ bytes = g_bytes_new(vol->vol, vol->channels);
+ v_vol = g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes, TRUE);
+ qemu_dbus_display1_audio_in_listener_call_set_volume(
+ listener, (uintptr_t)hw, vol->mute, v_vol,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void
+dbus_volume_in(HWVoiceIn *hw, Volume *vol)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioInListener *listener = NULL;
+
+ vo->has_volume = true;
+ vo->volume = *vol;
+
+ g_hash_table_iter_init(&iter, da->in_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ dbus_volume_in_listener(hw, listener);
+ }
+}
+
+static size_t
+dbus_read(HWVoiceIn *hw, void *buf, size_t size)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ /* DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw); */
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioInListener *listener = NULL;
+
+ trace_dbus_audio_read(size);
+
+ /* size = audio_rate_get_bytes(&hw->info, &vo->rate, size); */
+
+ g_hash_table_iter_init(&iter, da->in_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ g_autoptr(GVariant) v_data = NULL;
+ const char *data;
+ gsize n = 0;
+
+ if (qemu_dbus_display1_audio_in_listener_call_read_sync(
+ listener,
+ (uintptr_t)hw,
+ size,
+ G_DBUS_CALL_FLAGS_NONE, -1,
+ &v_data, NULL, NULL)) {
+ data = g_variant_get_fixed_array(v_data, &n, 1);
+ g_warn_if_fail(n <= size);
+ size = MIN(n, size);
+ memcpy(buf, data, size);
+ break;
+ }
+ }
+
+ return size;
+}
+
+static void
+dbus_enable_in(HWVoiceIn *hw, bool enable)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioInListener *listener = NULL;
+
+ vo->enabled = enable;
+ if (enable) {
+ audio_rate_start(&vo->rate);
+ }
+
+ g_hash_table_iter_init(&iter, da->in_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ qemu_dbus_display1_audio_in_listener_call_set_enabled(
+ listener, (uintptr_t)hw, enable,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ }
+}
+
+static void *
+dbus_audio_init(Audiodev *dev)
+{
+ DBusAudio *da = g_new0(DBusAudio, 1);
+
+ da->out_listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+ da->in_listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+ return da;
+}
+
+static void
+dbus_audio_fini(void *opaque)
+{
+ DBusAudio *da = opaque;
+
+ if (da->server) {
+ g_dbus_object_manager_server_unexport(da->server,
+ DBUS_DISPLAY1_AUDIO_PATH);
+ }
+ g_clear_object(&da->audio);
+ g_clear_object(&da->iface);
+ g_clear_pointer(&da->in_listeners, g_hash_table_unref);
+ g_clear_pointer(&da->out_listeners, g_hash_table_unref);
+ g_clear_object(&da->server);
+ g_free(da);
+}
+
+static void
+listener_out_vanished_cb(GDBusConnection *connection,
+ gboolean remote_peer_vanished,
+ GError *error,
+ DBusAudio *da)
+{
+ char *name = g_object_get_data(G_OBJECT(connection), "name");
+
+ g_hash_table_remove(da->out_listeners, name);
+}
+
+static void
+listener_in_vanished_cb(GDBusConnection *connection,
+ gboolean remote_peer_vanished,
+ GError *error,
+ DBusAudio *da)
+{
+ char *name = g_object_get_data(G_OBJECT(connection), "name");
+
+ g_hash_table_remove(da->in_listeners, name);
+}
+
+static gboolean
+dbus_audio_register_listener(AudioState *s,
+ GDBusMethodInvocation *invocation,
+ GUnixFDList *fd_list,
+ GVariant *arg_listener,
+ bool out)
+{
+ DBusAudio *da = s->drv_opaque;
+ const char *sender = g_dbus_method_invocation_get_sender(invocation);
+ g_autoptr(GDBusConnection) listener_conn = NULL;
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GSocket) socket = NULL;
+ g_autoptr(GSocketConnection) socket_conn = NULL;
+ g_autofree char *guid = g_dbus_generate_guid();
+ GHashTable *listeners = out ? da->out_listeners : da->in_listeners;
+ GObject *listener;
+ int fd;
+
+ trace_dbus_audio_register(sender, out ? "out" : "in");
+
+ if (g_hash_table_contains(listeners, sender)) {
+ g_dbus_method_invocation_return_error(invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_INVALID,
+ "`%s` is already registered!",
+ sender);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err);
+ if (err) {
+ g_dbus_method_invocation_return_error(invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Couldn't get peer fd: %s",
+ err->message);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ socket = g_socket_new_from_fd(fd, &err);
+ if (err) {
+ g_dbus_method_invocation_return_error(invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Couldn't make a socket: %s",
+ err->message);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+ socket_conn = g_socket_connection_factory_create_connection(socket);
+ if (out) {
+ qemu_dbus_display1_audio_complete_register_out_listener(
+ da->iface, invocation, NULL);
+ } else {
+ qemu_dbus_display1_audio_complete_register_in_listener(
+ da->iface, invocation, NULL);
+ }
+
+ listener_conn =
+ g_dbus_connection_new_sync(
+ G_IO_STREAM(socket_conn),
+ guid,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
+ NULL, NULL, &err);
+ if (err) {
+ error_report("Failed to setup peer connection: %s", err->message);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ listener = out ?
+ G_OBJECT(qemu_dbus_display1_audio_out_listener_proxy_new_sync(
+ listener_conn,
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+ NULL,
+ "/org/qemu/Display1/AudioOutListener",
+ NULL,
+ &err)) :
+ G_OBJECT(qemu_dbus_display1_audio_in_listener_proxy_new_sync(
+ listener_conn,
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+ NULL,
+ "/org/qemu/Display1/AudioInListener",
+ NULL,
+ &err));
+ if (!listener) {
+ error_report("Failed to setup proxy: %s", err->message);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ if (out) {
+ HWVoiceOut *hw;
+
+ QLIST_FOREACH(hw, &s->hw_head_out, entries) {
+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+ QemuDBusDisplay1AudioOutListener *l =
+ QEMU_DBUS_DISPLAY1_AUDIO_OUT_LISTENER(listener);
+
+ dbus_init_out_listener(l, hw);
+ qemu_dbus_display1_audio_out_listener_call_set_enabled(
+ l, (uintptr_t)hw, vo->enabled,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ }
+ } else {
+ HWVoiceIn *hw;
+
+ QLIST_FOREACH(hw, &s->hw_head_in, entries) {
+ DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+ QemuDBusDisplay1AudioInListener *l =
+ QEMU_DBUS_DISPLAY1_AUDIO_IN_LISTENER(listener);
+
+ dbus_init_in_listener(
+ QEMU_DBUS_DISPLAY1_AUDIO_IN_LISTENER(listener), hw);
+ qemu_dbus_display1_audio_in_listener_call_set_enabled(
+ l, (uintptr_t)hw, vo->enabled,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ }
+ }
+
+ g_object_set_data_full(G_OBJECT(listener_conn), "name",
+ g_strdup(sender), g_free);
+ g_hash_table_insert(listeners, g_strdup(sender), listener);
+ g_object_connect(listener_conn,
+ "signal::closed",
+ out ? listener_out_vanished_cb : listener_in_vanished_cb,
+ da,
+ NULL);
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_audio_register_out_listener(AudioState *s,
+ GDBusMethodInvocation *invocation,
+ GUnixFDList *fd_list,
+ GVariant *arg_listener)
+{
+ return dbus_audio_register_listener(s, invocation,
+ fd_list, arg_listener, true);
+
+}
+
+static gboolean
+dbus_audio_register_in_listener(AudioState *s,
+ GDBusMethodInvocation *invocation,
+ GUnixFDList *fd_list,
+ GVariant *arg_listener)
+{
+ return dbus_audio_register_listener(s, invocation,
+ fd_list, arg_listener, false);
+}
+
+static void
+dbus_audio_set_server(AudioState *s, GDBusObjectManagerServer *server)
+{
+ DBusAudio *da = s->drv_opaque;
+
+ g_assert(da);
+ g_assert(!da->server);
+
+ da->server = g_object_ref(server);
+
+ da->audio = g_dbus_object_skeleton_new(DBUS_DISPLAY1_AUDIO_PATH);
+ da->iface = qemu_dbus_display1_audio_skeleton_new();
+ g_object_connect(da->iface,
+ "swapped-signal::handle-register-in-listener",
+ dbus_audio_register_in_listener, s,
+ "swapped-signal::handle-register-out-listener",
+ dbus_audio_register_out_listener, s,
+ NULL);
+
+ g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(da->audio),
+ G_DBUS_INTERFACE_SKELETON(da->iface));
+ g_dbus_object_manager_server_export(da->server, da->audio);
+}
+
+static struct audio_pcm_ops dbus_pcm_ops = {
+ .init_out = dbus_init_out,
+ .fini_out = dbus_fini_out,
+ .write = audio_generic_write,
+ .get_buffer_out = dbus_get_buffer_out,
+ .put_buffer_out = dbus_put_buffer_out,
+ .enable_out = dbus_enable_out,
+ .volume_out = dbus_volume_out,
+
+ .init_in = dbus_init_in,
+ .fini_in = dbus_fini_in,
+ .read = dbus_read,
+ .run_buffer_in = audio_generic_run_buffer_in,
+ .enable_in = dbus_enable_in,
+ .volume_in = dbus_volume_in,
+};
+
+static struct audio_driver dbus_audio_driver = {
+ .name = "dbus",
+ .descr = "Timer based audio exposed with DBus interface",
+ .init = dbus_audio_init,
+ .fini = dbus_audio_fini,
+ .set_dbus_server = dbus_audio_set_server,
+ .pcm_ops = &dbus_pcm_ops,
+ .can_be_default = 1,
+ .max_voices_out = INT_MAX,
+ .max_voices_in = INT_MAX,
+ .voice_size_out = sizeof(DBusVoiceOut),
+ .voice_size_in = sizeof(DBusVoiceIn)
+};
+
+static void register_audio_dbus(void)
+{
+ audio_driver_register(&dbus_audio_driver);
+}
+type_init(register_audio_dbus);
+
+module_dep("ui-dbus")
diff --git a/audio/meson.build b/audio/meson.build
index 462533bb8c..0ac3791d0b 100644
--- a/audio/meson.build
+++ b/audio/meson.build
@@ -26,4 +26,10 @@ foreach m : [
endif
endforeach
+if dbus_display
+ module_ss = ss.source_set()
+ module_ss.add(when: gio, if_true: files('dbusaudio.c'))
+ audio_modules += {'dbus': module_ss}
+endif
+
modules += {'audio': audio_modules}
diff --git a/audio/trace-events b/audio/trace-events
index 957c92337b..e1ab643add 100644
--- a/audio/trace-events
+++ b/audio/trace-events
@@ -13,6 +13,11 @@ alsa_resume_out(void) "Resuming suspended output stream"
# ossaudio.c
oss_version(int version) "OSS version = 0x%x"
+# dbusaudio.c
+dbus_audio_register(const char *s, const char *dir) "sender = %s, dir = %s"
+dbus_audio_put_buffer_out(size_t len) "len = %zu"
+dbus_audio_read(size_t len) "len = %zu"
+
# audio.c
audio_timer_start(int interval) "interval %d ms"
audio_timer_stop(void) ""
diff --git a/backends/dbus-vmstate1.xml b/backends/dbus-vmstate1.xml
new file mode 100644
index 0000000000..601ee8dc7e
--- /dev/null
+++ b/backends/dbus-vmstate1.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+ <!--
+ org.qemu.VMState1:
+
+ This interface must be implemented at the object path
+ ``/org/qemu/VMState1`` to support helper migration.
+ -->
+ <interface name="org.qemu.VMState1">
+
+ <!--
+ Id:
+
+ A string that identifies the helper uniquely. (maximum 256 bytes
+ including terminating NUL byte)
+
+ .. note::
+
+ The VMState helper ID namespace is its own namespace. In particular,
+ it is not related to QEMU "id" used in -object/-device objects.
+ -->
+ <property name="Id" type="s" access="read"/>
+
+ <!--
+ Load:
+ @data: data to restore the state.
+
+ The method called on destination with the state to restore.
+
+ The helper may be initially started in a waiting state (with an
+ ``-incoming`` argument for example), and it may resume on success.
+
+ An error may be returned to the caller.
+ -->
+ <method name="Load">
+ <arg type="ay" name="data" direction="in"/>
+ </method>
+
+ <!--
+ Save:
+ @data: state data to save for later resume.
+
+ The method called on the source to get the current state to be
+ migrated. The helper should continue to run normally.
+
+ An error may be returned to the caller.
+ -->
+ <method name="Save">
+ <arg type="ay" name="data" direction="out"/>
+ </method>
+ </interface>
+</node>
diff --git a/chardev/char-socket.c b/chardev/char-socket.c
index 836cfa0bc2..d619088232 100644
--- a/chardev/char-socket.c
+++ b/chardev/char-socket.c
@@ -25,9 +25,7 @@
#include "qemu/osdep.h"
#include "chardev/char.h"
#include "io/channel-socket.h"
-#include "io/channel-tls.h"
#include "io/channel-websock.h"
-#include "io/net-listener.h"
#include "qemu/error-report.h"
#include "qemu/module.h"
#include "qemu/option.h"
@@ -37,61 +35,7 @@
#include "qemu/yank.h"
#include "chardev/char-io.h"
-#include "qom/object.h"
-
-/***********************************************************/
-/* TCP Net console */
-
-#define TCP_MAX_FDS 16
-
-typedef struct {
- char buf[21];
- size_t buflen;
-} TCPChardevTelnetInit;
-
-typedef enum {
- TCP_CHARDEV_STATE_DISCONNECTED,
- TCP_CHARDEV_STATE_CONNECTING,
- TCP_CHARDEV_STATE_CONNECTED,
-} TCPChardevState;
-
-struct SocketChardev {
- Chardev parent;
- QIOChannel *ioc; /* Client I/O channel */
- QIOChannelSocket *sioc; /* Client master channel */
- QIONetListener *listener;
- GSource *hup_source;
- QCryptoTLSCreds *tls_creds;
- char *tls_authz;
- TCPChardevState state;
- int max_size;
- int do_telnetopt;
- int do_nodelay;
- int *read_msgfds;
- size_t read_msgfds_num;
- int *write_msgfds;
- size_t write_msgfds_num;
- bool registered_yank;
-
- SocketAddress *addr;
- bool is_listen;
- bool is_telnet;
- bool is_tn3270;
- GSource *telnet_source;
- TCPChardevTelnetInit *telnet_init;
-
- bool is_websock;
-
- GSource *reconnect_timer;
- int64_t reconnect_time;
- bool connect_err_reported;
-
- QIOTask *connect_task;
-};
-typedef struct SocketChardev SocketChardev;
-
-DECLARE_INSTANCE_CHECKER(SocketChardev, SOCKET_CHARDEV,
- TYPE_CHARDEV_SOCKET)
+#include "chardev/char-socket.h"
static gboolean socket_reconnect_timeout(gpointer opaque);
static void tcp_chr_telnet_init(Chardev *chr);
@@ -1248,6 +1192,10 @@ static int qmp_chardev_open_socket_server(Chardev *chr,
qio_net_listener_set_name(s->listener, name);
g_free(name);
+ if (s->addr->type == SOCKET_ADDRESS_TYPE_FD && !*s->addr->u.fd.str) {
+ goto skip_listen;
+ }
+
if (qio_net_listener_open_sync(s->listener, s->addr, 1, errp) < 0) {
object_unref(OBJECT(s->listener));
s->listener = NULL;
@@ -1256,6 +1204,8 @@ static int qmp_chardev_open_socket_server(Chardev *chr,
qapi_free_SocketAddress(s->addr);
s->addr = socket_local_address(s->listener->sioc[0]->fd, errp);
+
+skip_listen:
update_disconnected_filename(s);
if (is_waitconnect) {
@@ -1466,9 +1416,9 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
SocketAddressLegacy *addr;
ChardevSocket *sock;
- if ((!!path + !!fd + !!host) != 1) {
+ if ((!!path + !!fd + !!host) > 1) {
error_setg(errp,
- "Exactly one of 'path', 'fd' or 'host' required");
+ "None or one of 'path', 'fd' or 'host' option required.");
return;
}
@@ -1542,12 +1492,10 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
.has_ipv6 = qemu_opt_get(opts, "ipv6"),
.ipv6 = qemu_opt_get_bool(opts, "ipv6", 0),
};
- } else if (fd) {
+ } else {
addr->type = SOCKET_ADDRESS_TYPE_FD;
addr->u.fd.data = g_new(String, 1);
addr->u.fd.data->str = g_strdup(fd);
- } else {
- g_assert_not_reached();
}
sock->addr = addr;
}
diff --git a/configure b/configure
index 8ccfe51673..51eae49daf 100755
--- a/configure
+++ b/configure
@@ -3694,6 +3694,7 @@ echo "QEMU_CFLAGS=$QEMU_CFLAGS" >> $config_host_mak
echo "QEMU_CXXFLAGS=$QEMU_CXXFLAGS" >> $config_host_mak
echo "GLIB_CFLAGS=$glib_cflags" >> $config_host_mak
echo "GLIB_LIBS=$glib_libs" >> $config_host_mak
+echo "GLIB_VERSION=$(pkg-config --modversion glib-2.0)" >> $config_host_mak
echo "QEMU_LDFLAGS=$QEMU_LDFLAGS" >> $config_host_mak
echo "LD_I386_EMULATION=$ld_i386_emulation" >> $config_host_mak
echo "EXESUF=$EXESUF" >> $config_host_mak
diff --git a/docs/conf.py b/docs/conf.py
index 763e7d2434..e79015975e 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -73,6 +73,12 @@ needs_sphinx = '1.6'
# ones.
extensions = ['kerneldoc', 'qmp_lexer', 'hxtool', 'depfile', 'qapidoc']
+if sphinx.version_info[:3] > (4, 0, 0):
+ tags.add('sphinx4')
+ extensions += ['dbusdoc']
+else:
+ extensions += ['fakedbusdoc']
+
# Add any paths that contain templates here, relative to this directory.
templates_path = [os.path.join(qemu_docdir, '_templates')]
@@ -311,3 +317,5 @@ kerneldoc_bin = ['perl', os.path.join(qemu_docdir, '../scripts/kernel-doc')]
kerneldoc_srctree = os.path.join(qemu_docdir, '..')
hxtool_srctree = os.path.join(qemu_docdir, '..')
qapidoc_srctree = os.path.join(qemu_docdir, '..')
+dbusdoc_srctree = os.path.join(qemu_docdir, '..')
+dbus_index_common_prefix = ["org.qemu."]
diff --git a/docs/interop/dbus-display.rst b/docs/interop/dbus-display.rst
new file mode 100644
index 0000000000..8c6e8e0f5a
--- /dev/null
+++ b/docs/interop/dbus-display.rst
@@ -0,0 +1,31 @@
+D-Bus display
+=============
+
+QEMU can export the VM display through D-Bus (when started with ``-display
+dbus``), to allow out-of-process UIs, remote protocol servers or other
+interactive display usages.
+
+Various specialized D-Bus interfaces are available on different object paths
+under ``/org/qemu/Display1/``, depending on the VM configuration.
+
+QEMU also implements the standard interfaces, such as
+`org.freedesktop.DBus.Introspectable
+<https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces>`_.
+
+.. contents::
+ :local:
+ :depth: 1
+
+.. only:: sphinx4
+
+ .. dbus-doc:: ui/dbus-display1.xml
+
+.. only:: not sphinx4
+
+ .. warning::
+ Sphinx 4 is required to build D-Bus documentation.
+
+ This is the content of ``ui/dbus-display1.xml``:
+
+ .. literalinclude:: ../../ui/dbus-display1.xml
+ :language: xml
diff --git a/docs/interop/dbus-vmstate.rst b/docs/interop/dbus-vmstate.rst
index 1d719c1c60..5fb3f279e2 100644
--- a/docs/interop/dbus-vmstate.rst
+++ b/docs/interop/dbus-vmstate.rst
@@ -2,9 +2,6 @@
D-Bus VMState
=============
-Introduction
-============
-
The QEMU dbus-vmstate object's aim is to migrate helpers' data running
on a QEMU D-Bus bus. (refer to the :doc:`dbus` document for
some recommendations on D-Bus usage)
@@ -26,49 +23,16 @@ dbus-vmstate object can be configured with the expected list of
helpers by setting its ``id-list`` property, with a comma-separated
``Id`` list.
-Interface
-=========
-
-On object path ``/org/qemu/VMState1``, the following
-``org.qemu.VMState1`` interface should be implemented:
-
-.. code:: xml
-
- <interface name="org.qemu.VMState1">
- <property name="Id" type="s" access="read"/>
- <method name="Load">
- <arg type="ay" name="data" direction="in"/>
- </method>
- <method name="Save">
- <arg type="ay" name="data" direction="out"/>
- </method>
- </interface>
-
-"Id" property
--------------
-
-A string that identifies the helper uniquely. (maximum 256 bytes
-including terminating NUL byte)
-
-.. note::
-
- The helper ID namespace is a separate namespace. In particular, it is not
- related to QEMU "id" used in -object/-device objects.
-
-Load(in u8[] bytes) method
---------------------------
-
-The method called on destination with the state to restore.
+.. only:: sphinx4
-The helper may be initially started in a waiting state (with
-an --incoming argument for example), and it may resume on success.
+ .. dbus-doc:: backends/dbus-vmstate1.xml
-An error may be returned to the caller.
+.. only:: not sphinx4
-Save(out u8[] bytes) method
----------------------------
+ .. warning::
+ Sphinx 4 is required to build D-Bus documentation.
-The method called on the source to get the current state to be
-migrated. The helper should continue to run normally.
+ This is the content of ``backends/dbus-vmstate1.xml``:
-An error may be returned to the caller.
+ .. literalinclude:: ../../backends/dbus-vmstate1.xml
+ :language: xml
diff --git a/docs/interop/dbus.rst b/docs/interop/dbus.rst
index be596d3f41..427debc9c5 100644
--- a/docs/interop/dbus.rst
+++ b/docs/interop/dbus.rst
@@ -108,3 +108,5 @@ QEMU Interfaces
===============
:doc:`dbus-vmstate`
+
+:doc:`dbus-display`
diff --git a/docs/interop/index.rst b/docs/interop/index.rst
index 47b9ed82bb..c59bac9834 100644
--- a/docs/interop/index.rst
+++ b/docs/interop/index.rst
@@ -12,6 +12,7 @@ are useful for making QEMU interoperate with other software.
bitmaps
dbus
dbus-vmstate
+ dbus-display
live-block-operations
pr-helper
qemu-ga
diff --git a/docs/sphinx/dbusdoc.py b/docs/sphinx/dbusdoc.py
new file mode 100644
index 0000000000..be284ed08f
--- /dev/null
+++ b/docs/sphinx/dbusdoc.py
@@ -0,0 +1,166 @@
+# D-Bus XML documentation extension
+#
+# Copyright (C) 2021, Red Hat Inc.
+#
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Author: Marc-André Lureau <marcandre.lureau@redhat.com>
+"""dbus-doc is a Sphinx extension that provides documentation from D-Bus XML."""
+
+import os
+import re
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ Dict,
+ Iterator,
+ List,
+ Optional,
+ Sequence,
+ Set,
+ Tuple,
+ Type,
+ TypeVar,
+ Union,
+)
+
+import sphinx
+from docutils import nodes
+from docutils.nodes import Element, Node
+from docutils.parsers.rst import Directive, directives
+from docutils.parsers.rst.states import RSTState
+from docutils.statemachine import StringList, ViewList
+from sphinx.application import Sphinx
+from sphinx.errors import ExtensionError
+from sphinx.util import logging
+from sphinx.util.docstrings import prepare_docstring
+from sphinx.util.docutils import SphinxDirective, switch_source_input
+from sphinx.util.nodes import nested_parse_with_titles
+
+import dbusdomain
+from dbusparser import parse_dbus_xml
+
+logger = logging.getLogger(__name__)
+
+__version__ = "1.0"
+
+
+class DBusDoc:
+ def __init__(self, sphinx_directive, dbusfile):
+ self._cur_doc = None
+ self._sphinx_directive = sphinx_directive
+ self._dbusfile = dbusfile
+ self._top_node = nodes.section()
+ self.result = StringList()
+ self.indent = ""
+
+ def add_line(self, line: str, *lineno: int) -> None:
+ """Append one line of generated reST to the output."""
+ if line.strip(): # not a blank line
+ self.result.append(self.indent + line, self._dbusfile, *lineno)
+ else:
+ self.result.append("", self._dbusfile, *lineno)
+
+ def add_method(self, method):
+ self.add_line(f".. dbus:method:: {method.name}")
+ self.add_line("")
+ self.indent += " "
+ for arg in method.in_args:
+ self.add_line(f":arg {arg.signature} {arg.name}: {arg.doc_string}")
+ for arg in method.out_args:
+ self.add_line(f":ret {arg.signature} {arg.name}: {arg.doc_string}")
+ self.add_line("")
+ for line in prepare_docstring("\n" + method.doc_string):
+ self.add_line(line)
+ self.indent = self.indent[:-3]
+
+ def add_signal(self, signal):
+ self.add_line(f".. dbus:signal:: {signal.name}")
+ self.add_line("")
+ self.indent += " "
+ for arg in signal.args:
+ self.add_line(f":arg {arg.signature} {arg.name}: {arg.doc_string}")
+ self.add_line("")
+ for line in prepare_docstring("\n" + signal.doc_string):
+ self.add_line(line)
+ self.indent = self.indent[:-3]
+
+ def add_property(self, prop):
+ self.add_line(f".. dbus:property:: {prop.name}")
+ self.indent += " "
+ self.add_line(f":type: {prop.signature}")
+ access = {"read": "readonly", "write": "writeonly", "readwrite": "readwrite"}[
+ prop.access
+ ]
+ self.add_line(f":{access}:")
+ if prop.emits_changed_signal:
+ self.add_line(f":emits-changed: yes")
+ self.add_line("")
+ for line in prepare_docstring("\n" + prop.doc_string):
+ self.add_line(line)
+ self.indent = self.indent[:-3]
+
+ def add_interface(self, iface):
+ self.add_line(f".. dbus:interface:: {iface.name}")
+ self.add_line("")
+ self.indent += " "
+ for line in prepare_docstring("\n" + iface.doc_string):
+ self.add_line(line)
+ for method in iface.methods:
+ self.add_method(method)
+ for sig in iface.signals:
+ self.add_signal(sig)
+ for prop in iface.properties:
+ self.add_property(prop)
+ self.indent = self.indent[:-3]
+
+
+def parse_generated_content(state: RSTState, content: StringList) -> List[Node]:
+ """Parse a generated content by Documenter."""
+ with switch_source_input(state, content):
+ node = nodes.paragraph()
+ node.document = state.document
+ state.nested_parse(content, 0, node)
+
+ return node.children
+
+
+class DBusDocDirective(SphinxDirective):
+ """Extract documentation from the specified D-Bus XML file"""
+
+ has_content = True
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = True
+
+ def run(self):
+ reporter = self.state.document.reporter
+
+ try:
+ source, lineno = reporter.get_source_and_line(self.lineno) # type: ignore
+ except AttributeError:
+ source, lineno = (None, None)
+
+ logger.debug("[dbusdoc] %s:%s: input:\n%s", source, lineno, self.block_text)
+
+ env = self.state.document.settings.env
+ dbusfile = env.config.qapidoc_srctree + "/" + self.arguments[0]
+ with open(dbusfile, "rb") as f:
+ xml_data = f.read()
+ xml = parse_dbus_xml(xml_data)
+ doc = DBusDoc(self, dbusfile)
+ for iface in xml:
+ doc.add_interface(iface)
+
+ result = parse_generated_content(self.state, doc.result)
+ return result
+
+
+def setup(app: Sphinx) -> Dict[str, Any]:
+ """Register dbus-doc directive with Sphinx"""
+ app.add_config_value("dbusdoc_srctree", None, "env")
+ app.add_directive("dbus-doc", DBusDocDirective)
+ dbusdomain.setup(app)
+
+ return dict(version=__version__, parallel_read_safe=True, parallel_write_safe=True)
diff --git a/docs/sphinx/dbusdomain.py b/docs/sphinx/dbusdomain.py
new file mode 100644
index 0000000000..2ea95af623
--- /dev/null
+++ b/docs/sphinx/dbusdomain.py
@@ -0,0 +1,406 @@
+# D-Bus sphinx domain extension
+#
+# Copyright (C) 2021, Red Hat Inc.
+#
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Author: Marc-André Lureau <marcandre.lureau@redhat.com>
+
+from typing import (
+ Any,
+ Dict,
+ Iterable,
+ Iterator,
+ List,
+ NamedTuple,
+ Optional,
+ Tuple,
+ cast,
+)
+
+from docutils import nodes
+from docutils.nodes import Element, Node
+from docutils.parsers.rst import directives
+from sphinx import addnodes
+from sphinx.addnodes import desc_signature, pending_xref
+from sphinx.directives import ObjectDescription
+from sphinx.domains import Domain, Index, IndexEntry, ObjType
+from sphinx.locale import _
+from sphinx.roles import XRefRole
+from sphinx.util import nodes as node_utils
+from sphinx.util.docfields import Field, TypedField
+from sphinx.util.typing import OptionSpec
+
+
+class DBusDescription(ObjectDescription[str]):
+ """Base class for DBus objects"""
+
+ option_spec: OptionSpec = ObjectDescription.option_spec.copy()
+ option_spec.update(
+ {
+ "deprecated": directives.flag,
+ }
+ )
+
+ def get_index_text(self, modname: str, name: str) -> str:
+ """Return the text for the index entry of the object."""
+ raise NotImplementedError("must be implemented in subclasses")
+
+ def add_target_and_index(
+ self, name: str, sig: str, signode: desc_signature
+ ) -> None:
+ ifacename = self.env.ref_context.get("dbus:interface")
+ node_id = name
+ if ifacename:
+ node_id = f"{ifacename}.{node_id}"
+
+ signode["names"].append(name)
+ signode["ids"].append(node_id)
+
+ if "noindexentry" not in self.options:
+ indextext = self.get_index_text(ifacename, name)
+ if indextext:
+ self.indexnode["entries"].append(
+ ("single", indextext, node_id, "", None)
+ )
+
+ domain = cast(DBusDomain, self.env.get_domain("dbus"))
+ domain.note_object(name, self.objtype, node_id, location=signode)
+
+
+class DBusInterface(DBusDescription):
+ """
+ Implementation of ``dbus:interface``.
+ """
+
+ def get_index_text(self, ifacename: str, name: str) -> str:
+ return ifacename
+
+ def before_content(self) -> None:
+ self.env.ref_context["dbus:interface"] = self.arguments[0]
+
+ def after_content(self) -> None:
+ self.env.ref_context.pop("dbus:interface")
+
+ def handle_signature(self, sig: str, signode: desc_signature) -> str:
+ signode += addnodes.desc_annotation("interface ", "interface ")
+ signode += addnodes.desc_name(sig, sig)
+ return sig
+
+ def run(self) -> List[Node]:
+ _, node = super().run()
+ name = self.arguments[0]
+ section = nodes.section(ids=[name + "-section"])
+ section += nodes.title(name, "%s interface" % name)
+ section += node
+ return [self.indexnode, section]
+
+
+class DBusMember(DBusDescription):
+
+ signal = False
+
+
+class DBusMethod(DBusMember):
+ """
+ Implementation of ``dbus:method``.
+ """
+
+ option_spec: OptionSpec = DBusMember.option_spec.copy()
+ option_spec.update(
+ {
+ "noreply": directives.flag,
+ }
+ )
+
+ doc_field_types: List[Field] = [
+ TypedField(
+ "arg",
+ label=_("Arguments"),
+ names=("arg",),
+ rolename="arg",
+ typerolename=None,
+ typenames=("argtype", "type"),
+ ),
+ TypedField(
+ "ret",
+ label=_("Returns"),
+ names=("ret",),
+ rolename="ret",
+ typerolename=None,
+ typenames=("rettype", "type"),
+ ),
+ ]
+
+ def get_index_text(self, ifacename: str, name: str) -> str:
+ return _("%s() (%s method)") % (name, ifacename)
+
+ def handle_signature(self, sig: str, signode: desc_signature) -> str:
+ params = addnodes.desc_parameterlist()
+ returns = addnodes.desc_parameterlist()
+
+ contentnode = addnodes.desc_content()
+ self.state.nested_parse(self.content, self.content_offset, contentnode)
+ for child in contentnode:
+ if isinstance(child, nodes.field_list):
+ for field in child:
+ ty, sg, name = field[0].astext().split(None, 2)
+ param = addnodes.desc_parameter()
+ param += addnodes.desc_sig_keyword_type(sg, sg)
+ param += addnodes.desc_sig_space()
+ param += addnodes.desc_sig_name(name, name)
+ if ty == "arg":
+ params += param
+ elif ty == "ret":
+ returns += param
+
+ anno = "signal " if self.signal else "method "
+ signode += addnodes.desc_annotation(anno, anno)
+ signode += addnodes.desc_name(sig, sig)
+ signode += params
+ if not self.signal and "noreply" not in self.options:
+ ret = addnodes.desc_returns()
+ ret += returns
+ signode += ret
+
+ return sig
+
+
+class DBusSignal(DBusMethod):
+ """
+ Implementation of ``dbus:signal``.
+ """
+
+ doc_field_types: List[Field] = [
+ TypedField(
+ "arg",
+ label=_("Arguments"),
+ names=("arg",),
+ rolename="arg",
+ typerolename=None,
+ typenames=("argtype", "type"),
+ ),
+ ]
+ signal = True
+
+ def get_index_text(self, ifacename: str, name: str) -> str:
+ return _("%s() (%s signal)") % (name, ifacename)
+
+
+class DBusProperty(DBusMember):
+ """
+ Implementation of ``dbus:property``.
+ """
+
+ option_spec: OptionSpec = DBusMember.option_spec.copy()
+ option_spec.update(
+ {
+ "type": directives.unchanged,
+ "readonly": directives.flag,
+ "writeonly": directives.flag,
+ "readwrite": directives.flag,
+ "emits-changed": directives.unchanged,
+ }
+ )
+
+ doc_field_types: List[Field] = []
+
+ def get_index_text(self, ifacename: str, name: str) -> str:
+ return _("%s (%s property)") % (name, ifacename)
+
+ def transform_content(self, contentnode: addnodes.desc_content) -> None:
+ fieldlist = nodes.field_list()
+ access = None
+ if "readonly" in self.options:
+ access = _("read-only")
+ if "writeonly" in self.options:
+ access = _("write-only")
+ if "readwrite" in self.options:
+ access = _("read & write")
+ if access:
+ content = nodes.Text(access)
+ fieldname = nodes.field_name("", _("Access"))
+ fieldbody = nodes.field_body("", nodes.paragraph("", "", content))
+ field = nodes.field("", fieldname, fieldbody)
+ fieldlist += field
+ emits = self.options.get("emits-changed", None)
+ if emits:
+ content = nodes.Text(emits)
+ fieldname = nodes.field_name("", _("Emits Changed"))
+ fieldbody = nodes.field_body("", nodes.paragraph("", "", content))
+ field = nodes.field("", fieldname, fieldbody)
+ fieldlist += field
+ if len(fieldlist) > 0:
+ contentnode.insert(0, fieldlist)
+
+ def handle_signature(self, sig: str, signode: desc_signature) -> str:
+ contentnode = addnodes.desc_content()
+ self.state.nested_parse(self.content, self.content_offset, contentnode)
+ ty = self.options.get("type")
+
+ signode += addnodes.desc_annotation("property ", "property ")
+ signode += addnodes.desc_name(sig, sig)
+ signode += addnodes.desc_sig_punctuation("", ":")
+ signode += addnodes.desc_sig_keyword_type(ty, ty)
+ return sig
+
+ def run(self) -> List[Node]:
+ self.name = "dbus:member"
+ return super().run()
+
+
+class DBusXRef(XRefRole):
+ def process_link(self, env, refnode, has_explicit_title, title, target):
+ refnode["dbus:interface"] = env.ref_context.get("dbus:interface")
+ if not has_explicit_title:
+ title = title.lstrip(".") # only has a meaning for the target
+ target = target.lstrip("~") # only has a meaning for the title
+ # if the first character is a tilde, don't display the module/class
+ # parts of the contents
+ if title[0:1] == "~":
+ title = title[1:]
+ dot = title.rfind(".")
+ if dot != -1:
+ title = title[dot + 1 :]
+ # if the first character is a dot, search more specific namespaces first
+ # else search builtins first
+ if target[0:1] == ".":
+ target = target[1:]
+ refnode["refspecific"] = True
+ return title, target
+
+
+class DBusIndex(Index):
+ """
+ Index subclass to provide a D-Bus interfaces index.
+ """
+
+ name = "dbusindex"
+ localname = _("D-Bus Interfaces Index")
+ shortname = _("dbus")
+
+ def generate(
+ self, docnames: Iterable[str] = None
+ ) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]:
+ content: Dict[str, List[IndexEntry]] = {}
+ # list of prefixes to ignore
+ ignores: List[str] = self.domain.env.config["dbus_index_common_prefix"]
+ ignores = sorted(ignores, key=len, reverse=True)
+
+ ifaces = sorted(
+ [
+ x
+ for x in self.domain.data["objects"].items()
+ if x[1].objtype == "interface"
+ ],
+ key=lambda x: x[0].lower(),
+ )
+ for name, (docname, node_id, _) in ifaces:
+ if docnames and docname not in docnames:
+ continue
+
+ for ignore in ignores:
+ if name.startswith(ignore):
+ name = name[len(ignore) :]
+ stripped = ignore
+ break
+ else:
+ stripped = ""
+
+ entries = content.setdefault(name[0].lower(), [])
+ entries.append(IndexEntry(stripped + name, 0, docname, node_id, "", "", ""))
+
+ # sort by first letter
+ sorted_content = sorted(content.items())
+
+ return sorted_content, False
+
+
+class ObjectEntry(NamedTuple):
+ docname: str
+ node_id: str
+ objtype: str
+
+
+class DBusDomain(Domain):
+ """
+ Implementation of the D-Bus domain.
+ """
+
+ name = "dbus"
+ label = "D-Bus"
+ object_types: Dict[str, ObjType] = {
+ "interface": ObjType(_("interface"), "iface", "obj"),
+ "method": ObjType(_("method"), "meth", "obj"),
+ "signal": ObjType(_("signal"), "sig", "obj"),
+ "property": ObjType(_("property"), "attr", "_prop", "obj"),
+ }
+ directives = {
+ "interface": DBusInterface,
+ "method": DBusMethod,
+ "signal": DBusSignal,
+ "property": DBusProperty,
+ }
+ roles = {
+ "iface": DBusXRef(),
+ "meth": DBusXRef(),
+ "sig": DBusXRef(),
+ "prop": DBusXRef(),
+ }
+ initial_data: Dict[str, Dict[str, Tuple[Any]]] = {
+ "objects": {}, # fullname -> ObjectEntry
+ }
+ indices = [
+ DBusIndex,
+ ]
+
+ @property
+ def objects(self) -> Dict[str, ObjectEntry]:
+ return self.data.setdefault("objects", {}) # fullname -> ObjectEntry
+
+ def note_object(
+ self, name: str, objtype: str, node_id: str, location: Any = None
+ ) -> None:
+ self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype)
+
+ def clear_doc(self, docname: str) -> None:
+ for fullname, obj in list(self.objects.items()):
+ if obj.docname == docname:
+ del self.objects[fullname]
+
+ def find_obj(self, typ: str, name: str) -> Optional[Tuple[str, ObjectEntry]]:
+ # skip parens
+ if name[-2:] == "()":
+ name = name[:-2]
+ if typ in ("meth", "sig", "prop"):
+ try:
+ ifacename, name = name.rsplit(".", 1)
+ except ValueError:
+ pass
+ return self.objects.get(name)
+
+ def resolve_xref(
+ self,
+ env: "BuildEnvironment",
+ fromdocname: str,
+ builder: "Builder",
+ typ: str,
+ target: str,
+ node: pending_xref,
+ contnode: Element,
+ ) -> Optional[Element]:
+ """Resolve the pending_xref *node* with the given *typ* and *target*."""
+ objdef = self.find_obj(typ, target)
+ if objdef:
+ return node_utils.make_refnode(
+ builder, fromdocname, objdef.docname, objdef.node_id, contnode
+ )
+
+ def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]:
+ for refname, obj in self.objects.items():
+ yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1)
+
+
+def setup(app):
+ app.add_domain(DBusDomain)
+ app.add_config_value("dbus_index_common_prefix", [], "env")
diff --git a/docs/sphinx/dbusparser.py b/docs/sphinx/dbusparser.py
new file mode 100644
index 0000000000..024553eae7
--- /dev/null
+++ b/docs/sphinx/dbusparser.py
@@ -0,0 +1,373 @@
+# Based from "GDBus - GLib D-Bus Library":
+#
+# Copyright (C) 2008-2011 Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General
+# Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+#
+# Author: David Zeuthen <davidz@redhat.com>
+
+import xml.parsers.expat
+
+
+class Annotation:
+ def __init__(self, key, value):
+ self.key = key
+ self.value = value
+ self.annotations = []
+ self.since = ""
+
+
+class Arg:
+ def __init__(self, name, signature):
+ self.name = name
+ self.signature = signature
+ self.annotations = []
+ self.doc_string = ""
+ self.since = ""
+
+
+class Method:
+ def __init__(self, name, h_type_implies_unix_fd=True):
+ self.name = name
+ self.h_type_implies_unix_fd = h_type_implies_unix_fd
+ self.in_args = []
+ self.out_args = []
+ self.annotations = []
+ self.doc_string = ""
+ self.since = ""
+ self.deprecated = False
+ self.unix_fd = False
+
+
+class Signal:
+ def __init__(self, name):
+ self.name = name
+ self.args = []
+ self.annotations = []
+ self.doc_string = ""
+ self.since = ""
+ self.deprecated = False
+
+
+class Property:
+ def __init__(self, name, signature, access):
+ self.name = name
+ self.signature = signature
+ self.access = access
+ self.annotations = []
+ self.arg = Arg("value", self.signature)
+ self.arg.annotations = self.annotations
+ self.readable = False
+ self.writable = False
+ if self.access == "readwrite":
+ self.readable = True
+ self.writable = True
+ elif self.access == "read":
+ self.readable = True
+ elif self.access == "write":
+ self.writable = True
+ else:
+ raise ValueError('Invalid access type "{}"'.format(self.access))
+ self.doc_string = ""
+ self.since = ""
+ self.deprecated = False
+ self.emits_changed_signal = True
+
+
+class Interface:
+ def __init__(self, name):
+ self.name = name
+ self.methods = []
+ self.signals = []
+ self.properties = []
+ self.annotations = []
+ self.doc_string = ""
+ self.doc_string_brief = ""
+ self.since = ""
+ self.deprecated = False
+
+
+class DBusXMLParser:
+ STATE_TOP = "top"
+ STATE_NODE = "node"
+ STATE_INTERFACE = "interface"
+ STATE_METHOD = "method"
+ STATE_SIGNAL = "signal"
+ STATE_PROPERTY = "property"
+ STATE_ARG = "arg"
+ STATE_ANNOTATION = "annotation"
+ STATE_IGNORED = "ignored"
+
+ def __init__(self, xml_data, h_type_implies_unix_fd=True):
+ self._parser = xml.parsers.expat.ParserCreate()
+ self._parser.CommentHandler = self.handle_comment
+ self._parser.CharacterDataHandler = self.handle_char_data
+ self._parser.StartElementHandler = self.handle_start_element
+ self._parser.EndElementHandler = self.handle_end_element
+
+ self.parsed_interfaces = []
+ self._cur_object = None
+
+ self.state = DBusXMLParser.STATE_TOP
+ self.state_stack = []
+ self._cur_object = None
+ self._cur_object_stack = []
+
+ self.doc_comment_last_symbol = ""
+
+ self._h_type_implies_unix_fd = h_type_implies_unix_fd
+
+ self._parser.Parse(xml_data)
+
+ COMMENT_STATE_BEGIN = "begin"
+ COMMENT_STATE_PARAMS = "params"
+ COMMENT_STATE_BODY = "body"
+ COMMENT_STATE_SKIP = "skip"
+
+ def handle_comment(self, data):
+ comment_state = DBusXMLParser.COMMENT_STATE_BEGIN
+ lines = data.split("\n")
+ symbol = ""
+ body = ""
+ in_para = False
+ params = {}
+ for line in lines:
+ orig_line = line
+ line = line.lstrip()
+ if comment_state == DBusXMLParser.COMMENT_STATE_BEGIN:
+ if len(line) > 0:
+ colon_index = line.find(": ")
+ if colon_index == -1:
+ if line.endswith(":"):
+ symbol = line[0 : len(line) - 1]
+ comment_state = DBusXMLParser.COMMENT_STATE_PARAMS
+ else:
+ comment_state = DBusXMLParser.COMMENT_STATE_SKIP
+ else:
+ symbol = line[0:colon_index]
+ rest_of_line = line[colon_index + 2 :].strip()
+ if len(rest_of_line) > 0:
+ body += rest_of_line + "\n"
+ comment_state = DBusXMLParser.COMMENT_STATE_PARAMS
+ elif comment_state == DBusXMLParser.COMMENT_STATE_PARAMS:
+ if line.startswith("@"):
+ colon_index = line.find(": ")
+ if colon_index == -1:
+ comment_state = DBusXMLParser.COMMENT_STATE_BODY
+ if not in_para:
+ in_para = True
+ body += orig_line + "\n"
+ else:
+ param = line[1:colon_index]
+ docs = line[colon_index + 2 :]
+ params[param] = docs
+ else:
+ comment_state = DBusXMLParser.COMMENT_STATE_BODY
+ if len(line) > 0:
+ if not in_para:
+ in_para = True
+ body += orig_line + "\n"
+ elif comment_state == DBusXMLParser.COMMENT_STATE_BODY:
+ if len(line) > 0:
+ if not in_para:
+ in_para = True
+ body += orig_line + "\n"
+ else:
+ if in_para:
+ body += "\n"
+ in_para = False
+ if in_para:
+ body += "\n"
+
+ if symbol != "":
+ self.doc_comment_last_symbol = symbol
+ self.doc_comment_params = params
+ self.doc_comment_body = body
+
+ def handle_char_data(self, data):
+ # print 'char_data=%s'%data
+ pass
+
+ def handle_start_element(self, name, attrs):
+ old_state = self.state
+ old_cur_object = self._cur_object
+ if self.state == DBusXMLParser.STATE_IGNORED:
+ self.state = DBusXMLParser.STATE_IGNORED
+ elif self.state == DBusXMLParser.STATE_TOP:
+ if name == DBusXMLParser.STATE_NODE:
+ self.state = DBusXMLParser.STATE_NODE
+ else:
+ self.state = DBusXMLParser.STATE_IGNORED
+ elif self.state == DBusXMLParser.STATE_NODE:
+ if name == DBusXMLParser.STATE_INTERFACE:
+ self.state = DBusXMLParser.STATE_INTERFACE
+ iface = Interface(attrs["name"])
+ self._cur_object = iface
+ self.parsed_interfaces.append(iface)
+ elif name == DBusXMLParser.STATE_ANNOTATION:
+ self.state = DBusXMLParser.STATE_ANNOTATION
+ anno = Annotation(attrs["name"], attrs["value"])
+ self._cur_object.annotations.append(anno)
+ self._cur_object = anno
+ else:
+ self.state = DBusXMLParser.STATE_IGNORED
+
+ # assign docs, if any
+ if "name" in attrs and self.doc_comment_last_symbol == attrs["name"]:
+ self._cur_object.doc_string = self.doc_comment_body
+ if "short_description" in self.doc_comment_params:
+ short_description = self.doc_comment_params["short_description"]
+ self._cur_object.doc_string_brief = short_description
+ if "since" in self.doc_comment_params:
+ self._cur_object.since = self.doc_comment_params["since"].strip()
+
+ elif self.state == DBusXMLParser.STATE_INTERFACE:
+ if name == DBusXMLParser.STATE_METHOD:
+ self.state = DBusXMLParser.STATE_METHOD
+ method = Method(
+ attrs["name"], h_type_implies_unix_fd=self._h_type_implies_unix_fd
+ )
+ self._cur_object.methods.append(method)
+ self._cur_object = method
+ elif name == DBusXMLParser.STATE_SIGNAL:
+ self.state = DBusXMLParser.STATE_SIGNAL
+ signal = Signal(attrs["name"])
+ self._cur_object.signals.append(signal)
+ self._cur_object = signal
+ elif name == DBusXMLParser.STATE_PROPERTY:
+ self.state = DBusXMLParser.STATE_PROPERTY
+ prop = Property(attrs["name"], attrs["type"], attrs["access"])
+ self._cur_object.properties.append(prop)
+ self._cur_object = prop
+ elif name == DBusXMLParser.STATE_ANNOTATION:
+ self.state = DBusXMLParser.STATE_ANNOTATION
+ anno = Annotation(attrs["name"], attrs["value"])
+ self._cur_object.annotations.append(anno)
+ self._cur_object = anno
+ else:
+ self.state = DBusXMLParser.STATE_IGNORED
+
+ # assign docs, if any
+ if "name" in attrs and self.doc_comment_last_symbol == attrs["name"]:
+ self._cur_object.doc_string = self.doc_comment_body
+ if "since" in self.doc_comment_params:
+ self._cur_object.since = self.doc_comment_params["since"].strip()
+
+ elif self.state == DBusXMLParser.STATE_METHOD:
+ if name == DBusXMLParser.STATE_ARG:
+ self.state = DBusXMLParser.STATE_ARG
+ arg_name = None
+ if "name" in attrs:
+ arg_name = attrs["name"]
+ arg = Arg(arg_name, attrs["type"])
+ direction = attrs.get("direction", "in")
+ if direction == "in":
+ self._cur_object.in_args.append(arg)
+ elif direction == "out":
+ self._cur_object.out_args.append(arg)
+ else:
+ raise ValueError('Invalid direction "{}"'.format(direction))
+ self._cur_object = arg
+ elif name == DBusXMLParser.STATE_ANNOTATION:
+ self.state = DBusXMLParser.STATE_ANNOTATION
+ anno = Annotation(attrs["name"], attrs["value"])
+ self._cur_object.annotations.append(anno)
+ self._cur_object = anno
+ else:
+ self.state = DBusXMLParser.STATE_IGNORED
+
+ # assign docs, if any
+ if self.doc_comment_last_symbol == old_cur_object.name:
+ if "name" in attrs and attrs["name"] in self.doc_comment_params:
+ doc_string = self.doc_comment_params[attrs["name"]]
+ if doc_string is not None:
+ self._cur_object.doc_string = doc_string
+ if "since" in self.doc_comment_params:
+ self._cur_object.since = self.doc_comment_params[
+ "since"
+ ].strip()
+
+ elif self.state == DBusXMLParser.STATE_SIGNAL:
+ if name == DBusXMLParser.STATE_ARG:
+ self.state = DBusXMLParser.STATE_ARG
+ arg_name = None
+ if "name" in attrs:
+ arg_name = attrs["name"]
+ arg = Arg(arg_name, attrs["type"])
+ self._cur_object.args.append(arg)
+ self._cur_object = arg
+ elif name == DBusXMLParser.STATE_ANNOTATION:
+ self.state = DBusXMLParser.STATE_ANNOTATION
+ anno = Annotation(attrs["name"], attrs["value"])
+ self._cur_object.annotations.append(anno)
+ self._cur_object = anno
+ else:
+ self.state = DBusXMLParser.STATE_IGNORED
+
+ # assign docs, if any
+ if self.doc_comment_last_symbol == old_cur_object.name:
+ if "name" in attrs and attrs["name"] in self.doc_comment_params:
+ doc_string = self.doc_comment_params[attrs["name"]]
+ if doc_string is not None:
+ self._cur_object.doc_string = doc_string
+ if "since" in self.doc_comment_params:
+ self._cur_object.since = self.doc_comment_params[
+ "since"
+ ].strip()
+
+ elif self.state == DBusXMLParser.STATE_PROPERTY:
+ if name == DBusXMLParser.STATE_ANNOTATION:
+ self.state = DBusXMLParser.STATE_ANNOTATION
+ anno = Annotation(attrs["name"], attrs["value"])
+ self._cur_object.annotations.append(anno)
+ self._cur_object = anno
+ else:
+ self.state = DBusXMLParser.STATE_IGNORED
+
+ elif self.state == DBusXMLParser.STATE_ARG:
+ if name == DBusXMLParser.STATE_ANNOTATION:
+ self.state = DBusXMLParser.STATE_ANNOTATION
+ anno = Annotation(attrs["name"], attrs["value"])
+ self._cur_object.annotations.append(anno)
+ self._cur_object = anno
+ else:
+ self.state = DBusXMLParser.STATE_IGNORED
+
+ elif self.state == DBusXMLParser.STATE_ANNOTATION:
+ if name == DBusXMLParser.STATE_ANNOTATION:
+ self.state = DBusXMLParser.STATE_ANNOTATION
+ anno = Annotation(attrs["name"], attrs["value"])
+ self._cur_object.annotations.append(anno)
+ self._cur_object = anno
+ else:
+ self.state = DBusXMLParser.STATE_IGNORED
+
+ else:
+ raise ValueError(
+ 'Unhandled state "{}" while entering element with name "{}"'.format(
+ self.state, name
+ )
+ )
+
+ self.state_stack.append(old_state)
+ self._cur_object_stack.append(old_cur_object)
+
+ def handle_end_element(self, name):
+ self.state = self.state_stack.pop()
+ self._cur_object = self._cur_object_stack.pop()
+
+
+def parse_dbus_xml(xml_data):
+ parser = DBusXMLParser(xml_data, True)
+ return parser.parsed_interfaces
diff --git a/docs/sphinx/fakedbusdoc.py b/docs/sphinx/fakedbusdoc.py
new file mode 100644
index 0000000000..a680b25754
--- /dev/null
+++ b/docs/sphinx/fakedbusdoc.py
@@ -0,0 +1,25 @@
+# D-Bus XML documentation extension, compatibility gunk for <sphinx4
+#
+# Copyright (C) 2021, Red Hat Inc.
+#
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Author: Marc-André Lureau <marcandre.lureau@redhat.com>
+"""dbus-doc is a Sphinx extension that provides documentation from D-Bus XML."""
+
+from sphinx.application import Sphinx
+from sphinx.util.docutils import SphinxDirective
+from typing import Any, Dict
+
+
+class FakeDBusDocDirective(SphinxDirective):
+ has_content = True
+ required_arguments = 1
+
+ def run(self):
+ return []
+
+
+def setup(app: Sphinx) -> Dict[str, Any]:
+ """Register a fake dbus-doc directive with Sphinx"""
+ app.add_directive("dbus-doc", FakeDBusDocDirective)
diff --git a/hw/display/qxl.c b/hw/display/qxl.c
index 29c80b4289..e2d6e317da 100644
--- a/hw/display/qxl.c
+++ b/hw/display/qxl.c
@@ -2171,12 +2171,17 @@ static void qxl_realize_common(PCIQXLDevice *qxl, Error **errp)
}
#if SPICE_SERVER_VERSION >= 0x000e02 /* release 0.14.2 */
+ Error *err = NULL;
char device_address[256] = "";
- if (qemu_spice_fill_device_address(qxl->vga.con, device_address, 256)) {
+ if (qemu_console_fill_device_address(qxl->vga.con,
+ device_address, sizeof(device_address),
+ &err)) {
spice_qxl_set_device_info(&qxl->ssd.qxl,
device_address,
0,
qxl->max_outputs);
+ } else {
+ error_report_err(err);
}
#endif
diff --git a/hw/display/vhost-user-gpu.c b/hw/display/vhost-user-gpu.c
index 49df56cd14..09818231bd 100644
--- a/hw/display/vhost-user-gpu.c
+++ b/hw/display/vhost-user-gpu.c
@@ -254,8 +254,8 @@ vhost_user_gpu_handle_display(VhostUserGPU *g, VhostUserGpuMsg *msg)
vhost_user_gpu_unblock(g);
break;
}
- dpy_gl_update(con, m->x, m->y, m->width, m->height);
g->backend_blocked = true;
+ dpy_gl_update(con, m->x, m->y, m->width, m->height);
break;
}
case VHOST_USER_GPU_UPDATE: {
diff --git a/hw/display/virtio-gpu-base.c b/hw/display/virtio-gpu-base.c
index c8da4806e0..fff0fb4a82 100644
--- a/hw/display/virtio-gpu-base.c
+++ b/hw/display/virtio-gpu-base.c
@@ -117,6 +117,10 @@ virtio_gpu_gl_block(void *opaque, bool block)
g->renderer_blocked--;
}
assert(g->renderer_blocked >= 0);
+
+ if (!block && g->renderer_blocked == 0) {
+ virtio_gpu_gl_flushed(g);
+ }
}
static int
@@ -143,7 +147,6 @@ static const GraphicHwOps virtio_gpu_ops = {
.text_update = virtio_gpu_text_update,
.ui_info = virtio_gpu_ui_info,
.gl_block = virtio_gpu_gl_block,
- .gl_flushed = virtio_gpu_gl_flushed,
};
bool
diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c
index 18d054922f..73cb92c8d5 100644
--- a/hw/display/virtio-gpu-virgl.c
+++ b/hw/display/virtio-gpu-virgl.c
@@ -175,7 +175,7 @@ static void virgl_cmd_set_scanout(VirtIOGPU *g,
virgl_renderer_force_ctx_0();
dpy_gl_scanout_texture(
g->parent_obj.scanout[ss.scanout_id].con, info.tex_id,
- info.flags & 1 /* FIXME: Y_0_TOP */,
+ info.flags & VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP,
info.width, info.height,
ss.r.x, ss.r.y, ss.r.width, ss.r.height);
} else {
@@ -609,6 +609,7 @@ int virtio_gpu_virgl_init(VirtIOGPU *g)
ret = virgl_renderer_init(g, 0, &virtio_gpu_3d_cbs);
if (ret != 0) {
+ error_report("virgl could not be initialized: %d", ret);
return ret;
}
diff --git a/hw/display/virtio-vga.c b/hw/display/virtio-vga.c
index 9e57f61e9e..b23a75a04b 100644
--- a/hw/display/virtio-vga.c
+++ b/hw/display/virtio-vga.c
@@ -68,16 +68,6 @@ static void virtio_vga_base_gl_block(void *opaque, bool block)
}
}
-static void virtio_vga_base_gl_flushed(void *opaque)
-{
- VirtIOVGABase *vvga = opaque;
- VirtIOGPUBase *g = vvga->vgpu;
-
- if (g->hw_ops->gl_flushed) {
- g->hw_ops->gl_flushed(g);
- }
-}
-
static int virtio_vga_base_get_flags(void *opaque)
{
VirtIOVGABase *vvga = opaque;
@@ -93,7 +83,6 @@ static const GraphicHwOps virtio_vga_base_ops = {
.text_update = virtio_vga_base_text_update,
.ui_info = virtio_vga_base_ui_info,
.gl_block = virtio_vga_base_gl_block,
- .gl_flushed = virtio_vga_base_gl_flushed,
};
static const VMStateDescription vmstate_virtio_vga_base = {
diff --git a/include/chardev/char-socket.h b/include/chardev/char-socket.h
new file mode 100644
index 0000000000..6b6e2ceba1
--- /dev/null
+++ b/include/chardev/char-socket.h
@@ -0,0 +1,86 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * 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 OTHER
+ * 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.
+ */
+#ifndef CHAR_SOCKET_H_
+#define CHAR_SOCKET_H_
+
+#include "io/channel-socket.h"
+#include "io/channel-tls.h"
+#include "io/net-listener.h"
+#include "chardev/char.h"
+#include "qom/object.h"
+
+#define TCP_MAX_FDS 16
+
+typedef struct {
+ char buf[21];
+ size_t buflen;
+} TCPChardevTelnetInit;
+
+typedef enum {
+ TCP_CHARDEV_STATE_DISCONNECTED,
+ TCP_CHARDEV_STATE_CONNECTING,
+ TCP_CHARDEV_STATE_CONNECTED,
+} TCPChardevState;
+
+typedef ChardevClass SocketChardevClass;
+
+struct SocketChardev {
+ Chardev parent;
+ QIOChannel *ioc; /* Client I/O channel */
+ QIOChannelSocket *sioc; /* Client master channel */
+ QIONetListener *listener;
+ GSource *hup_source;
+ QCryptoTLSCreds *tls_creds;
+ char *tls_authz;
+ TCPChardevState state;
+ int max_size;
+ int do_telnetopt;
+ int do_nodelay;
+ int *read_msgfds;
+ size_t read_msgfds_num;
+ int *write_msgfds;
+ size_t write_msgfds_num;
+ bool registered_yank;
+
+ SocketAddress *addr;
+ bool is_listen;
+ bool is_telnet;
+ bool is_tn3270;
+ GSource *telnet_source;
+ TCPChardevTelnetInit *telnet_init;
+
+ bool is_websock;
+
+ GSource *reconnect_timer;
+ int64_t reconnect_time;
+ bool connect_err_reported;
+
+ QIOTask *connect_task;
+};
+typedef struct SocketChardev SocketChardev;
+
+DECLARE_INSTANCE_CHECKER(SocketChardev, SOCKET_CHARDEV,
+ TYPE_CHARDEV_SOCKET)
+
+#endif /* CHAR_SOCKET_H_ */
diff --git a/include/qemu/cutils.h b/include/qemu/cutils.h
index 986ed8e15f..320543950c 100644
--- a/include/qemu/cutils.h
+++ b/include/qemu/cutils.h
@@ -209,4 +209,9 @@ int qemu_pstrcmp0(const char **str1, const char **str2);
*/
char *get_relocated_path(const char *dir);
+static inline const char *yes_no(bool b)
+{
+ return b ? "yes" : "no";
+}
+
#endif
diff --git a/include/qemu/dbus.h b/include/qemu/dbus.h
index 9d591f9ee4..08f00dfd53 100644
--- a/include/qemu/dbus.h
+++ b/include/qemu/dbus.h
@@ -12,6 +12,30 @@
#include <gio/gio.h>
+#include "qom/object.h"
+#include "chardev/char.h"
+#include "qemu/notify.h"
+#include "qemu/typedefs.h"
+
+/* glib/gio 2.68 */
+#define DBUS_METHOD_INVOCATION_HANDLED TRUE
+#define DBUS_METHOD_INVOCATION_UNHANDLED FALSE
+
+/* in msec */
+#define DBUS_DEFAULT_TIMEOUT 1000
+
+#define DBUS_DISPLAY1_ROOT "/org/qemu/Display1"
+
+#define DBUS_DISPLAY_ERROR (dbus_display_error_quark())
+GQuark dbus_display_error_quark(void);
+
+typedef enum {
+ DBUS_DISPLAY_ERROR_FAILED,
+ DBUS_DISPLAY_ERROR_INVALID,
+ DBUS_DISPLAY_ERROR_UNSUPPORTED,
+ DBUS_DISPLAY_N_ERRORS,
+} DBusDisplayError;
+
GStrv qemu_dbus_get_queued_owners(GDBusConnection *connection,
const char *name,
Error **errp);
diff --git a/include/qemu/option.h b/include/qemu/option.h
index 306bf07575..bbd86e1c4e 100644
--- a/include/qemu/option.h
+++ b/include/qemu/option.h
@@ -150,4 +150,6 @@ QDict *keyval_parse(const char *params, const char *implied_key,
bool *help, Error **errp);
void keyval_merge(QDict *old, const QDict *new, Error **errp);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(QemuOpts, qemu_opts_del)
+
#endif
diff --git a/include/ui/clipboard.h b/include/ui/clipboard.h
index 6298986b15..ce76aa451f 100644
--- a/include/ui/clipboard.h
+++ b/include/ui/clipboard.h
@@ -20,8 +20,10 @@
*/
typedef enum QemuClipboardType QemuClipboardType;
+typedef enum QemuClipboardNotifyType QemuClipboardNotifyType;
typedef enum QemuClipboardSelection QemuClipboardSelection;
typedef struct QemuClipboardPeer QemuClipboardPeer;
+typedef struct QemuClipboardNotify QemuClipboardNotify;
typedef struct QemuClipboardInfo QemuClipboardInfo;
/**
@@ -55,25 +57,55 @@ enum QemuClipboardSelection {
* struct QemuClipboardPeer
*
* @name: peer name.
- * @update: notifier for clipboard updates.
+ * @notifier: notifier for clipboard updates.
* @request: callback for clipboard data requests.
*
* Clipboard peer description.
*/
struct QemuClipboardPeer {
const char *name;
- Notifier update;
+ Notifier notifier;
void (*request)(QemuClipboardInfo *info,
QemuClipboardType type);
};
/**
+ * enum QemuClipboardNotifyType
+ *
+ * @QEMU_CLIPBOARD_UPDATE_INFO: clipboard info update
+ * @QEMU_CLIPBOARD_RESET_SERIAL: reset clipboard serial
+ *
+ * Clipboard notify type.
+ */
+enum QemuClipboardNotifyType {
+ QEMU_CLIPBOARD_UPDATE_INFO,
+ QEMU_CLIPBOARD_RESET_SERIAL,
+};
+
+/**
+ * struct QemuClipboardNotify
+ *
+ * @type: the type of event.
+ * @info: a QemuClipboardInfo event.
+ *
+ * Clipboard notify data.
+ */
+struct QemuClipboardNotify {
+ QemuClipboardNotifyType type;
+ union {
+ QemuClipboardInfo *info;
+ };
+};
+
+/**
* struct QemuClipboardInfo
*
* @refcount: reference counter.
* @owner: clipboard owner.
* @selection: clipboard selection.
* @types: clipboard data array (one entry per type).
+ * @has_serial: whether @serial is available.
+ * @serial: the grab serial counter.
*
* Clipboard content data and metadata.
*/
@@ -81,6 +113,8 @@ struct QemuClipboardInfo {
uint32_t refcount;
QemuClipboardPeer *owner;
QemuClipboardSelection selection;
+ bool has_serial;
+ uint32_t serial;
struct {
bool available;
bool requested;
@@ -141,6 +175,16 @@ void qemu_clipboard_peer_release(QemuClipboardPeer *peer,
QemuClipboardInfo *qemu_clipboard_info(QemuClipboardSelection selection);
/**
+ * qemu_clipboard_check_serial
+ *
+ * @info: clipboard info.
+ * @client: whether to check from the client context and priority.
+ *
+ * Return TRUE if the @info has a higher serial than the current clipboard.
+ */
+bool qemu_clipboard_check_serial(QemuClipboardInfo *info, bool client);
+
+/**
* qemu_clipboard_info_new
*
* @owner: clipboard owner.
@@ -189,6 +233,13 @@ void qemu_clipboard_info_unref(QemuClipboardInfo *info);
void qemu_clipboard_update(QemuClipboardInfo *info);
/**
+ * qemu_clipboard_reset_serial
+ *
+ * Reset the clipboard serial.
+ */
+void qemu_clipboard_reset_serial(void);
+
+/**
* qemu_clipboard_request
*
* @info: clipboard info.
diff --git a/include/ui/console.h b/include/ui/console.h
index 6d678924f6..f590819880 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
@@ -108,6 +108,17 @@ struct QemuConsoleClass {
#define QEMU_ALLOCATED_FLAG 0x01
#define QEMU_PLACEHOLDER_FLAG 0x02
+typedef struct ScanoutTexture {
+ uint32_t backing_id;
+ bool backing_y_0_top;
+ uint32_t backing_width;
+ uint32_t backing_height;
+ uint32_t x;
+ uint32_t y;
+ uint32_t width;
+ uint32_t height;
+} ScanoutTexture;
+
typedef struct DisplaySurface {
pixman_format_code_t format;
pixman_image_t *image;
@@ -178,7 +189,24 @@ typedef struct QemuDmaBuf {
bool draw_submitted;
} QemuDmaBuf;
+enum display_scanout {
+ SCANOUT_NONE,
+ SCANOUT_SURFACE,
+ SCANOUT_TEXTURE,
+ SCANOUT_DMABUF,
+};
+
+typedef struct DisplayScanout {
+ enum display_scanout kind;
+ union {
+ /* DisplaySurface *surface; is kept in QemuConsole */
+ ScanoutTexture texture;
+ QemuDmaBuf *dmabuf;
+ };
+} DisplayScanout;
+
typedef struct DisplayState DisplayState;
+typedef struct DisplayGLCtx DisplayGLCtx;
typedef struct DisplayChangeListenerOps {
const char *dpy_name;
@@ -214,16 +242,6 @@ typedef struct DisplayChangeListenerOps {
QEMUCursor *cursor);
/* required if GL */
- QEMUGLContext (*dpy_gl_ctx_create)(DisplayChangeListener *dcl,
- QEMUGLParams *params);
- /* required if GL */
- void (*dpy_gl_ctx_destroy)(DisplayChangeListener *dcl,
- QEMUGLContext ctx);
- /* required if GL */
- int (*dpy_gl_ctx_make_current)(DisplayChangeListener *dcl,
- QEMUGLContext ctx);
-
- /* required if GL */
void (*dpy_gl_scanout_disable)(DisplayChangeListener *dcl);
/* required if GL */
void (*dpy_gl_scanout_texture)(DisplayChangeListener *dcl,
@@ -263,6 +281,26 @@ struct DisplayChangeListener {
QLIST_ENTRY(DisplayChangeListener) next;
};
+typedef struct DisplayGLCtxOps {
+ /*
+ * We only check if the GLCtx is compatible with a DCL via ops. A natural
+ * evolution of this would be a callback to check some runtime requirements
+ * and allow various DCL kinds.
+ */
+ const DisplayChangeListenerOps *compatible_dcl;
+
+ QEMUGLContext (*dpy_gl_ctx_create)(DisplayGLCtx *dgc,
+ QEMUGLParams *params);
+ void (*dpy_gl_ctx_destroy)(DisplayGLCtx *dgc,
+ QEMUGLContext ctx);
+ int (*dpy_gl_ctx_make_current)(DisplayGLCtx *dgc,
+ QEMUGLContext ctx);
+} DisplayGLCtxOps;
+
+struct DisplayGLCtx {
+ const DisplayGLCtxOps *ops;
+};
+
DisplayState *init_displaystate(void);
DisplaySurface *qemu_create_displaysurface_from(int width, int height,
pixman_format_code_t format,
@@ -292,7 +330,7 @@ void unregister_displaychangelistener(DisplayChangeListener *dcl);
bool dpy_ui_info_supported(QemuConsole *con);
const QemuUIInfo *dpy_get_ui_info(const QemuConsole *con);
-int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info);
+int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info, bool delay);
void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h);
void dpy_gfx_update_full(QemuConsole *con);
@@ -391,7 +429,6 @@ typedef struct GraphicHwOps {
void (*update_interval)(void *opaque, uint64_t interval);
int (*ui_info)(void *opaque, uint32_t head, QemuUIInfo *info);
void (*gl_block)(void *opaque, bool block);
- void (*gl_flushed)(void *opaque);
} GraphicHwOps;
QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head,
@@ -407,10 +444,11 @@ void graphic_hw_update_done(QemuConsole *con);
void graphic_hw_invalidate(QemuConsole *con);
void graphic_hw_text_update(QemuConsole *con, console_ch_t *chardata);
void graphic_hw_gl_block(QemuConsole *con, bool block);
-void graphic_hw_gl_flushed(QemuConsole *con);
void qemu_console_early_init(void);
+void qemu_console_set_display_gl_ctx(QemuConsole *con, DisplayGLCtx *ctx);
+
QemuConsole *qemu_console_lookup_by_index(unsigned int index);
QemuConsole *qemu_console_lookup_by_device(DeviceState *dev, uint32_t head);
QemuConsole *qemu_console_lookup_by_device_name(const char *device_id,
@@ -484,4 +522,10 @@ int index_from_key(const char *key, size_t key_length);
int udmabuf_fd(void);
#endif
+/* util.c */
+bool qemu_console_fill_device_address(QemuConsole *con,
+ char *device_address,
+ size_t size,
+ Error **errp);
+
#endif
diff --git a/include/ui/dbus-display.h b/include/ui/dbus-display.h
new file mode 100644
index 0000000000..88f153c237
--- /dev/null
+++ b/include/ui/dbus-display.h
@@ -0,0 +1,17 @@
+#ifndef DBUS_DISPLAY_H_
+#define DBUS_DISPLAY_H_
+
+#include "qapi/error.h"
+#include "ui/dbus-module.h"
+
+static inline bool qemu_using_dbus_display(Error **errp)
+{
+ if (!using_dbus_display) {
+ error_set(errp, ERROR_CLASS_DEVICE_NOT_ACTIVE,
+ "D-Bus display is not in use");
+ return false;
+ }
+ return true;
+}
+
+#endif /* DBUS_DISPLAY_H_ */
diff --git a/include/ui/dbus-module.h b/include/ui/dbus-module.h
new file mode 100644
index 0000000000..ace4a17a5c
--- /dev/null
+++ b/include/ui/dbus-module.h
@@ -0,0 +1,11 @@
+#ifndef DBUS_MODULE_H_
+#define DBUS_MODULE_H_
+
+struct QemuDBusDisplayOps {
+ bool (*add_client)(int csock, Error **errp);
+};
+
+extern int using_dbus_display;
+extern struct QemuDBusDisplayOps qemu_dbus_display;
+
+#endif /* DBUS_MODULE_H_*/
diff --git a/include/ui/egl-context.h b/include/ui/egl-context.h
index 9374fe41e3..c2761d747a 100644
--- a/include/ui/egl-context.h
+++ b/include/ui/egl-context.h
@@ -4,10 +4,10 @@
#include "ui/console.h"
#include "ui/egl-helpers.h"
-QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl,
+QEMUGLContext qemu_egl_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params);
-void qemu_egl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx);
-int qemu_egl_make_context_current(DisplayChangeListener *dcl,
+void qemu_egl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx);
+int qemu_egl_make_context_current(DisplayGLCtx *dgc,
QEMUGLContext ctx);
#endif /* EGL_CONTEXT_H */
diff --git a/include/ui/gtk.h b/include/ui/gtk.h
index 7d22affd38..101b147d1b 100644
--- a/include/ui/gtk.h
+++ b/include/ui/gtk.h
@@ -35,6 +35,7 @@ typedef struct GtkDisplayState GtkDisplayState;
typedef struct VirtualGfxConsole {
GtkWidget *drawing_area;
+ DisplayGLCtx dgc;
DisplayChangeListener dcl;
QKbdState *kbd;
DisplaySurface *ds;
@@ -165,7 +166,7 @@ void gd_egl_update(DisplayChangeListener *dcl,
void gd_egl_refresh(DisplayChangeListener *dcl);
void gd_egl_switch(DisplayChangeListener *dcl,
DisplaySurface *surface);
-QEMUGLContext gd_egl_create_context(DisplayChangeListener *dcl,
+QEMUGLContext gd_egl_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params);
void gd_egl_scanout_disable(DisplayChangeListener *dcl);
void gd_egl_scanout_texture(DisplayChangeListener *dcl,
@@ -187,7 +188,7 @@ void gd_egl_flush(DisplayChangeListener *dcl,
void gd_egl_scanout_flush(DisplayChangeListener *dcl,
uint32_t x, uint32_t y, uint32_t w, uint32_t h);
void gtk_egl_init(DisplayGLMode mode);
-int gd_egl_make_current(DisplayChangeListener *dcl,
+int gd_egl_make_current(DisplayGLCtx *dgc,
QEMUGLContext ctx);
/* ui/gtk-gl-area.c */
@@ -198,9 +199,9 @@ void gd_gl_area_update(DisplayChangeListener *dcl,
void gd_gl_area_refresh(DisplayChangeListener *dcl);
void gd_gl_area_switch(DisplayChangeListener *dcl,
DisplaySurface *surface);
-QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl,
+QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params);
-void gd_gl_area_destroy_context(DisplayChangeListener *dcl,
+void gd_gl_area_destroy_context(DisplayGLCtx *dgc,
QEMUGLContext ctx);
void gd_gl_area_scanout_dmabuf(DisplayChangeListener *dcl,
QemuDmaBuf *dmabuf);
@@ -215,7 +216,7 @@ void gd_gl_area_scanout_disable(DisplayChangeListener *dcl);
void gd_gl_area_scanout_flush(DisplayChangeListener *dcl,
uint32_t x, uint32_t y, uint32_t w, uint32_t h);
void gtk_gl_area_init(void);
-int gd_gl_area_make_current(DisplayChangeListener *dcl,
+int gd_gl_area_make_current(DisplayGLCtx *dgc,
QEMUGLContext ctx);
/* gtk-clipboard.c */
diff --git a/include/ui/sdl2.h b/include/ui/sdl2.h
index f85c117a78..71bcf7ebda 100644
--- a/include/ui/sdl2.h
+++ b/include/ui/sdl2.h
@@ -16,6 +16,7 @@
#endif
struct sdl2_console {
+ DisplayGLCtx dgc;
DisplayChangeListener dcl;
DisplaySurface *surface;
DisplayOptions *opts;
@@ -65,10 +66,10 @@ void sdl2_gl_switch(DisplayChangeListener *dcl,
void sdl2_gl_refresh(DisplayChangeListener *dcl);
void sdl2_gl_redraw(struct sdl2_console *scon);
-QEMUGLContext sdl2_gl_create_context(DisplayChangeListener *dcl,
+QEMUGLContext sdl2_gl_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params);
-void sdl2_gl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx);
-int sdl2_gl_make_context_current(DisplayChangeListener *dcl,
+void sdl2_gl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx);
+int sdl2_gl_make_context_current(DisplayGLCtx *dgc,
QEMUGLContext ctx);
void sdl2_gl_scanout_disable(DisplayChangeListener *dcl);
diff --git a/include/ui/spice-display.h b/include/ui/spice-display.h
index ed298d58f0..e271e011da 100644
--- a/include/ui/spice-display.h
+++ b/include/ui/spice-display.h
@@ -86,6 +86,7 @@ typedef struct SimpleSpiceCursor SimpleSpiceCursor;
struct SimpleSpiceDisplay {
DisplaySurface *ds;
+ DisplayGLCtx dgc;
DisplayChangeListener dcl;
void *buf;
int bufsize;
@@ -183,8 +184,4 @@ void qemu_spice_display_start(void);
void qemu_spice_display_stop(void);
int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd);
-bool qemu_spice_fill_device_address(QemuConsole *con,
- char *device_address,
- size_t size);
-
#endif
diff --git a/meson.build b/meson.build
index f45ecf31bd..73d4b241df 100644
--- a/meson.build
+++ b/meson.build
@@ -404,14 +404,16 @@ endif
add_project_arguments(config_host['GLIB_CFLAGS'].split(),
native: false, language: ['c', 'cpp', 'objc'])
glib = declare_dependency(compile_args: config_host['GLIB_CFLAGS'].split(),
- link_args: config_host['GLIB_LIBS'].split())
+ link_args: config_host['GLIB_LIBS'].split(),
+ version: config_host['GLIB_VERSION'])
# override glib dep with the configure results (for subprojects)
meson.override_dependency('glib-2.0', glib)
gio = not_found
if 'CONFIG_GIO' in config_host
gio = declare_dependency(compile_args: config_host['GIO_CFLAGS'].split(),
- link_args: config_host['GIO_LIBS'].split())
+ link_args: config_host['GIO_LIBS'].split(),
+ version: config_host['GLIB_VERSION'])
endif
lttng = not_found
if 'ust' in get_option('trace_backends')
@@ -1395,6 +1397,15 @@ endif
have_host_block_device = (targetos != 'darwin' or
cc.has_header('IOKit/storage/IOMedia.h'))
+dbus_display = false
+if not get_option('dbus_display').disabled()
+ # FIXME enable_modules shouldn't be necessary, but: https://github.com/mesonbuild/meson/issues/8333
+ dbus_display = gio.version().version_compare('>=2.64') and config_host.has_key('GDBUS_CODEGEN') and enable_modules
+ if get_option('dbus_display').enabled() and not dbus_display
+ error('Requirements missing to enable -display dbus (glib>=2.64 && --enable-modules)')
+ endif
+endif
+
have_virtfs = (targetos == 'linux' and
have_system and
libattr.found() and
@@ -1497,8 +1508,14 @@ config_host_data.set('CONFIG_ZSTD', zstd.found())
config_host_data.set('CONFIG_FUSE', fuse.found())
config_host_data.set('CONFIG_FUSE_LSEEK', fuse_lseek.found())
config_host_data.set('CONFIG_SPICE_PROTOCOL', spice_protocol.found())
+if spice_protocol.found()
+config_host_data.set('CONFIG_SPICE_PROTOCOL_MAJOR', spice_protocol.version().split('.')[0])
+config_host_data.set('CONFIG_SPICE_PROTOCOL_MINOR', spice_protocol.version().split('.')[1])
+config_host_data.set('CONFIG_SPICE_PROTOCOL_MICRO', spice_protocol.version().split('.')[2])
+endif
config_host_data.set('CONFIG_SPICE', spice.found())
config_host_data.set('CONFIG_X11', x11.found())
+config_host_data.set('CONFIG_DBUS_DISPLAY', dbus_display)
config_host_data.set('CONFIG_CFI', get_option('cfi'))
config_host_data.set('CONFIG_SELINUX', selinux.found())
config_host_data.set('QEMU_VERSION', '"@0@"'.format(meson.project_version()))
@@ -3222,6 +3239,7 @@ summary_info += {'Trace backends': ','.join(get_option('trace_backends'))}
if 'simple' in get_option('trace_backends')
summary_info += {'Trace output file': get_option('trace_file') + '-<pid>'}
endif
+summary_info += {'D-Bus display': dbus_display}
summary_info += {'QOM debugging': config_host.has_key('CONFIG_QOM_CAST_DEBUG')}
summary_info += {'vhost-kernel support': config_host.has_key('CONFIG_VHOST_KERNEL')}
summary_info += {'vhost-net support': config_host.has_key('CONFIG_VHOST_NET')}
diff --git a/meson_options.txt b/meson_options.txt
index 4114bfcaa4..921967eddb 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -66,6 +66,8 @@ option('cfi_debug', type: 'boolean', value: 'false',
description: 'Verbose errors in case of CFI violation')
option('multiprocess', type: 'feature', value: 'auto',
description: 'Out of process device emulation support')
+option('dbus_display', type: 'feature', value: 'auto',
+ description: '-display dbus support')
option('attr', type : 'feature', value : 'auto',
description: 'attr/xattr support')
diff --git a/monitor/qmp-cmds.c b/monitor/qmp-cmds.c
index 343353e27a..14e3beeaaf 100644
--- a/monitor/qmp-cmds.c
+++ b/monitor/qmp-cmds.c
@@ -24,6 +24,7 @@
#include "chardev/char.h"
#include "ui/qemu-spice.h"
#include "ui/console.h"
+#include "ui/dbus-display.h"
#include "sysemu/kvm.h"
#include "sysemu/runstate.h"
#include "sysemu/runstate-action.h"
@@ -286,6 +287,18 @@ void qmp_add_client(const char *protocol, const char *fdname,
vnc_display_add_client(NULL, fd, skipauth);
return;
#endif
+#ifdef CONFIG_DBUS_DISPLAY
+ } else if (strcmp(protocol, "@dbus-display") == 0) {
+ if (!qemu_using_dbus_display(errp)) {
+ close(fd);
+ return;
+ }
+ if (!qemu_dbus_display.add_client(fd, errp)) {
+ close(fd);
+ return;
+ }
+ return;
+#endif
} else if ((s = qemu_chr_find(protocol)) != NULL) {
if (qemu_chr_add_client(s, fd) < 0) {
error_setg(errp, "failed to add client");
diff --git a/qapi/audio.json b/qapi/audio.json
index 9cba0df8a4..693e327c6b 100644
--- a/qapi/audio.json
+++ b/qapi/audio.json
@@ -386,7 +386,7 @@
# Since: 4.0
##
{ 'enum': 'AudiodevDriver',
- 'data': [ 'none', 'alsa', 'coreaudio', 'dsound', 'jack', 'oss', 'pa',
+ 'data': [ 'none', 'alsa', 'coreaudio', 'dbus', 'dsound', 'jack', 'oss', 'pa',
'sdl', 'spice', 'wav' ] }
##
@@ -412,6 +412,7 @@
'none': 'AudiodevGenericOptions',
'alsa': 'AudiodevAlsaOptions',
'coreaudio': 'AudiodevCoreaudioOptions',
+ 'dbus': 'AudiodevGenericOptions',
'dsound': 'AudiodevDsoundOptions',
'jack': 'AudiodevJackOptions',
'oss': 'AudiodevOssOptions',
diff --git a/qapi/char.json b/qapi/char.json
index f5133a5eeb..7b42151575 100644
--- a/qapi/char.json
+++ b/qapi/char.json
@@ -359,6 +359,20 @@
'if': 'CONFIG_SPICE' }
##
+# @ChardevDBus:
+#
+# Configuration info for DBus chardevs.
+#
+# @name: name of the channel (following docs/spice-port-fqdn.txt)
+#
+# Since: 7.0
+##
+{ 'struct': 'ChardevDBus',
+ 'data': { 'name': 'str' },
+ 'base': 'ChardevCommon',
+ 'if': 'CONFIG_DBUS_DISPLAY' }
+
+##
# @ChardevVC:
#
# Configuration info for virtual console chardevs.
@@ -422,6 +436,7 @@
# @spicevmc: Since 1.5
# @spiceport: Since 1.5
# @qemu-vdagent: Since 6.1
+# @dbus: Since 7.0
# @vc: v1.5
# @ringbuf: Since 1.6
# @memory: Since 1.5
@@ -447,6 +462,7 @@
{ 'name': 'spicevmc', 'if': 'CONFIG_SPICE' },
{ 'name': 'spiceport', 'if': 'CONFIG_SPICE' },
{ 'name': 'qemu-vdagent', 'if': 'CONFIG_SPICE_PROTOCOL' },
+ { 'name': 'dbus', 'if': 'CONFIG_DBUS_DISPLAY' },
'vc',
'ringbuf',
# next one is just for compatibility
@@ -536,6 +552,15 @@
'if': 'CONFIG_SPICE_PROTOCOL' }
##
+# @ChardevDBusWrapper:
+#
+# Since: 7.0
+##
+{ 'struct': 'ChardevDBusWrapper',
+ 'data': { 'data': 'ChardevDBus' },
+ 'if': 'CONFIG_DBUS_DISPLAY' }
+
+##
# @ChardevVCWrapper:
#
# Since: 1.5
@@ -582,6 +607,8 @@
'if': 'CONFIG_SPICE' },
'qemu-vdagent': { 'type': 'ChardevQemuVDAgentWrapper',
'if': 'CONFIG_SPICE_PROTOCOL' },
+ 'dbus': { 'type': 'ChardevDBusWrapper',
+ 'if': 'CONFIG_DBUS_DISPLAY' },
'vc': 'ChardevVCWrapper',
'ringbuf': 'ChardevRingbufWrapper',
# next one is just for compatibility
diff --git a/qapi/misc.json b/qapi/misc.json
index 358548abe1..e8054f415b 100644
--- a/qapi/misc.json
+++ b/qapi/misc.json
@@ -14,8 +14,8 @@
# Allow client connections for VNC, Spice and socket based
# character devices to be passed in to QEMU via SCM_RIGHTS.
#
-# @protocol: protocol name. Valid names are "vnc", "spice" or the
-# name of a character device (eg. from -chardev id=XXXX)
+# @protocol: protocol name. Valid names are "vnc", "spice", "@dbus-display" or
+# the name of a character device (eg. from -chardev id=XXXX)
#
# @fdname: file descriptor name previously passed via 'getfd' command
#
diff --git a/qapi/ui.json b/qapi/ui.json
index d7567ac866..2b4371da37 100644
--- a/qapi/ui.json
+++ b/qapi/ui.json
@@ -1121,6 +1121,30 @@
{ 'struct' : 'DisplayEGLHeadless',
'data' : { '*rendernode' : 'str' } }
+##
+# @DisplayDBus:
+#
+# DBus display options.
+#
+# @addr: The D-Bus bus address (default to the session bus).
+#
+# @rendernode: Which DRM render node should be used. Default is the first
+# available node on the host.
+#
+# @p2p: Whether to use peer-to-peer connections (accepted through
+# ``add_client``).
+#
+# @audiodev: Use the specified DBus audiodev to export audio.
+#
+# Since: 7.0
+#
+##
+{ 'struct' : 'DisplayDBus',
+ 'data' : { '*rendernode' : 'str',
+ '*addr': 'str',
+ '*p2p': 'bool',
+ '*audiodev': 'str' } }
+
##
# @DisplayGLMode:
#
@@ -1186,6 +1210,8 @@
# application to connect to it. The server will redirect
# the serial console and QEMU monitors. (Since 4.0)
#
+# @dbus: Start a D-Bus service for the display. (Since 7.0)
+#
# Since: 2.12
#
##
@@ -1199,7 +1225,10 @@
'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } },
{ 'name': 'curses', 'if': 'CONFIG_CURSES' },
{ 'name': 'cocoa', 'if': 'CONFIG_COCOA' },
- { 'name': 'spice-app', 'if': 'CONFIG_SPICE'} ] }
+ { 'name': 'spice-app', 'if': 'CONFIG_SPICE' },
+ { 'name': 'dbus', 'if': 'CONFIG_DBUS_DISPLAY' }
+ ]
+}
##
# @DisplayOptions:
@@ -1227,7 +1256,8 @@
'gtk': { 'type': 'DisplayGTK', 'if': 'CONFIG_GTK' },
'curses': { 'type': 'DisplayCurses', 'if': 'CONFIG_CURSES' },
'egl-headless': { 'type': 'DisplayEGLHeadless',
- 'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } }
+ 'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } },
+ 'dbus': { 'type': 'DisplayDBus', 'if': 'CONFIG_DBUS_DISPLAY' }
}
}
diff --git a/qemu-options.hx b/qemu-options.hx
index 489b58e151..7d47510947 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -660,6 +660,9 @@ DEF("audiodev", HAS_ARG, QEMU_OPTION_audiodev,
#ifdef CONFIG_SPICE
"-audiodev spice,id=id[,prop[=value][,...]]\n"
#endif
+#ifdef CONFIG_DBUS_DISPLAY
+ "-audiodev dbus,id=id[,prop[=value][,...]]\n"
+#endif
"-audiodev wav,id=id[,prop[=value][,...]]\n"
" path= path of wav file to record\n",
QEMU_ARCH_ALL)
@@ -1863,6 +1866,10 @@ DEF("display", HAS_ARG, QEMU_OPTION_display,
#if defined(CONFIG_OPENGL)
"-display egl-headless[,rendernode=<file>]\n"
#endif
+#if defined(CONFIG_DBUS_DISPLAY)
+ "-display dbus[,addr=<dbusaddr>]\n"
+ " [,gl=on|core|es|off][,rendernode=<file>]\n"
+#endif
"-display none\n"
" select display backend type\n"
" The default display is equivalent to\n "
@@ -1889,6 +1896,19 @@ SRST
application. The Spice server will redirect the serial consoles
and QEMU monitors. (Since 4.0)
+ ``dbus``
+ Export the display over D-Bus interfaces. (Since 7.0)
+
+ The connection is registered with the "org.qemu" name (and queued when
+ already owned).
+
+ ``addr=<dbusaddr>`` : D-Bus bus address to connect to.
+
+ ``p2p=yes|no`` : Use peer-to-peer connection, accepted via QMP ``add_client``.
+
+ ``gl=on|off|core|es`` : Use OpenGL for rendering (the D-Bus interface
+ will share framebuffers with DMABUF file descriptors).
+
``sdl``
Display video output via SDL (usually in a separate graphics
window; see the SDL documentation for other possibilities).
diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
index ae8f18edc2..50bd7bed4d 100644
--- a/scripts/meson-buildoptions.sh
+++ b/scripts/meson-buildoptions.sh
@@ -33,6 +33,7 @@ meson_options_help() {
printf "%s\n" ' coreaudio CoreAudio sound support'
printf "%s\n" ' curl CURL block device driver'
printf "%s\n" ' curses curses UI'
+ printf "%s\n" ' dbus-display -display dbus support'
printf "%s\n" ' docs Documentations build support'
printf "%s\n" ' dsound DirectSound sound support'
printf "%s\n" ' fuse FUSE block device export'
@@ -131,6 +132,8 @@ _meson_option_parse() {
--disable-curl) printf "%s" -Dcurl=disabled ;;
--enable-curses) printf "%s" -Dcurses=enabled ;;
--disable-curses) printf "%s" -Dcurses=disabled ;;
+ --enable-dbus-display) printf "%s" -Ddbus_display=enabled ;;
+ --disable-dbus-display) printf "%s" -Ddbus_display=disabled ;;
--enable-docs) printf "%s" -Ddocs=enabled ;;
--disable-docs) printf "%s" -Ddocs=disabled ;;
--enable-dsound) printf "%s" -Ddsound=enabled ;;
diff --git a/scripts/modinfo-collect.py b/scripts/modinfo-collect.py
index 4acb188c3e..61b90688c6 100755
--- a/scripts/modinfo-collect.py
+++ b/scripts/modinfo-collect.py
@@ -51,6 +51,9 @@ def main(args):
with open('compile_commands.json') as f:
compile_commands = json.load(f)
for src in args:
+ if not src.endswith('.c'):
+ print("MODINFO_DEBUG skip %s" % src)
+ continue
print("MODINFO_DEBUG src %s" % src)
command = find_command(src, target, compile_commands)
cmdline = process_command(src, command)
diff --git a/tests/qtest/dbus-display-test.c b/tests/qtest/dbus-display-test.c
new file mode 100644
index 0000000000..43c77aff04
--- /dev/null
+++ b/tests/qtest/dbus-display-test.c
@@ -0,0 +1,257 @@
+#include "qemu/osdep.h"
+#include "qemu/dbus.h"
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+#include "libqos/libqtest.h"
+#include "qemu-common.h"
+#include "dbus-display1.h"
+
+static GDBusConnection*
+test_dbus_p2p_from_fd(int fd)
+{
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GSocket) socket = NULL;
+ g_autoptr(GSocketConnection) socketc = NULL;
+ GDBusConnection *conn;
+
+ socket = g_socket_new_from_fd(fd, &err);
+ g_assert_no_error(err);
+
+ socketc = g_socket_connection_factory_create_connection(socket);
+ g_assert(socketc != NULL);
+
+ conn = g_dbus_connection_new_sync(
+ G_IO_STREAM(socketc), NULL,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+ G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING,
+ NULL, NULL, &err);
+ g_assert_no_error(err);
+
+ return conn;
+}
+
+static void
+test_setup(QTestState **qts, GDBusConnection **conn)
+{
+ int pair[2];
+
+ *qts = qtest_init("-display dbus,p2p=yes -name dbus-test");
+
+ g_assert_cmpint(socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0);
+
+ qtest_qmp_add_client(*qts, "@dbus-display", pair[1]);
+
+ *conn = test_dbus_p2p_from_fd(pair[0]);
+ g_dbus_connection_start_message_processing(*conn);
+}
+
+static void
+test_dbus_display_vm(void)
+{
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GDBusConnection) conn = NULL;
+ g_autoptr(QemuDBusDisplay1VMProxy) vm = NULL;
+ QTestState *qts = NULL;
+
+ test_setup(&qts, &conn);
+
+ vm = QEMU_DBUS_DISPLAY1_VM_PROXY(
+ qemu_dbus_display1_vm_proxy_new_sync(
+ conn,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ DBUS_DISPLAY1_ROOT "/VM",
+ NULL,
+ &err));
+ g_assert_no_error(err);
+
+ g_assert_cmpstr(
+ qemu_dbus_display1_vm_get_name(QEMU_DBUS_DISPLAY1_VM(vm)),
+ ==,
+ "dbus-test");
+ qtest_quit(qts);
+}
+
+typedef struct TestDBusConsoleRegister {
+ GMainLoop *loop;
+ GThread *thread;
+ GDBusConnection *listener_conn;
+ GDBusObjectManagerServer *server;
+} TestDBusConsoleRegister;
+
+static gboolean listener_handle_scanout(
+ QemuDBusDisplay1Listener *object,
+ GDBusMethodInvocation *invocation,
+ guint arg_width,
+ guint arg_height,
+ guint arg_stride,
+ guint arg_pixman_format,
+ GVariant *arg_data,
+ TestDBusConsoleRegister *test)
+{
+ g_main_loop_quit(test->loop);
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static void
+test_dbus_console_setup_listener(TestDBusConsoleRegister *test)
+{
+ g_autoptr(GDBusObjectSkeleton) listener = NULL;
+ g_autoptr(QemuDBusDisplay1ListenerSkeleton) iface = NULL;
+
+ test->server = g_dbus_object_manager_server_new(DBUS_DISPLAY1_ROOT);
+ listener = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Listener");
+ iface = QEMU_DBUS_DISPLAY1_LISTENER_SKELETON(
+ qemu_dbus_display1_listener_skeleton_new());
+ g_object_connect(iface,
+ "signal::handle-scanout", listener_handle_scanout, test,
+ NULL);
+ g_dbus_object_skeleton_add_interface(listener,
+ G_DBUS_INTERFACE_SKELETON(iface));
+ g_dbus_object_manager_server_export(test->server, listener);
+ g_dbus_object_manager_server_set_connection(test->server,
+ test->listener_conn);
+
+ g_dbus_connection_start_message_processing(test->listener_conn);
+}
+
+static void
+test_dbus_console_registered(GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ TestDBusConsoleRegister *test = user_data;
+ g_autoptr(GError) err = NULL;
+
+ qemu_dbus_display1_console_call_register_listener_finish(
+ QEMU_DBUS_DISPLAY1_CONSOLE(source_object),
+ NULL, res, &err);
+ g_assert_no_error(err);
+
+ test->listener_conn = g_thread_join(test->thread);
+ test_dbus_console_setup_listener(test);
+}
+
+static gpointer
+test_dbus_p2p_server_setup_thread(gpointer data)
+{
+ return test_dbus_p2p_from_fd(GPOINTER_TO_INT(data));
+}
+
+static void
+test_dbus_display_console(void)
+{
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GDBusConnection) conn = NULL;
+ g_autoptr(QemuDBusDisplay1ConsoleProxy) console = NULL;
+ g_autoptr(GUnixFDList) fd_list = NULL;
+ g_autoptr(GMainLoop) loop = NULL;
+ QTestState *qts = NULL;
+ int pair[2], idx;
+ TestDBusConsoleRegister test;
+
+ test_setup(&qts, &conn);
+
+ g_assert_cmpint(socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0);
+ fd_list = g_unix_fd_list_new();
+ idx = g_unix_fd_list_append(fd_list, pair[1], NULL);
+
+ console = QEMU_DBUS_DISPLAY1_CONSOLE_PROXY(
+ qemu_dbus_display1_console_proxy_new_sync(
+ conn,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "/org/qemu/Display1/Console_0",
+ NULL,
+ &err));
+ g_assert_no_error(err);
+
+ test.loop = loop = g_main_loop_new(NULL, FALSE);
+ test.thread = g_thread_new(NULL, test_dbus_p2p_server_setup_thread,
+ GINT_TO_POINTER(pair[0]));
+
+ qemu_dbus_display1_console_call_register_listener(
+ QEMU_DBUS_DISPLAY1_CONSOLE(console),
+ g_variant_new_handle(idx),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ fd_list,
+ NULL,
+ test_dbus_console_registered,
+ &test);
+
+ g_main_loop_run(loop);
+
+ g_clear_object(&test.server);
+ g_clear_object(&test.listener_conn);
+ qtest_quit(qts);
+}
+
+static void
+test_dbus_display_keyboard(void)
+{
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GDBusConnection) conn = NULL;
+ g_autoptr(QemuDBusDisplay1KeyboardProxy) keyboard = NULL;
+ QTestState *qts = NULL;
+
+ test_setup(&qts, &conn);
+
+ keyboard = QEMU_DBUS_DISPLAY1_KEYBOARD_PROXY(
+ qemu_dbus_display1_keyboard_proxy_new_sync(
+ conn,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "/org/qemu/Display1/Console_0",
+ NULL,
+ &err));
+ g_assert_no_error(err);
+
+
+ g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 0);
+ g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0);
+
+ qemu_dbus_display1_keyboard_call_press_sync(
+ QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard),
+ 0x1C, /* qnum enter */
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &err);
+ g_assert_no_error(err);
+
+ /* may be should wait for interrupt? */
+ g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 1);
+ g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0x5A); /* scan code 2 enter */
+
+ qemu_dbus_display1_keyboard_call_release_sync(
+ QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard),
+ 0x1C, /* qnum enter */
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &err);
+ g_assert_no_error(err);
+
+ g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 1);
+ g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0xF0); /* scan code 2 release */
+ g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0x5A); /* scan code 2 enter */
+
+ g_assert_cmpint(qemu_dbus_display1_keyboard_get_modifiers(
+ QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard)), ==, 0);
+
+ qtest_quit(qts);
+}
+
+int
+main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/dbus-display/vm", test_dbus_display_vm);
+ qtest_add_func("/dbus-display/console", test_dbus_display_console);
+ qtest_add_func("/dbus-display/keyboard", test_dbus_display_keyboard);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/dbus-vmstate1.xml b/tests/qtest/dbus-vmstate1.xml
deleted file mode 100644
index cc8563be4c..0000000000
--- a/tests/qtest/dbus-vmstate1.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
- <interface name="org.qemu.VMState1">
- <property name="Id" type="s" access="read"/>
- <method name="Load">
- <arg type="ay" name="data" direction="in"/>
- </method>
- <method name="Save">
- <arg type="ay" name="data" direction="out"/>
- </method>
- </interface>
-</node>
diff --git a/tests/qtest/libqos/libqtest.h b/tests/qtest/libqos/libqtest.h
index dff6b31cf0..a6d38d7ef7 100644
--- a/tests/qtest/libqos/libqtest.h
+++ b/tests/qtest/libqos/libqtest.h
@@ -745,6 +745,16 @@ void qtest_qmp_device_add(QTestState *qts, const char *driver, const char *id,
const char *fmt, ...) GCC_FMT_ATTR(4, 5);
/**
+ * qtest_qmp_add_client:
+ * @qts: QTestState instance to operate on
+ * @protocol: the protocol to add to
+ * @fd: the client file-descriptor
+ *
+ * Call QMP ``getfd`` followed by ``add_client`` with the given @fd.
+ */
+void qtest_qmp_add_client(QTestState *qts, const char *protocol, int fd);
+
+/**
* qtest_qmp_device_del:
* @qts: QTestState instance to operate on
* @id: Identification string
diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c
index 65ed949685..a68326caae 100644
--- a/tests/qtest/libqtest.c
+++ b/tests/qtest/libqtest.c
@@ -1453,6 +1453,25 @@ void qtest_qmp_device_add(QTestState *qts, const char *driver, const char *id,
qobject_unref(args);
}
+void qtest_qmp_add_client(QTestState *qts, const char *protocol, int fd)
+{
+ QDict *resp;
+
+ resp = qtest_qmp_fds(qts, &fd, 1, "{'execute': 'getfd',"
+ "'arguments': {'fdname': 'fdname'}}");
+ g_assert(resp);
+ g_assert(!qdict_haskey(resp, "event")); /* We don't expect any events */
+ g_assert(!qdict_haskey(resp, "error"));
+ qobject_unref(resp);
+
+ resp = qtest_qmp(
+ qts, "{'execute': 'add_client',"
+ "'arguments': {'protocol': %s, 'fdname': 'fdname'}}", protocol);
+ g_assert(resp);
+ g_assert(!qdict_haskey(resp, "event")); /* We don't expect any events */
+ g_assert(!qdict_haskey(resp, "error"));
+ qobject_unref(resp);
+}
/*
* Generic hot-unplugging test via the device_del QMP command.
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index ebeac59b3f..1b2bde6660 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -92,13 +92,17 @@ qtests_i386 = \
'test-x86-cpuid-compat',
'numa-test']
+if dbus_display
+ qtests_i386 += ['dbus-display-test']
+endif
+
dbus_daemon = find_program('dbus-daemon', required: false)
if dbus_daemon.found() and config_host.has_key('GDBUS_CODEGEN')
# Temporarily disabled due to Patchew failures:
#qtests_i386 += ['dbus-vmstate-test']
dbus_vmstate1 = custom_target('dbus-vmstate description',
output: ['dbus-vmstate1.h', 'dbus-vmstate1.c'],
- input: files('dbus-vmstate1.xml'),
+ input: meson.source_root() / 'backends/dbus-vmstate1.xml',
command: [config_host['GDBUS_CODEGEN'],
'@INPUT@',
'--interface-prefix', 'org.qemu',
@@ -265,6 +269,10 @@ qtests = {
'vmgenid-test': files('boot-sector.c', 'acpi-utils.c'),
}
+if dbus_display
+qtests += {'dbus-display-test': [dbus_display1, gio]}
+endif
+
qtest_executables = {}
foreach dir : target_dirs
if not dir.endswith('-softmmu')
diff --git a/ui/clipboard.c b/ui/clipboard.c
index d7b008d62a..82572ea116 100644
--- a/ui/clipboard.c
+++ b/ui/clipboard.c
@@ -8,7 +8,7 @@ static QemuClipboardInfo *cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT];
void qemu_clipboard_peer_register(QemuClipboardPeer *peer)
{
- notifier_list_add(&clipboard_notifiers, &peer->update);
+ notifier_list_add(&clipboard_notifiers, &peer->notifier);
}
void qemu_clipboard_peer_unregister(QemuClipboardPeer *peer)
@@ -18,8 +18,7 @@ void qemu_clipboard_peer_unregister(QemuClipboardPeer *peer)
for (i = 0; i < QEMU_CLIPBOARD_SELECTION__COUNT; i++) {
qemu_clipboard_peer_release(peer, i);
}
-
- notifier_remove(&peer->update);
+ notifier_remove(&peer->notifier);
}
bool qemu_clipboard_peer_owns(QemuClipboardPeer *peer,
@@ -42,12 +41,32 @@ void qemu_clipboard_peer_release(QemuClipboardPeer *peer,
}
}
+bool qemu_clipboard_check_serial(QemuClipboardInfo *info, bool client)
+{
+ if (!info->has_serial ||
+ !cbinfo[info->selection] ||
+ !cbinfo[info->selection]->has_serial) {
+ return true;
+ }
+
+ if (client) {
+ return cbinfo[info->selection]->serial >= info->serial;
+ } else {
+ return cbinfo[info->selection]->serial > info->serial;
+ }
+}
+
void qemu_clipboard_update(QemuClipboardInfo *info)
{
+ QemuClipboardNotify notify = {
+ .type = QEMU_CLIPBOARD_UPDATE_INFO,
+ .info = info,
+ };
g_autoptr(QemuClipboardInfo) old = NULL;
+
assert(info->selection < QEMU_CLIPBOARD_SELECTION__COUNT);
- notifier_list_notify(&clipboard_notifiers, info);
+ notifier_list_notify(&clipboard_notifiers, &notify);
old = cbinfo[info->selection];
cbinfo[info->selection] = qemu_clipboard_info_ref(info);
@@ -110,6 +129,13 @@ void qemu_clipboard_request(QemuClipboardInfo *info,
info->owner->request(info, type);
}
+void qemu_clipboard_reset_serial(void)
+{
+ QemuClipboardNotify notify = { .type = QEMU_CLIPBOARD_RESET_SERIAL };
+
+ notifier_list_notify(&clipboard_notifiers, &notify);
+}
+
void qemu_clipboard_set_data(QemuClipboardPeer *peer,
QemuClipboardInfo *info,
QemuClipboardType type,
diff --git a/ui/cocoa.m b/ui/cocoa.m
index 68a6302184..69745c483b 100644
--- a/ui/cocoa.m
+++ b/ui/cocoa.m
@@ -552,7 +552,7 @@ QemuCocoaView *cocoaView;
info.width = frameSize.width;
info.height = frameSize.height;
- dpy_set_ui_info(dcl.con, &info);
+ dpy_set_ui_info(dcl.con, &info, TRUE);
}
- (void)viewDidMoveToWindow
@@ -1808,14 +1808,12 @@ static void cocoa_clipboard_request(QemuClipboardInfo *info,
static QemuClipboardPeer cbpeer = {
.name = "cocoa",
- .update = { .notify = cocoa_clipboard_notify },
+ .notifier = { .notify = cocoa_clipboard_notify },
.request = cocoa_clipboard_request
};
-static void cocoa_clipboard_notify(Notifier *notifier, void *data)
+static void cocoa_clipboard_update_info(QemuClipboardInfo *info)
{
- QemuClipboardInfo *info = data;
-
if (info->owner == &cbpeer || info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) {
return;
}
@@ -1831,6 +1829,20 @@ static void cocoa_clipboard_notify(Notifier *notifier, void *data)
qemu_event_set(&cbevent);
}
+static void cocoa_clipboard_notify(Notifier *notifier, void *data)
+{
+ QemuClipboardNotify *notify = data;
+
+ switch (notify->type) {
+ case QEMU_CLIPBOARD_UPDATE_INFO:
+ cocoa_clipboard_update_info(notify->info);
+ return;
+ case QEMU_CLIPBOARD_RESET_SERIAL:
+ /* ignore */
+ return;
+ }
+}
+
static void cocoa_clipboard_request(QemuClipboardInfo *info,
QemuClipboardType type)
{
diff --git a/ui/console.c b/ui/console.c
index 29a3e3f0f5..40eebb6d2c 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -77,9 +77,11 @@ struct QemuConsole {
console_type_t console_type;
DisplayState *ds;
DisplaySurface *surface;
+ DisplayScanout scanout;
int dcls;
- DisplayChangeListener *gl;
- bool gl_block;
+ DisplayGLCtx *gl;
+ int gl_block;
+ QEMUTimer *gl_unblock_timer;
int window_id;
/* Graphic console state. */
@@ -145,6 +147,7 @@ static void dpy_refresh(DisplayState *s);
static DisplayState *get_alloc_displaystate(void);
static void text_console_update_cursor_timer(void);
static void text_console_update_cursor(void *opaque);
+static bool displaychangelistener_has_dmabuf(DisplayChangeListener *dcl);
static void gui_update(void *opaque)
{
@@ -233,22 +236,36 @@ void graphic_hw_update(QemuConsole *con)
}
}
-void graphic_hw_gl_block(QemuConsole *con, bool block)
+static void graphic_hw_gl_unblock_timer(void *opaque)
{
- assert(con != NULL);
-
- con->gl_block = block;
- if (con->hw_ops->gl_block) {
- con->hw_ops->gl_block(con->hw, block);
- }
+ warn_report("console: no gl-unblock within one second");
}
-void graphic_hw_gl_flushed(QemuConsole *con)
+void graphic_hw_gl_block(QemuConsole *con, bool block)
{
+ uint64_t timeout;
assert(con != NULL);
- if (con->hw_ops->gl_flushed) {
- con->hw_ops->gl_flushed(con->hw);
+ if (block) {
+ con->gl_block++;
+ } else {
+ con->gl_block--;
+ }
+ assert(con->gl_block >= 0);
+ if (!con->hw_ops->gl_block) {
+ return;
+ }
+ if ((block && con->gl_block != 1) || (!block && con->gl_block != 0)) {
+ return;
+ }
+ con->hw_ops->gl_block(con->hw, block);
+
+ if (block) {
+ timeout = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
+ timeout += 1000; /* one sec */
+ timer_mod(con->gl_unblock_timer, timeout);
+ } else {
+ timer_del(con->gl_unblock_timer);
}
}
@@ -466,6 +483,8 @@ static void text_console_resize(QemuConsole *s)
TextCell *cells, *c, *c1;
int w1, x, y, last_width;
+ assert(s->scanout.kind == SCANOUT_SURFACE);
+
last_width = s->width;
s->width = surface_width(s->surface) / FONT_WIDTH;
s->height = surface_height(s->surface) / FONT_HEIGHT;
@@ -1037,6 +1056,48 @@ static void console_putchar(QemuConsole *s, int ch)
}
}
+static void displaychangelistener_display_console(DisplayChangeListener *dcl,
+ QemuConsole *con)
+{
+ static const char nodev[] =
+ "This VM has no graphic display device.";
+ static DisplaySurface *dummy;
+
+ if (!con) {
+ if (!dcl->ops->dpy_gfx_switch) {
+ return;
+ }
+ if (!dummy) {
+ dummy = qemu_create_placeholder_surface(640, 480, nodev);
+ }
+ dcl->ops->dpy_gfx_switch(dcl, dummy);
+ return;
+ }
+
+ if (con->scanout.kind == SCANOUT_DMABUF &&
+ displaychangelistener_has_dmabuf(dcl)) {
+ dcl->ops->dpy_gl_scanout_dmabuf(dcl, con->scanout.dmabuf);
+ } else if (con->scanout.kind == SCANOUT_TEXTURE &&
+ dcl->ops->dpy_gl_scanout_texture) {
+ dcl->ops->dpy_gl_scanout_texture(dcl,
+ con->scanout.texture.backing_id,
+ con->scanout.texture.backing_y_0_top,
+ con->scanout.texture.backing_width,
+ con->scanout.texture.backing_height,
+ con->scanout.texture.x,
+ con->scanout.texture.y,
+ con->scanout.texture.width,
+ con->scanout.texture.height);
+ } else if (con->scanout.kind == SCANOUT_SURFACE &&
+ dcl->ops->dpy_gfx_switch) {
+ dcl->ops->dpy_gfx_switch(dcl, con->surface);
+ }
+
+ dcl->ops->dpy_gfx_update(dcl, 0, 0,
+ qemu_console_get_width(con, 0),
+ qemu_console_get_height(con, 0));
+}
+
void console_select(unsigned int index)
{
DisplayChangeListener *dcl;
@@ -1053,13 +1114,7 @@ void console_select(unsigned int index)
if (dcl->con != NULL) {
continue;
}
- if (dcl->ops->dpy_gfx_switch) {
- dcl->ops->dpy_gfx_switch(dcl, s->surface);
- }
- }
- if (s->surface) {
- dpy_gfx_update(s, 0, 0, surface_width(s->surface),
- surface_height(s->surface));
+ displaychangelistener_display_console(dcl, s);
}
}
if (ds->have_text) {
@@ -1443,24 +1498,36 @@ static bool dpy_compatible_with(QemuConsole *con,
return true;
}
+void qemu_console_set_display_gl_ctx(QemuConsole *con, DisplayGLCtx *gl)
+{
+ /* display has opengl support */
+ assert(con);
+ if (con->gl) {
+ error_report("The console already has an OpenGL context.");
+ exit(1);
+ }
+ con->gl = gl;
+}
+
+static bool dpy_gl_compatible_with(QemuConsole *con, DisplayChangeListener *dcl)
+{
+ if (!con->gl) {
+ return true;
+ }
+
+ return con->gl->ops->compatible_dcl == dcl->ops;
+}
+
void register_displaychangelistener(DisplayChangeListener *dcl)
{
- static const char nodev[] =
- "This VM has no graphic display device.";
- static DisplaySurface *dummy;
QemuConsole *con;
assert(!dcl->ds);
- if (dcl->ops->dpy_gl_ctx_create) {
- /* display has opengl support */
- assert(dcl->con);
- if (dcl->con->gl) {
- fprintf(stderr, "can't register two opengl displays (%s, %s)\n",
- dcl->ops->dpy_name, dcl->con->gl->ops->dpy_name);
- exit(1);
- }
- dcl->con->gl = dcl;
+ if (dcl->con && !dpy_gl_compatible_with(dcl->con, dcl)) {
+ error_report("Display %s is incompatible with the GL context",
+ dcl->ops->dpy_name);
+ exit(1);
}
if (dcl->con) {
@@ -1477,16 +1544,7 @@ void register_displaychangelistener(DisplayChangeListener *dcl)
} else {
con = active_console;
}
- if (dcl->ops->dpy_gfx_switch) {
- if (con) {
- dcl->ops->dpy_gfx_switch(dcl, con->surface);
- } else {
- if (!dummy) {
- dummy = qemu_create_placeholder_surface(640, 480, nodev);
- }
- dcl->ops->dpy_gfx_switch(dcl, dummy);
- }
- }
+ displaychangelistener_display_console(dcl, con);
text_console_update_cursor(NULL);
}
@@ -1538,7 +1596,7 @@ const QemuUIInfo *dpy_get_ui_info(const QemuConsole *con)
return &con->ui_info;
}
-int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info)
+int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info, bool delay)
{
if (con == NULL) {
con = active_console;
@@ -1558,7 +1616,8 @@ int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info)
* go notify the guest.
*/
con->ui_info = *info;
- timer_mod(con->ui_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000);
+ timer_mod(con->ui_timer,
+ qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + (delay ? 1000 : 0));
return 0;
}
@@ -1566,13 +1625,9 @@ void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h)
{
DisplayState *s = con->ds;
DisplayChangeListener *dcl;
- int width = w;
- int height = h;
+ int width = qemu_console_get_width(con, x + w);
+ int height = qemu_console_get_height(con, y + h);
- if (con->surface) {
- width = surface_width(con->surface);
- height = surface_height(con->surface);
- }
x = MAX(x, 0);
y = MAX(y, 0);
x = MIN(x, width);
@@ -1595,12 +1650,10 @@ void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h)
void dpy_gfx_update_full(QemuConsole *con)
{
- if (!con->surface) {
- return;
- }
- dpy_gfx_update(con, 0, 0,
- surface_width(con->surface),
- surface_height(con->surface));
+ int w = qemu_console_get_width(con, 0);
+ int h = qemu_console_get_height(con, 0);
+
+ dpy_gfx_update(con, 0, 0, w, h);
}
void dpy_gfx_replace_surface(QemuConsole *con,
@@ -1627,6 +1680,7 @@ void dpy_gfx_replace_surface(QemuConsole *con,
assert(old_surface != surface);
+ con->scanout.kind = SCANOUT_SURFACE;
con->surface = surface;
QLIST_FOREACH(dcl, &s->listeners, next) {
if (con != (dcl->con ? dcl->con : active_console)) {
@@ -1799,8 +1853,15 @@ int dpy_gl_ctx_make_current(QemuConsole *con, QEMUGLContext ctx)
void dpy_gl_scanout_disable(QemuConsole *con)
{
- assert(con->gl);
- con->gl->ops->dpy_gl_scanout_disable(con->gl);
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
+
+ if (con->scanout.kind != SCANOUT_SURFACE) {
+ con->scanout.kind = SCANOUT_NONE;
+ }
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ dcl->ops->dpy_gl_scanout_disable(dcl);
+ }
}
void dpy_gl_scanout_texture(QemuConsole *con,
@@ -1811,56 +1872,88 @@ void dpy_gl_scanout_texture(QemuConsole *con,
uint32_t x, uint32_t y,
uint32_t width, uint32_t height)
{
- assert(con->gl);
- con->gl->ops->dpy_gl_scanout_texture(con->gl, backing_id,
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
+
+ con->scanout.kind = SCANOUT_TEXTURE;
+ con->scanout.texture = (ScanoutTexture) {
+ backing_id, backing_y_0_top, backing_width, backing_height,
+ x, y, width, height
+ };
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ dcl->ops->dpy_gl_scanout_texture(dcl, backing_id,
backing_y_0_top,
backing_width, backing_height,
x, y, width, height);
+ }
}
void dpy_gl_scanout_dmabuf(QemuConsole *con,
QemuDmaBuf *dmabuf)
{
- assert(con->gl);
- con->gl->ops->dpy_gl_scanout_dmabuf(con->gl, dmabuf);
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
+
+ con->scanout.kind = SCANOUT_DMABUF;
+ con->scanout.dmabuf = dmabuf;
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ dcl->ops->dpy_gl_scanout_dmabuf(dcl, dmabuf);
+ }
}
void dpy_gl_cursor_dmabuf(QemuConsole *con, QemuDmaBuf *dmabuf,
bool have_hot, uint32_t hot_x, uint32_t hot_y)
{
- assert(con->gl);
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
- if (con->gl->ops->dpy_gl_cursor_dmabuf) {
- con->gl->ops->dpy_gl_cursor_dmabuf(con->gl, dmabuf,
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ if (dcl->ops->dpy_gl_cursor_dmabuf) {
+ dcl->ops->dpy_gl_cursor_dmabuf(dcl, dmabuf,
have_hot, hot_x, hot_y);
+ }
}
}
void dpy_gl_cursor_position(QemuConsole *con,
uint32_t pos_x, uint32_t pos_y)
{
- assert(con->gl);
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
- if (con->gl->ops->dpy_gl_cursor_position) {
- con->gl->ops->dpy_gl_cursor_position(con->gl, pos_x, pos_y);
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ if (dcl->ops->dpy_gl_cursor_position) {
+ dcl->ops->dpy_gl_cursor_position(dcl, pos_x, pos_y);
+ }
}
}
void dpy_gl_release_dmabuf(QemuConsole *con,
QemuDmaBuf *dmabuf)
{
- assert(con->gl);
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
- if (con->gl->ops->dpy_gl_release_dmabuf) {
- con->gl->ops->dpy_gl_release_dmabuf(con->gl, dmabuf);
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ if (dcl->ops->dpy_gl_release_dmabuf) {
+ dcl->ops->dpy_gl_release_dmabuf(dcl, dmabuf);
+ }
}
}
void dpy_gl_update(QemuConsole *con,
uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
+
assert(con->gl);
- con->gl->ops->dpy_gl_update(con->gl, x, y, w, h);
+
+ graphic_hw_gl_block(con, true);
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ dcl->ops->dpy_gl_update(dcl, x, y, w, h);
+ }
+ graphic_hw_gl_block(con, false);
}
/***********************************************************/
@@ -1929,10 +2022,8 @@ QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head,
s = qemu_console_lookup_unused();
if (s) {
trace_console_gfx_reuse(s->index);
- if (s->surface) {
- width = surface_width(s->surface);
- height = surface_height(s->surface);
- }
+ width = qemu_console_get_width(s, 0);
+ height = qemu_console_get_height(s, 0);
} else {
trace_console_gfx_new();
s = new_console(ds, GRAPHIC_CONSOLE, head);
@@ -1947,6 +2038,8 @@ QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head,
surface = qemu_create_placeholder_surface(width, height, noinit);
dpy_gfx_replace_surface(s, surface);
+ s->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
+ graphic_hw_gl_unblock_timer, s);
return s;
}
@@ -1959,13 +2052,8 @@ void graphic_console_close(QemuConsole *con)
static const char unplugged[] =
"Guest display has been unplugged";
DisplaySurface *surface;
- int width = 640;
- int height = 480;
-
- if (con->surface) {
- width = surface_width(con->surface);
- height = surface_height(con->surface);
- }
+ int width = qemu_console_get_width(con, 640);
+ int height = qemu_console_get_height(con, 480);
trace_console_gfx_close(con->index);
object_property_set_link(OBJECT(con), "device", NULL, &error_abort);
@@ -2117,7 +2205,19 @@ int qemu_console_get_width(QemuConsole *con, int fallback)
if (con == NULL) {
con = active_console;
}
- return con ? surface_width(con->surface) : fallback;
+ if (con == NULL) {
+ return fallback;
+ }
+ switch (con->scanout.kind) {
+ case SCANOUT_DMABUF:
+ return con->scanout.dmabuf->width;
+ case SCANOUT_TEXTURE:
+ return con->scanout.texture.width;
+ case SCANOUT_SURFACE:
+ return surface_width(con->surface);
+ default:
+ return fallback;
+ }
}
int qemu_console_get_height(QemuConsole *con, int fallback)
@@ -2125,7 +2225,19 @@ int qemu_console_get_height(QemuConsole *con, int fallback)
if (con == NULL) {
con = active_console;
}
- return con ? surface_height(con->surface) : fallback;
+ if (con == NULL) {
+ return fallback;
+ }
+ switch (con->scanout.kind) {
+ case SCANOUT_DMABUF:
+ return con->scanout.dmabuf->height;
+ case SCANOUT_TEXTURE:
+ return con->scanout.texture.height;
+ case SCANOUT_SURFACE:
+ return surface_height(con->surface);
+ default:
+ return fallback;
+ }
}
static void vc_chr_accept_input(Chardev *chr)
@@ -2191,12 +2303,13 @@ static void text_console_do_init(Chardev *chr, DisplayState *ds)
s->total_height = DEFAULT_BACKSCROLL;
s->x = 0;
s->y = 0;
- if (!s->surface) {
- if (active_console && active_console->surface) {
- g_width = surface_width(active_console->surface);
- g_height = surface_height(active_console->surface);
+ if (s->scanout.kind != SCANOUT_SURFACE) {
+ if (active_console && active_console->scanout.kind == SCANOUT_SURFACE) {
+ g_width = qemu_console_get_width(active_console, g_width);
+ g_height = qemu_console_get_height(active_console, g_height);
}
s->surface = qemu_create_displaysurface(g_width, g_height);
+ s->scanout.kind = SCANOUT_SURFACE;
}
s->hw_ops = &text_console_ops;
@@ -2255,6 +2368,7 @@ static void vc_chr_open(Chardev *chr,
s = new_console(NULL, TEXT_CONSOLE, 0);
} else {
s = new_console(NULL, TEXT_CONSOLE_FIXED_SIZE, 0);
+ s->scanout.kind = SCANOUT_SURFACE;
s->surface = qemu_create_displaysurface(width, height);
}
@@ -2278,13 +2392,13 @@ static void vc_chr_open(Chardev *chr,
void qemu_console_resize(QemuConsole *s, int width, int height)
{
- DisplaySurface *surface;
+ DisplaySurface *surface = qemu_console_surface(s);
assert(s->console_type == GRAPHIC_CONSOLE);
- if (s->surface && (s->surface->flags & QEMU_ALLOCATED_FLAG) &&
- pixman_image_get_width(s->surface->image) == width &&
- pixman_image_get_height(s->surface->image) == height) {
+ if (surface && (surface->flags & QEMU_ALLOCATED_FLAG) &&
+ pixman_image_get_width(surface->image) == width &&
+ pixman_image_get_height(surface->image) == height) {
return;
}
@@ -2294,7 +2408,12 @@ void qemu_console_resize(QemuConsole *s, int width, int height)
DisplaySurface *qemu_console_surface(QemuConsole *console)
{
- return console->surface;
+ switch (console->scanout.kind) {
+ case SCANOUT_SURFACE:
+ return console->surface;
+ default:
+ return NULL;
+ }
}
PixelFormat qemu_default_pixelformat(int bpp)
diff --git a/ui/dbus-chardev.c b/ui/dbus-chardev.c
new file mode 100644
index 0000000000..940ef937cd
--- /dev/null
+++ b/ui/dbus-chardev.c
@@ -0,0 +1,296 @@
+/*
+ * QEMU DBus display
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * 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 OTHER
+ * 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 "trace.h"
+#include "qapi/error.h"
+#include "qemu/config-file.h"
+#include "qemu/option.h"
+
+#include <gio/gunixfdlist.h>
+
+#include "dbus.h"
+
+static char *
+dbus_display_chardev_path(DBusChardev *chr)
+{
+ return g_strdup_printf(DBUS_DISPLAY1_ROOT "/Chardev_%s",
+ CHARDEV(chr)->label);
+}
+
+static void
+dbus_display_chardev_export(DBusDisplay *dpy, DBusChardev *chr)
+{
+ g_autoptr(GDBusObjectSkeleton) sk = NULL;
+ g_autofree char *path = dbus_display_chardev_path(chr);
+
+ if (chr->exported) {
+ return;
+ }
+
+ sk = g_dbus_object_skeleton_new(path);
+ g_dbus_object_skeleton_add_interface(
+ sk, G_DBUS_INTERFACE_SKELETON(chr->iface));
+ g_dbus_object_manager_server_export(dpy->server, sk);
+ chr->exported = true;
+}
+
+static void
+dbus_display_chardev_unexport(DBusDisplay *dpy, DBusChardev *chr)
+{
+ g_autofree char *path = dbus_display_chardev_path(chr);
+
+ if (!chr->exported) {
+ return;
+ }
+
+ g_dbus_object_manager_server_unexport(dpy->server, path);
+ chr->exported = false;
+}
+
+static int
+dbus_display_chardev_foreach(Object *obj, void *data)
+{
+ DBusDisplay *dpy = DBUS_DISPLAY(data);
+
+ if (!CHARDEV_IS_DBUS(obj)) {
+ return 0;
+ }
+
+ dbus_display_chardev_export(dpy, DBUS_CHARDEV(obj));
+
+ return 0;
+}
+
+static void
+dbus_display_on_notify(Notifier *notifier, void *data)
+{
+ DBusDisplay *dpy = container_of(notifier, DBusDisplay, notifier);
+ DBusDisplayEvent *event = data;
+
+ switch (event->type) {
+ case DBUS_DISPLAY_CHARDEV_OPEN:
+ dbus_display_chardev_export(dpy, event->chardev);
+ break;
+ case DBUS_DISPLAY_CHARDEV_CLOSE:
+ dbus_display_chardev_unexport(dpy, event->chardev);
+ break;
+ }
+}
+
+void
+dbus_chardev_init(DBusDisplay *dpy)
+{
+ dpy->notifier.notify = dbus_display_on_notify;
+ dbus_display_notifier_add(&dpy->notifier);
+
+ object_child_foreach(container_get(object_get_root(), "/chardevs"),
+ dbus_display_chardev_foreach, dpy);
+}
+
+static gboolean
+dbus_chr_register(
+ DBusChardev *dc,
+ GDBusMethodInvocation *invocation,
+ GUnixFDList *fd_list,
+ GVariant *arg_stream,
+ QemuDBusDisplay1Chardev *object)
+{
+ g_autoptr(GError) err = NULL;
+ int fd;
+
+ fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_stream), &err);
+ if (err) {
+ g_dbus_method_invocation_return_error(
+ invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Couldn't get peer FD: %s", err->message);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ if (qemu_chr_add_client(CHARDEV(dc), fd) < 0) {
+ g_dbus_method_invocation_return_error(invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Couldn't register FD!");
+ close(fd);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ g_object_set(dc->iface,
+ "owner", g_dbus_method_invocation_get_sender(invocation),
+ NULL);
+
+ qemu_dbus_display1_chardev_complete_register(object, invocation, NULL);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_chr_send_break(
+ DBusChardev *dc,
+ GDBusMethodInvocation *invocation,
+ QemuDBusDisplay1Chardev *object)
+{
+ qemu_chr_be_event(CHARDEV(dc), CHR_EVENT_BREAK);
+
+ qemu_dbus_display1_chardev_complete_send_break(object, invocation);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static void
+dbus_chr_open(Chardev *chr, ChardevBackend *backend,
+ bool *be_opened, Error **errp)
+{
+ ERRP_GUARD();
+
+ DBusChardev *dc = DBUS_CHARDEV(chr);
+ DBusDisplayEvent event = {
+ .type = DBUS_DISPLAY_CHARDEV_OPEN,
+ .chardev = dc,
+ };
+ g_autoptr(ChardevBackend) be = NULL;
+ g_autoptr(QemuOpts) opts = NULL;
+
+ dc->iface = qemu_dbus_display1_chardev_skeleton_new();
+ g_object_set(dc->iface, "name", backend->u.dbus.data->name, NULL);
+ g_object_connect(dc->iface,
+ "swapped-signal::handle-register",
+ dbus_chr_register, dc,
+ "swapped-signal::handle-send-break",
+ dbus_chr_send_break, dc,
+ NULL);
+
+ dbus_display_notify(&event);
+
+ be = g_new0(ChardevBackend, 1);
+ opts = qemu_opts_create(qemu_find_opts("chardev"), NULL, 0, &error_abort);
+ qemu_opt_set(opts, "server", "on", &error_abort);
+ qemu_opt_set(opts, "wait", "off", &error_abort);
+ CHARDEV_CLASS(object_class_by_name(TYPE_CHARDEV_SOCKET))->parse(
+ opts, be, errp);
+ if (*errp) {
+ return;
+ }
+ CHARDEV_CLASS(object_class_by_name(TYPE_CHARDEV_SOCKET))->open(
+ chr, be, be_opened, errp);
+}
+
+static void
+dbus_chr_set_fe_open(Chardev *chr, int fe_open)
+{
+ DBusChardev *dc = DBUS_CHARDEV(chr);
+
+ g_object_set(dc->iface, "feopened", fe_open, NULL);
+}
+
+static void
+dbus_chr_set_echo(Chardev *chr, bool echo)
+{
+ DBusChardev *dc = DBUS_CHARDEV(chr);
+
+ g_object_set(dc->iface, "echo", echo, NULL);
+}
+
+static void
+dbus_chr_be_event(Chardev *chr, QEMUChrEvent event)
+{
+ DBusChardev *dc = DBUS_CHARDEV(chr);
+ DBusChardevClass *klass = DBUS_CHARDEV_GET_CLASS(chr);
+
+ switch (event) {
+ case CHR_EVENT_CLOSED:
+ if (dc->iface) {
+ /* on finalize, iface is set to NULL */
+ g_object_set(dc->iface, "owner", "", NULL);
+ }
+ break;
+ default:
+ break;
+ };
+
+ klass->parent_chr_be_event(chr, event);
+}
+
+static void
+dbus_chr_parse(QemuOpts *opts, ChardevBackend *backend,
+ Error **errp)
+{
+ const char *name = qemu_opt_get(opts, "name");
+ ChardevDBus *dbus;
+
+ if (name == NULL) {
+ error_setg(errp, "chardev: dbus: no name given");
+ return;
+ }
+
+ backend->type = CHARDEV_BACKEND_KIND_DBUS;
+ dbus = backend->u.dbus.data = g_new0(ChardevDBus, 1);
+ qemu_chr_parse_common(opts, qapi_ChardevDBus_base(dbus));
+ dbus->name = g_strdup(name);
+}
+
+static void
+char_dbus_class_init(ObjectClass *oc, void *data)
+{
+ DBusChardevClass *klass = DBUS_CHARDEV_CLASS(oc);
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->parse = dbus_chr_parse;
+ cc->open = dbus_chr_open;
+ cc->chr_set_fe_open = dbus_chr_set_fe_open;
+ cc->chr_set_echo = dbus_chr_set_echo;
+ klass->parent_chr_be_event = cc->chr_be_event;
+ cc->chr_be_event = dbus_chr_be_event;
+}
+
+static void
+char_dbus_finalize(Object *obj)
+{
+ DBusChardev *dc = DBUS_CHARDEV(obj);
+ DBusDisplayEvent event = {
+ .type = DBUS_DISPLAY_CHARDEV_CLOSE,
+ .chardev = dc,
+ };
+
+ dbus_display_notify(&event);
+ g_clear_object(&dc->iface);
+}
+
+static const TypeInfo char_dbus_type_info = {
+ .name = TYPE_CHARDEV_DBUS,
+ .parent = TYPE_CHARDEV_SOCKET,
+ .class_size = sizeof(DBusChardevClass),
+ .instance_size = sizeof(DBusChardev),
+ .instance_finalize = char_dbus_finalize,
+ .class_init = char_dbus_class_init,
+};
+module_obj(TYPE_CHARDEV_DBUS);
+
+static void
+register_types(void)
+{
+ type_register_static(&char_dbus_type_info);
+}
+
+type_init(register_types);
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é Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * 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 OTHER
+ * 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=utf-8"
+
+static void
+dbus_clipboard_complete_request(
+ DBusDisplay *dpy,
+ GDBusMethodInvocation *invocation,
+ QemuClipboardInfo *info,
+ QemuClipboardType type)
+{
+ GVariant *v_data = 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 = info->owner == &dpy->clipboard_peer;
+ const char *mime[QEMU_CLIPBOARD_TYPE__COUNT + 1] = { 0, };
+ DBusClipboardRequest *req;
+ int i = 0;
+
+ if (info->owner == 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 = &dpy->clipboard_request[info->selection];
+ if (req->invocation && info->types[req->type].data) {
+ dbus_clipboard_complete_request(dpy, req->invocation, info, req->type);
+ g_clear_object(&req->invocation);
+ g_source_remove(req->timeout_id);
+ req->timeout_id = 0;
+ return;
+ }
+
+ if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
+ mime[i++] = 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 =
+ container_of(notifier, DBusDisplay, clipboard_peer.notifier);
+ QemuClipboardNotify *notify = 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 = container_of(info->owner, DBusDisplay, clipboard_peer);
+ g_autofree char *mime = NULL;
+ g_autoptr(GVariant) v_data = NULL;
+ g_autoptr(GError) err = NULL;
+ const char *data = NULL;
+ const char *mimes[] = { MIME_TEXT_PLAIN_UTF8, NULL };
+ size_t n;
+
+ if (type != 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 = 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 = 0;
+}
+
+static void
+dbus_clipboard_unregister_proxy(DBusDisplay *dpy)
+{
+ const char *name = NULL;
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS(dpy->clipboard_request); ++i) {
+ dbus_clipboard_request_cancelled(&dpy->clipboard_request[i]);
+ }
+
+ if (!dpy->clipboard_proxy) {
+ return;
+ }
+
+ name = 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 = NULL;
+ const char *name = 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 =
+ 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 = 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, invocation);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_clipboard_check_caller(DBusDisplay *dpy, GDBusMethodInvocation *invocation)
+{
+ 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 = arg_selection;
+ g_autoptr(QemuClipboardInfo) info = NULL;
+
+ if (!dbus_clipboard_check_caller(dpy, invocation)) {
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ if (s >= 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 = 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 = true;
+ }
+ info->serial = arg_serial;
+ info->has_serial = 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, invocation);
+ 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 = arg_selection;
+ QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT;
+ QemuClipboardInfo *info = NULL;
+
+ if (!dbus_clipboard_check_caller(dpy, invocation)) {
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ if (s >= 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 = qemu_clipboard_info(s);
+ if (!info || !info->owner || info->owner == &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 = g_object_ref(invocation);
+ dpy->clipboard_request[s].type = type;
+ dpy->clipboard_request[s].timeout_id =
+ 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 = NULL;
+
+ assert(!dpy->clipboard);
+
+ clipboard = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Clipboard");
+ dpy->clipboard = 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 = "dbus";
+ dpy->clipboard_peer.notifier.notify = dbus_clipboard_notify;
+ dpy->clipboard_peer.request = dbus_clipboard_qemu_request;
+ qemu_clipboard_peer_register(&dpy->clipboard_peer);
+}
diff --git a/ui/dbus-console.c b/ui/dbus-console.c
new file mode 100644
index 0000000000..e062f721d7
--- /dev/null
+++ b/ui/dbus-console.c
@@ -0,0 +1,497 @@
+/*
+ * QEMU DBus display console
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * 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 OTHER
+ * 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 "qapi/error.h"
+#include "ui/input.h"
+#include "ui/kbd-state.h"
+#include "trace.h"
+
+#include <gio/gunixfdlist.h>
+
+#include "dbus.h"
+
+struct _DBusDisplayConsole {
+ GDBusObjectSkeleton parent_instance;
+ DisplayChangeListener dcl;
+
+ DBusDisplay *display;
+ QemuConsole *con;
+ GHashTable *listeners;
+ QemuDBusDisplay1Console *iface;
+
+ QemuDBusDisplay1Keyboard *iface_kbd;
+ QKbdState *kbd;
+
+ QemuDBusDisplay1Mouse *iface_mouse;
+ gboolean last_set;
+ guint last_x;
+ guint last_y;
+ Notifier mouse_mode_notifier;
+};
+
+G_DEFINE_TYPE(DBusDisplayConsole,
+ dbus_display_console,
+ G_TYPE_DBUS_OBJECT_SKELETON)
+
+static void
+dbus_display_console_set_size(DBusDisplayConsole *ddc,
+ uint32_t width, uint32_t height)
+{
+ g_object_set(ddc->iface,
+ "width", width,
+ "height", height,
+ NULL);
+}
+
+static void
+dbus_gfx_switch(DisplayChangeListener *dcl,
+ struct DisplaySurface *new_surface)
+{
+ DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
+
+ dbus_display_console_set_size(ddc,
+ surface_width(new_surface),
+ surface_height(new_surface));
+}
+
+static void
+dbus_gfx_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+}
+
+static void
+dbus_gl_scanout_disable(DisplayChangeListener *dcl)
+{
+}
+
+static void
+dbus_gl_scanout_texture(DisplayChangeListener *dcl,
+ uint32_t tex_id,
+ bool backing_y_0_top,
+ uint32_t backing_width,
+ uint32_t backing_height,
+ uint32_t x, uint32_t y,
+ uint32_t w, uint32_t h)
+{
+ DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
+
+ dbus_display_console_set_size(ddc, w, h);
+}
+
+static void
+dbus_gl_scanout_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf)
+{
+ DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
+
+ dbus_display_console_set_size(ddc,
+ dmabuf->width,
+ dmabuf->height);
+}
+
+static void
+dbus_gl_scanout_update(DisplayChangeListener *dcl,
+ uint32_t x, uint32_t y,
+ uint32_t w, uint32_t h)
+{
+}
+
+static const DisplayChangeListenerOps dbus_console_dcl_ops = {
+ .dpy_name = "dbus-console",
+ .dpy_gfx_switch = dbus_gfx_switch,
+ .dpy_gfx_update = dbus_gfx_update,
+ .dpy_gl_scanout_disable = dbus_gl_scanout_disable,
+ .dpy_gl_scanout_texture = dbus_gl_scanout_texture,
+ .dpy_gl_scanout_dmabuf = dbus_gl_scanout_dmabuf,
+ .dpy_gl_update = dbus_gl_scanout_update,
+};
+
+static void
+dbus_display_console_init(DBusDisplayConsole *object)
+{
+ DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object);
+
+ ddc->listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
+ NULL, g_object_unref);
+ ddc->dcl.ops = &dbus_console_dcl_ops;
+}
+
+static void
+dbus_display_console_dispose(GObject *object)
+{
+ DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object);
+
+ unregister_displaychangelistener(&ddc->dcl);
+ g_clear_object(&ddc->iface_kbd);
+ g_clear_object(&ddc->iface);
+ g_clear_pointer(&ddc->listeners, g_hash_table_unref);
+ g_clear_pointer(&ddc->kbd, qkbd_state_free);
+
+ G_OBJECT_CLASS(dbus_display_console_parent_class)->dispose(object);
+}
+
+static void
+dbus_display_console_class_init(DBusDisplayConsoleClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+ gobject_class->dispose = dbus_display_console_dispose;
+}
+
+static void
+listener_vanished_cb(DBusDisplayListener *listener)
+{
+ DBusDisplayConsole *ddc = dbus_display_listener_get_console(listener);
+ const char *name = dbus_display_listener_get_bus_name(listener);
+
+ trace_dbus_listener_vanished(name);
+
+ g_hash_table_remove(ddc->listeners, name);
+ qkbd_state_lift_all_keys(ddc->kbd);
+}
+
+static gboolean
+dbus_console_set_ui_info(DBusDisplayConsole *ddc,
+ GDBusMethodInvocation *invocation,
+ guint16 arg_width_mm,
+ guint16 arg_height_mm,
+ gint arg_xoff,
+ gint arg_yoff,
+ guint arg_width,
+ guint arg_height)
+{
+ QemuUIInfo info = {
+ .width_mm = arg_width_mm,
+ .height_mm = arg_height_mm,
+ .xoff = arg_xoff,
+ .yoff = arg_yoff,
+ .width = arg_width,
+ .height = arg_height,
+ };
+
+ if (!dpy_ui_info_supported(ddc->con)) {
+ g_dbus_method_invocation_return_error(invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_UNSUPPORTED,
+ "SetUIInfo is not supported");
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ dpy_set_ui_info(ddc->con, &info, false);
+ qemu_dbus_display1_console_complete_set_uiinfo(ddc->iface, invocation);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_console_register_listener(DBusDisplayConsole *ddc,
+ GDBusMethodInvocation *invocation,
+ GUnixFDList *fd_list,
+ GVariant *arg_listener)
+{
+ const char *sender = g_dbus_method_invocation_get_sender(invocation);
+ GDBusConnection *listener_conn;
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GSocket) socket = NULL;
+ g_autoptr(GSocketConnection) socket_conn = NULL;
+ g_autofree char *guid = g_dbus_generate_guid();
+ DBusDisplayListener *listener;
+ int fd;
+
+ if (sender && g_hash_table_contains(ddc->listeners, sender)) {
+ g_dbus_method_invocation_return_error(
+ invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_INVALID,
+ "`%s` is already registered!",
+ sender);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err);
+ if (err) {
+ g_dbus_method_invocation_return_error(
+ invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Couldn't get peer fd: %s", err->message);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ socket = g_socket_new_from_fd(fd, &err);
+ if (err) {
+ g_dbus_method_invocation_return_error(
+ invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Couldn't make a socket: %s", err->message);
+ close(fd);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+ socket_conn = g_socket_connection_factory_create_connection(socket);
+
+ qemu_dbus_display1_console_complete_register_listener(
+ ddc->iface, invocation, NULL);
+
+ listener_conn = g_dbus_connection_new_sync(
+ G_IO_STREAM(socket_conn),
+ guid,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
+ NULL, NULL, &err);
+ if (err) {
+ error_report("Failed to setup peer connection: %s", err->message);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ listener = dbus_display_listener_new(sender, listener_conn, ddc);
+ if (!listener) {
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ g_hash_table_insert(ddc->listeners,
+ (gpointer)dbus_display_listener_get_bus_name(listener),
+ listener);
+ g_object_connect(listener_conn,
+ "swapped-signal::closed", listener_vanished_cb, listener,
+ NULL);
+
+ trace_dbus_registered_listener(sender);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_kbd_press(DBusDisplayConsole *ddc,
+ GDBusMethodInvocation *invocation,
+ guint arg_keycode)
+{
+ QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode);
+
+ trace_dbus_kbd_press(arg_keycode);
+
+ qkbd_state_key_event(ddc->kbd, qcode, true);
+
+ qemu_dbus_display1_keyboard_complete_press(ddc->iface_kbd, invocation);
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_kbd_release(DBusDisplayConsole *ddc,
+ GDBusMethodInvocation *invocation,
+ guint arg_keycode)
+{
+ QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode);
+
+ trace_dbus_kbd_release(arg_keycode);
+
+ qkbd_state_key_event(ddc->kbd, qcode, false);
+
+ qemu_dbus_display1_keyboard_complete_release(ddc->iface_kbd, invocation);
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static void
+dbus_kbd_qemu_leds_updated(void *data, int ledstate)
+{
+ DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(data);
+
+ qemu_dbus_display1_keyboard_set_modifiers(ddc->iface_kbd, ledstate);
+}
+
+static gboolean
+dbus_mouse_rel_motion(DBusDisplayConsole *ddc,
+ GDBusMethodInvocation *invocation,
+ int dx, int dy)
+{
+ trace_dbus_mouse_rel_motion(dx, dy);
+
+ if (qemu_input_is_absolute()) {
+ g_dbus_method_invocation_return_error(
+ invocation, DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_INVALID,
+ "Mouse is not relative");
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ qemu_input_queue_rel(ddc->con, INPUT_AXIS_X, dx);
+ qemu_input_queue_rel(ddc->con, INPUT_AXIS_Y, dy);
+ qemu_input_event_sync();
+
+ qemu_dbus_display1_mouse_complete_rel_motion(ddc->iface_mouse,
+ invocation);
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_mouse_set_pos(DBusDisplayConsole *ddc,
+ GDBusMethodInvocation *invocation,
+ guint x, guint y)
+{
+ int width, height;
+
+ trace_dbus_mouse_set_pos(x, y);
+
+ if (!qemu_input_is_absolute()) {
+ g_dbus_method_invocation_return_error(
+ invocation, DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_INVALID,
+ "Mouse is not absolute");
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ width = qemu_console_get_width(ddc->con, 0);
+ height = qemu_console_get_height(ddc->con, 0);
+ if (x >= width || y >= height) {
+ g_dbus_method_invocation_return_error(
+ invocation, DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_INVALID,
+ "Invalid mouse position");
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+ qemu_input_queue_abs(ddc->con, INPUT_AXIS_X, x, 0, width);
+ qemu_input_queue_abs(ddc->con, INPUT_AXIS_Y, y, 0, height);
+ qemu_input_event_sync();
+
+ qemu_dbus_display1_mouse_complete_set_abs_position(ddc->iface_mouse,
+ invocation);
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_mouse_press(DBusDisplayConsole *ddc,
+ GDBusMethodInvocation *invocation,
+ guint button)
+{
+ trace_dbus_mouse_press(button);
+
+ qemu_input_queue_btn(ddc->con, button, true);
+ qemu_input_event_sync();
+
+ qemu_dbus_display1_mouse_complete_press(ddc->iface_mouse, invocation);
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_mouse_release(DBusDisplayConsole *ddc,
+ GDBusMethodInvocation *invocation,
+ guint button)
+{
+ trace_dbus_mouse_release(button);
+
+ qemu_input_queue_btn(ddc->con, button, false);
+ qemu_input_event_sync();
+
+ qemu_dbus_display1_mouse_complete_release(ddc->iface_mouse, invocation);
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static void
+dbus_mouse_mode_change(Notifier *notify, void *data)
+{
+ DBusDisplayConsole *ddc =
+ container_of(notify, DBusDisplayConsole, mouse_mode_notifier);
+
+ g_object_set(ddc->iface_mouse,
+ "is-absolute", qemu_input_is_absolute(),
+ NULL);
+}
+
+int dbus_display_console_get_index(DBusDisplayConsole *ddc)
+{
+ return qemu_console_get_index(ddc->con);
+}
+
+DBusDisplayConsole *
+dbus_display_console_new(DBusDisplay *display, QemuConsole *con)
+{
+ g_autofree char *path = NULL;
+ g_autofree char *label = NULL;
+ char device_addr[256] = "";
+ DBusDisplayConsole *ddc;
+ int idx;
+
+ assert(display);
+ assert(con);
+
+ label = qemu_console_get_label(con);
+ idx = qemu_console_get_index(con);
+ path = g_strdup_printf(DBUS_DISPLAY1_ROOT "/Console_%d", idx);
+ ddc = g_object_new(DBUS_DISPLAY_TYPE_CONSOLE,
+ "g-object-path", path,
+ NULL);
+ ddc->display = display;
+ ddc->con = con;
+ /* handle errors, and skip non graphics? */
+ qemu_console_fill_device_address(
+ con, device_addr, sizeof(device_addr), NULL);
+
+ ddc->iface = qemu_dbus_display1_console_skeleton_new();
+ g_object_set(ddc->iface,
+ "label", label,
+ "type", qemu_console_is_graphic(con) ? "Graphic" : "Text",
+ "head", qemu_console_get_head(con),
+ "width", qemu_console_get_width(con, 0),
+ "height", qemu_console_get_height(con, 0),
+ "device-address", device_addr,
+ NULL);
+ g_object_connect(ddc->iface,
+ "swapped-signal::handle-register-listener",
+ dbus_console_register_listener, ddc,
+ "swapped-signal::handle-set-uiinfo",
+ dbus_console_set_ui_info, ddc,
+ NULL);
+ g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
+ G_DBUS_INTERFACE_SKELETON(ddc->iface));
+
+ ddc->kbd = qkbd_state_init(con);
+ ddc->iface_kbd = qemu_dbus_display1_keyboard_skeleton_new();
+ qemu_add_led_event_handler(dbus_kbd_qemu_leds_updated, ddc);
+ g_object_connect(ddc->iface_kbd,
+ "swapped-signal::handle-press", dbus_kbd_press, ddc,
+ "swapped-signal::handle-release", dbus_kbd_release, ddc,
+ NULL);
+ g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
+ G_DBUS_INTERFACE_SKELETON(ddc->iface_kbd));
+
+ ddc->iface_mouse = qemu_dbus_display1_mouse_skeleton_new();
+ g_object_connect(ddc->iface_mouse,
+ "swapped-signal::handle-set-abs-position", dbus_mouse_set_pos, ddc,
+ "swapped-signal::handle-rel-motion", dbus_mouse_rel_motion, ddc,
+ "swapped-signal::handle-press", dbus_mouse_press, ddc,
+ "swapped-signal::handle-release", dbus_mouse_release, ddc,
+ NULL);
+ g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
+ G_DBUS_INTERFACE_SKELETON(ddc->iface_mouse));
+
+ register_displaychangelistener(&ddc->dcl);
+ ddc->mouse_mode_notifier.notify = dbus_mouse_mode_change;
+ qemu_add_mouse_mode_change_notifier(&ddc->mouse_mode_notifier);
+
+ return ddc;
+}
diff --git a/ui/dbus-display1.xml b/ui/dbus-display1.xml
new file mode 100644
index 0000000000..c3b2293376
--- /dev/null
+++ b/ui/dbus-display1.xml
@@ -0,0 +1,761 @@
+<?xml version="1.0" encoding="utf-8"?>
+<node>
+ <!--
+ org.qemu.Display1.VM:
+
+ This interface is implemented on ``/org/qemu/Display1/VM``.
+ -->
+ <interface name="org.qemu.Display1.VM">
+ <!--
+ Name:
+
+ The name of the VM.
+ -->
+ <property name="Name" type="s" access="read"/>
+
+ <!--
+ UUID:
+
+ The UUID of the VM.
+ -->
+ <property name="UUID" type="s" access="read"/>
+
+ <!--
+ ConsoleIDs:
+
+ The list of consoles available on ``/org/qemu/Display1/Console_$id``.
+ -->
+ <property name="ConsoleIDs" type="au" access="read"/>
+ </interface>
+
+ <!--
+ org.qemu.Display1.Console:
+
+ This interface is implemented on ``/org/qemu/Display1/Console_$id``. You
+ may discover available consoles through introspection or with the
+ :dbus:prop:`org.qemu.Display1.VM.ConsoleIDs` property.
+
+ A console is attached to a video device head. It may be "Graphic" or
+ "Text" (see :dbus:prop:`Type` and other properties).
+
+ Interactions with a console may be done with
+ :dbus:iface:`org.qemu.Display1.Keyboard` and
+ :dbus:iface:`org.qemu.Display1.Mouse` interfaces when available.
+ -->
+ <interface name="org.qemu.Display1.Console">
+ <!--
+ RegisterListener:
+ @listener: a Unix socket FD, for peer-to-peer D-Bus communication.
+
+ Register a console listener, which will receive display updates, until
+ it is disconnected.
+
+ Multiple listeners may be registered simultaneously.
+
+ The listener is expected to implement the
+ :dbus:iface:`org.qemu.Display1.Listener` interface.
+ -->
+ <method name="RegisterListener">
+ <arg type="h" name="listener" direction="in"/>
+ </method>
+
+ <!--
+ SetUIInfo:
+ @width_mm: the physical display width in millimeters.
+ @height_mm: the physical display height in millimeters.
+ @xoff: horizontal offset, in pixels.
+ @yoff: vertical offset, in pixels.
+ @width: console width, in pixels.
+ @height: console height, in pixels.
+
+ Modify the dimensions and display settings.
+ -->
+ <method name="SetUIInfo">
+ <arg name="width_mm" type="q" direction="in"/>
+ <arg name="height_mm" type="q" direction="in"/>
+ <arg name="xoff" type="i" direction="in"/>
+ <arg name="yoff" type="i" direction="in"/>
+ <arg name="width" type="u" direction="in"/>
+ <arg name="height" type="u" direction="in"/>
+ </method>
+
+ <!--
+ Label:
+
+ A user-friendly name for the console (for ex: "VGA").
+ -->
+ <property name="Label" type="s" access="read"/>
+
+ <!--
+ Head:
+
+ Graphical device head number.
+ -->
+ <property name="Head" type="u" access="read"/>
+
+ <!--
+ Type:
+
+ Console type ("Graphic" or "Text").
+ -->
+ <property name="Type" type="s" access="read"/>
+
+ <!--
+ Width:
+
+ Console width, in pixels.
+ -->
+ <property name="Width" type="u" access="read"/>
+
+ <!--
+ Height:
+
+ Console height, in pixels.
+ -->
+ <property name="Height" type="u" access="read"/>
+
+ <!--
+ DeviceAddress:
+
+ The device address (ex: "pci/0000/02.0").
+ -->
+ <property name="DeviceAddress" type="s" access="read"/>
+ </interface>
+
+ <!--
+ org.qemu.Display1.Keyboard:
+
+ This interface in implemented on ``/org/qemu/Display1/Console_$id`` (see
+ :dbus:iface:`~org.qemu.Display1.Console`).
+ -->
+ <interface name="org.qemu.Display1.Keyboard">
+ <!--
+ Press:
+ @keycode: QEMU key number (xtkbd + special re-encoding of high bit)
+
+ Send a key press event.
+ -->
+ <method name="Press">
+ <arg type="u" name="keycode" direction="in"/>
+ </method>
+
+ <!--
+ Release:
+ @keycode: QEMU key number (xtkbd + special re-encoding of high bit)
+
+ Send a key release event.
+ -->
+ <method name="Release">
+ <arg type="u" name="keycode" direction="in"/>
+ </method>
+
+ <!--
+ Modifiers:
+
+ The active keyboard modifiers::
+
+ Scroll = 1 << 0
+ Num = 1 << 1
+ Caps = 1 << 2
+ -->
+ <property name="Modifiers" type="u" access="read"/>
+ </interface>
+
+ <!--
+ org.qemu.Display1.Mouse:
+
+ This interface in implemented on ``/org/qemu/Display1/Console_$id`` (see
+ :dbus:iface:`~org.qemu.Display1.Console` documentation).
+
+ .. _dbus-button-values:
+
+ **Button values**::
+
+ Left = 0
+ Middle = 1
+ Right = 2
+ Wheel-up = 3
+ Wheel-down = 4
+ Side = 5
+ Extra = 6
+ -->
+ <interface name="org.qemu.Display1.Mouse">
+ <!--
+ Press:
+ @button: :ref:`button value<dbus-button-values>`.
+
+ Send a mouse button press event.
+ -->
+ <method name="Press">
+ <arg type="u" name="button" direction="in"/>
+ </method>
+
+ <!--
+ Release:
+ @button: :ref:`button value<dbus-button-values>`.
+
+ Send a mouse button release event.
+ -->
+ <method name="Release">
+ <arg type="u" name="button" direction="in"/>
+ </method>
+
+ <!--
+ SetAbsPosition:
+ @x: X position, in pixels.
+ @y: Y position, in pixels.
+
+ Set the mouse pointer position.
+
+ Returns an error if not :dbus:prop:`IsAbsolute`.
+ -->
+ <method name="SetAbsPosition">
+ <arg type="u" name="x" direction="in"/>
+ <arg type="u" name="y" direction="in"/>
+ </method>
+
+ <!--
+ RelMotion:
+ @dx: X-delta, in pixels.
+ @dy: Y-delta, in pixels.
+
+ Move the mouse pointer position, relative to the current position.
+
+ Returns an error if :dbus:prop:`IsAbsolute`.
+ -->
+ <method name="RelMotion">
+ <arg type="i" name="dx" direction="in"/>
+ <arg type="i" name="dy" direction="in"/>
+ </method>
+
+ <!--
+ IsAbsolute:
+
+ Whether the mouse is using absolute movements.
+ -->
+ <property name="IsAbsolute" type="b" access="read"/>
+ </interface>
+
+ <!--
+ org.qemu.Display1.Listener:
+
+ This client-side interface must be available on
+ ``/org/qemu/Display1/Listener`` when registering the peer-to-peer
+ connection with :dbus:meth:`~org.qemu.Display1.Console.Register`.
+ -->
+ <interface name="org.qemu.Display1.Listener">
+ <!--
+ Scanout:
+ @width: display width, in pixels.
+ @height: display height, in pixels.
+ @stride: data stride, in bytes.
+ @pixman_format: image format (ex: ``PIXMAN_X8R8G8B8``).
+ @data: image data.
+
+ Resize and update the display content.
+
+ The data to transfer for the display update may be large. The preferred
+ scanout method is :dbus:meth:`ScanoutDMABUF`, used whenever possible.
+ -->
+ <method name="Scanout">
+ <arg type="u" name="width" direction="in"/>
+ <arg type="u" name="height" direction="in"/>
+ <arg type="u" name="stride" direction="in"/>
+ <arg type="u" name="pixman_format" direction="in"/>
+ <arg type="ay" name="data" direction="in">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </arg>
+ </method>
+
+ <!--
+ Update:
+ @x: X update position, in pixels.
+ @y: Y update position, in pixels.
+ @width: update width, in pixels.
+ @height: update height, in pixels.
+ @stride: data stride, in bytes.
+ @pixman_format: image format (ex: ``PIXMAN_X8R8G8B8``).
+ @data: display image data.
+
+ Update the display content.
+
+ This method is only called after a :dbus:meth:`Scanout` call.
+ -->
+ <method name="Update">
+ <arg type="i" name="x" direction="in"/>
+ <arg type="i" name="y" direction="in"/>
+ <arg type="i" name="width" direction="in"/>
+ <arg type="i" name="height" direction="in"/>
+ <arg type="u" name="stride" direction="in"/>
+ <arg type="u" name="pixman_format" direction="in"/>
+ <arg type="ay" name="data" direction="in">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </arg>
+ </method>
+
+ <!--
+ ScanoutDMABUF:
+ @dmabuf: the DMABUF file descriptor.
+ @width: display width, in pixels.
+ @height: display height, in pixels.
+ @stride: stride, in bytes.
+ @fourcc: DMABUF fourcc.
+ @modifier: DMABUF modifier.
+ @y0_top: whether Y position 0 is the top or not.
+
+ Resize and update the display content with a DMABUF.
+ -->
+ <method name="ScanoutDMABUF">
+ <arg type="h" name="dmabuf" direction="in"/>
+ <arg type="u" name="width" direction="in"/>
+ <arg type="u" name="height" direction="in"/>
+ <arg type="u" name="stride" direction="in"/>
+ <arg type="u" name="fourcc" direction="in"/>
+ <!-- xywh? -->
+ <arg type="t" name="modifier" direction="in"/>
+ <arg type="b" name="y0_top" direction="in"/>
+ </method>
+
+ <!--
+ UpdateDMABUF:
+ @x: the X update position, in pixels.
+ @y: the Y update position, in pixels.
+ @width: the update width, in pixels.
+ @height: the update height, in pixels.
+
+ Update the display content with the current DMABUF and the given region.
+ -->
+ <method name="UpdateDMABUF">
+ <arg type="i" name="x" direction="in"/>
+ <arg type="i" name="y" direction="in"/>
+ <arg type="i" name="width" direction="in"/>
+ <arg type="i" name="height" direction="in"/>
+ </method>
+
+ <!--
+ Disable:
+
+ Disable the display (turn it off).
+ -->
+ <method name="Disable">
+ </method>
+
+ <!--
+ MouseSet:
+ @x: X mouse position, in pixels.
+ @y: Y mouse position, in pixels.
+ @on: whether the mouse is visible or not.
+
+ Set the mouse position and visibility.
+ -->
+ <method name="MouseSet">
+ <arg type="i" name="x" direction="in"/>
+ <arg type="i" name="y" direction="in"/>
+ <arg type="i" name="on" direction="in"/>
+ </method>
+
+ <!--
+ CursorDefine:
+ @width: cursor width, in pixels.
+ @height: cursor height, in pixels.
+ @hot_x: hot-spot X position, in pixels.
+ @hot_y: hot-spot Y position, in pixels.
+ @data: the cursor data.
+
+ Set the mouse cursor shape and hot-spot. The "data" must be ARGB, 32-bit
+ per pixel.
+ -->
+ <method name="CursorDefine">
+ <arg type="i" name="width" direction="in"/>
+ <arg type="i" name="height" direction="in"/>
+ <arg type="i" name="hot_x" direction="in"/>
+ <arg type="i" name="hot_y" direction="in"/>
+ <arg type="ay" name="data" direction="in">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </arg>
+ </method>
+ </interface>
+
+ <!--
+ org.qemu.Display1.Clipboard:
+
+ This interface must be implemented by both the client and the server on
+ ``/org/qemu/Display1/Clipboard`` to support clipboard sharing between
+ the client and the guest.
+
+ Once :dbus:meth:`Register`'ed, method calls may be sent and received in both
+ directions. Unregistered callers will get error replies.
+
+ .. _dbus-clipboard-selection:
+
+ **Selection values**::
+
+ Clipboard = 0
+ Primary = 1
+ Secondary = 2
+
+ .. _dbus-clipboard-serial:
+
+ **Serial counter**
+
+ To solve potential clipboard races, clipboard grabs have an associated
+ serial counter. It is set to 0 on registration, and incremented by 1 for
+ each grab. The peer with the highest serial is the clipboard grab owner.
+
+ When a grab with a lower serial is received, it should be discarded.
+
+ When a grab is attempted with the same serial number as the current grab,
+ the one coming from the client should have higher priority, and the client
+ should gain clipboard grab ownership.
+ -->
+ <interface name="org.qemu.Display1.Clipboard">
+ <!--
+ Register:
+
+ Register a clipboard session and reinitialize the serial counter.
+
+ The client must register itself, and is granted an exclusive
+ access for handling the clipboard.
+
+ The server can reinitialize the session as well (to reset the counter).
+ -->
+ <method name="Register"/>
+
+ <!--
+ Unregister:
+
+ Unregister the clipboard session.
+ -->
+ <method name="Unregister"/>
+ <!--
+ Grab:
+ @selection: a :ref:`selection value<dbus-clipboard-selection>`.
+ @serial: the current grab :ref:`serial<dbus-clipboard-serial>`.
+ @mimes: the list of available content MIME types.
+
+ Grab the clipboard, claiming current clipboard content.
+ -->
+ <method name="Grab">
+ <arg type="u" name="selection"/>
+ <arg type="u" name="serial"/>
+ <arg type="as" name="mimes"/>
+ </method>
+
+ <!--
+ Release:
+ @selection: a :ref:`selection value<dbus-clipboard-selection>`.
+
+ Release the clipboard (does nothing if not the current owner).
+ -->
+ <method name="Release">
+ <arg type="u" name="selection"/>
+ </method>
+
+ <!--
+ Request:
+ @selection: a :ref:`selection value<dbus-clipboard-selection>`
+ @mimes: requested MIME types (by order of preference).
+ @reply_mime: the returned data MIME type.
+ @data: the clipboard data.
+
+ Request the clipboard content.
+
+ Return an error if the clipboard is empty, or the requested MIME types
+ are unavailable.
+ -->
+ <method name="Request">
+ <arg type="u" name="selection"/>
+ <arg type="as" name="mimes"/>
+ <arg type="s" name="reply_mime" direction="out"/>
+ <arg type="ay" name="data" direction="out">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </arg>
+ </method>
+ </interface>
+
+ <!--
+ org.qemu.Display1.Audio:
+
+ Audio backend may be available on ``/org/qemu/Display1/Audio``.
+ -->
+ <interface name="org.qemu.Display1.Audio">
+ <!--
+ RegisterOutListener:
+ @listener: a Unix socket FD, for peer-to-peer D-Bus communication.
+
+ Register an audio backend playback handler.
+
+ Multiple listeners may be registered simultaneously.
+
+ The listener is expected to implement the
+ :dbus:iface:`org.qemu.Display1.AudioOutListener` interface.
+ -->
+ <method name="RegisterOutListener">
+ <arg type="h" name="listener" direction="in"/>
+ </method>
+
+ <!--
+ RegisterInListener:
+ @listener: a Unix socket FD, for peer-to-peer D-Bus communication.
+
+ Register an audio backend record handler.
+
+ Multiple listeners may be registered simultaneously.
+
+ The listener is expected to implement the
+ :dbus:iface:`org.qemu.Display1.AudioInListener` interface.
+ -->
+ <method name="RegisterInListener">
+ <arg type="h" name="listener" direction="in"/>
+ </method>
+ </interface>
+
+ <!--
+ org.qemu.Display1.AudioOutListener:
+
+ This client-side interface must be available on
+ ``/org/qemu/Display1/AudioOutListener`` when registering the peer-to-peer
+ connection with :dbus:meth:`~org.qemu.Display1.Audio.RegisterOutListener`.
+ -->
+ <interface name="org.qemu.Display1.AudioOutListener">
+ <!--
+ Init:
+ @id: the stream ID.
+ @bits: PCM bits per sample.
+ @is_signed: whether the PCM data is signed.
+ @is_float: PCM floating point format.
+ @freq: the PCM frequency in Hz.
+ @nchannels: the number of channels.
+ @bytes_per_frame: the bytes per frame.
+ @bytes_per_second: the bytes per second.
+ @be: whether using big-endian format.
+
+ Initializes a PCM playback stream.
+ -->
+ <method name="Init">
+ <arg name="id" type="t" direction="in"/>
+ <arg name="bits" type="y" direction="in"/>
+ <arg name="is_signed" type="b" direction="in"/>
+ <arg name="is_float" type="b" direction="in"/>
+ <arg name="freq" type="u" direction="in"/>
+ <arg name="nchannels" type="y" direction="in"/>
+ <arg name="bytes_per_frame" type="u" direction="in"/>
+ <arg name="bytes_per_second" type="u" direction="in"/>
+ <arg name="be" type="b" direction="in"/>
+ </method>
+
+ <!--
+ Fini:
+ @id: the stream ID.
+
+ Finish & close a playback stream.
+ -->
+ <method name="Fini">
+ <arg name="id" type="t" direction="in"/>
+ </method>
+
+ <!--
+ SetEnabled:
+ @id: the stream ID.
+
+ Resume or suspend the playback stream.
+ -->
+ <method name="SetEnabled">
+ <arg name="id" type="t" direction="in"/>
+ <arg name="enabled" type="b" direction="in"/>
+ </method>
+
+ <!--
+ SetVolume:
+ @id: the stream ID.
+ @mute: whether the stream is muted.
+ @volume: the volume per-channel.
+
+ Set the stream volume and mute state (volume without unit, 0-255).
+ -->
+ <method name="SetVolume">
+ <arg name="id" type="t" direction="in"/>
+ <arg name="mute" type="b" direction="in"/>
+ <arg name="volume" type="ay" direction="in">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </arg>
+ </method>
+
+ <!--
+ Write:
+ @id: the stream ID.
+ @data: the PCM data.
+
+ PCM stream to play.
+ -->
+ <method name="Write">
+ <arg name="id" type="t" direction="in"/>
+ <arg type="ay" name="data" direction="in">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </arg>
+ </method>
+ </interface>
+
+ <!--
+ org.qemu.Display1.AudioInListener:
+
+ This client-side interface must be available on
+ ``/org/qemu/Display1/AudioInListener`` when registering the peer-to-peer
+ connection with :dbus:meth:`~org.qemu.Display1.Audio.RegisterInListener`.
+ -->
+ <interface name="org.qemu.Display1.AudioInListener">
+ <!--
+ Init:
+ @id: the stream ID.
+ @bits: PCM bits per sample.
+ @is_signed: whether the PCM data is signed.
+ @is_float: PCM floating point format.
+ @freq: the PCM frequency in Hz.
+ @nchannels: the number of channels.
+ @bytes_per_frame: the bytes per frame.
+ @bytes_per_second: the bytes per second.
+ @be: whether using big-endian format.
+
+ Initializes a PCM record stream.
+ -->
+ <method name="Init">
+ <arg name="id" type="t" direction="in"/>
+ <arg name="bits" type="y" direction="in"/>
+ <arg name="is_signed" type="b" direction="in"/>
+ <arg name="is_float" type="b" direction="in"/>
+ <arg name="freq" type="u" direction="in"/>
+ <arg name="nchannels" type="y" direction="in"/>
+ <arg name="bytes_per_frame" type="u" direction="in"/>
+ <arg name="bytes_per_second" type="u" direction="in"/>
+ <arg name="be" type="b" direction="in"/>
+ </method>
+
+ <!--
+ Fini:
+ @id: the stream ID.
+
+ Finish & close a record stream.
+ -->
+ <method name="Fini">
+ <arg name="id" type="t" direction="in"/>
+ </method>
+
+ <!--
+ SetEnabled:
+ @id: the stream ID.
+
+ Resume or suspend the record stream.
+ -->
+ <method name="SetEnabled">
+ <arg name="id" type="t" direction="in"/>
+ <arg name="enabled" type="b" direction="in"/>
+ </method>
+
+ <!--
+ SetVolume:
+ @id: the stream ID.
+ @mute: whether the stream is muted.
+ @volume: the volume per-channel.
+
+ Set the stream volume and mute state (volume without unit, 0-255).
+ -->
+ <method name="SetVolume">
+ <arg name="id" type="t" direction="in"/>
+ <arg name="mute" type="b" direction="in"/>
+ <arg name="volume" type="ay" direction="in">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </arg>
+ </method>
+
+ <!--
+ Read:
+ @id: the stream ID.
+ @size: the amount to read, in bytes.
+ @data: the recorded data (which may be less than requested).
+
+ Read "size" bytes from the record stream.
+ -->
+ <method name="Read">
+ <arg name="id" type="t" direction="in"/>
+ <arg name="size" type="t" direction="in"/>
+ <arg type="ay" name="data" direction="out">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </arg>
+ </method>
+ </interface>
+
+ <!--
+ org.qemu.Display1.Chardev:
+
+ Character devices may be available on ``/org/qemu/Display1/Chardev_$id``.
+
+ They may be used for different kind of streams, which are identified via
+ their FQDN :dbus:prop:`Name`.
+
+ .. _dbus-chardev-fqdn:
+
+ Here are some known reserved kind names (the ``org.qemu`` prefix is
+ reserved by QEMU):
+
+ org.qemu.console.serial.0
+ A serial console stream.
+
+ org.qemu.monitor.hmp.0
+ A QEMU HMP human monitor.
+
+ org.qemu.monitor.qmp.0
+ A QEMU QMP monitor.
+
+ org.qemu.usbredir
+ A usbredir stream.
+ -->
+ <interface name="org.qemu.Display1.Chardev">
+ <!--
+ Register:
+ @stream: a Unix FD to redirect the stream to.
+
+ Register a file-descriptor for the stream handling.
+
+ The current handler, if any, will be replaced.
+ -->
+ <method name="Register">
+ <arg type="h" name="stream" direction="in"/>
+ </method>
+
+ <!--
+ SendBreak:
+
+ Send a break event to the character device.
+ -->
+ <method name="SendBreak"/>
+
+ <!--
+ Name:
+
+ The FQDN name to identify the kind of stream. See :ref:`reserved
+ names<dbus-chardev-fqdn>`.
+ -->
+ <property name="Name" type="s" access="read"/>
+
+ <!--
+ FEOpened:
+
+ Whether the front-end side is opened.
+ -->
+ <property name="FEOpened" type="b" access="read"/>
+
+ <!--
+ Echo:
+
+ Whether the input should be echo'ed (for serial streams).
+ -->
+ <property name="Echo" type="b" access="read"/>
+
+ <!--
+ Owner:
+
+ The D-Bus unique name of the registered handler.
+ -->
+ <property name="Owner" type="s" access="read"/>
+ </interface>
+</node>
diff --git a/ui/dbus-error.c b/ui/dbus-error.c
new file mode 100644
index 0000000000..85a9194d57
--- /dev/null
+++ b/ui/dbus-error.c
@@ -0,0 +1,48 @@
+/*
+ * QEMU DBus display errors
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * 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 OTHER
+ * 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 "dbus.h"
+
+static const GDBusErrorEntry dbus_display_error_entries[] = {
+ { DBUS_DISPLAY_ERROR_FAILED, "org.qemu.Display1.Error.Failed" },
+ { DBUS_DISPLAY_ERROR_INVALID, "org.qemu.Display1.Error.Invalid" },
+ { DBUS_DISPLAY_ERROR_UNSUPPORTED, "org.qemu.Display1.Error.Unsupported" },
+};
+
+G_STATIC_ASSERT(G_N_ELEMENTS(dbus_display_error_entries) ==
+ DBUS_DISPLAY_N_ERRORS);
+
+GQuark
+dbus_display_error_quark(void)
+{
+ static gsize quark;
+
+ g_dbus_error_register_error_domain(
+ "dbus-display-error-quark",
+ &quark,
+ dbus_display_error_entries,
+ G_N_ELEMENTS(dbus_display_error_entries));
+
+ return (GQuark)quark;
+}
diff --git a/ui/dbus-listener.c b/ui/dbus-listener.c
new file mode 100644
index 0000000000..81c119b13a
--- /dev/null
+++ b/ui/dbus-listener.c
@@ -0,0 +1,486 @@
+/*
+ * QEMU DBus display console
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * 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 OTHER
+ * 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 "sysemu/sysemu.h"
+#include "dbus.h"
+#include <gio/gunixfdlist.h>
+
+#include "ui/shader.h"
+#include "ui/egl-helpers.h"
+#include "ui/egl-context.h"
+#include "trace.h"
+
+struct _DBusDisplayListener {
+ GObject parent;
+
+ char *bus_name;
+ DBusDisplayConsole *console;
+ GDBusConnection *conn;
+
+ QemuDBusDisplay1Listener *proxy;
+
+ DisplayChangeListener dcl;
+ DisplaySurface *ds;
+ QemuGLShader *gls;
+ int gl_updates;
+};
+
+G_DEFINE_TYPE(DBusDisplayListener, dbus_display_listener, G_TYPE_OBJECT)
+
+static void dbus_update_gl_cb(GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) err = NULL;
+ DBusDisplayListener *ddl = user_data;
+
+ if (!qemu_dbus_display1_listener_call_update_dmabuf_finish(ddl->proxy,
+ res, &err)) {
+ error_report("Failed to call update: %s", err->message);
+ }
+
+ graphic_hw_gl_block(ddl->dcl.con, false);
+ g_object_unref(ddl);
+}
+
+static void dbus_call_update_gl(DBusDisplayListener *ddl,
+ int x, int y, int w, int h)
+{
+ graphic_hw_gl_block(ddl->dcl.con, true);
+ glFlush();
+ qemu_dbus_display1_listener_call_update_dmabuf(ddl->proxy,
+ x, y, w, h,
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_DEFAULT_TIMEOUT, NULL,
+ dbus_update_gl_cb,
+ g_object_ref(ddl));
+}
+
+static void dbus_scanout_disable(DisplayChangeListener *dcl)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+ ddl->ds = NULL;
+ qemu_dbus_display1_listener_call_disable(
+ ddl->proxy, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void dbus_scanout_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GUnixFDList) fd_list = NULL;
+
+ fd_list = g_unix_fd_list_new();
+ if (g_unix_fd_list_append(fd_list, dmabuf->fd, &err) != 0) {
+ error_report("Failed to setup dmabuf fdlist: %s", err->message);
+ return;
+ }
+
+ qemu_dbus_display1_listener_call_scanout_dmabuf(
+ ddl->proxy,
+ g_variant_new_handle(0),
+ dmabuf->width,
+ dmabuf->height,
+ dmabuf->stride,
+ dmabuf->fourcc,
+ dmabuf->modifier,
+ dmabuf->y0_top,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ fd_list,
+ NULL, NULL, NULL);
+}
+
+static void dbus_scanout_texture(DisplayChangeListener *dcl,
+ uint32_t tex_id,
+ bool backing_y_0_top,
+ uint32_t backing_width,
+ uint32_t backing_height,
+ uint32_t x, uint32_t y,
+ uint32_t w, uint32_t h)
+{
+ QemuDmaBuf dmabuf = {
+ .width = backing_width,
+ .height = backing_height,
+ .y0_top = backing_y_0_top,
+ };
+
+ assert(tex_id);
+ dmabuf.fd = egl_get_fd_for_texture(
+ tex_id, (EGLint *)&dmabuf.stride,
+ (EGLint *)&dmabuf.fourcc,
+ &dmabuf.modifier);
+ if (dmabuf.fd < 0) {
+ error_report("%s: failed to get fd for texture", __func__);
+ return;
+ }
+
+ dbus_scanout_dmabuf(dcl, &dmabuf);
+ close(dmabuf.fd);
+}
+
+static void dbus_cursor_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf, bool have_hot,
+ uint32_t hot_x, uint32_t hot_y)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+ DisplaySurface *ds;
+ GVariant *v_data = NULL;
+ egl_fb cursor_fb;
+
+ if (!dmabuf) {
+ qemu_dbus_display1_listener_call_mouse_set(
+ ddl->proxy, 0, 0, false,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ return;
+ }
+
+ egl_dmabuf_import_texture(dmabuf);
+ if (!dmabuf->texture) {
+ return;
+ }
+ egl_fb_setup_for_tex(&cursor_fb, dmabuf->width, dmabuf->height,
+ dmabuf->texture, false);
+ ds = qemu_create_displaysurface(dmabuf->width, dmabuf->height);
+ egl_fb_read(ds, &cursor_fb);
+
+ v_data = g_variant_new_from_data(
+ G_VARIANT_TYPE("ay"),
+ surface_data(ds),
+ surface_width(ds) * surface_height(ds) * 4,
+ TRUE,
+ (GDestroyNotify)qemu_free_displaysurface,
+ ds);
+ qemu_dbus_display1_listener_call_cursor_define(
+ ddl->proxy,
+ surface_width(ds),
+ surface_height(ds),
+ hot_x,
+ hot_y,
+ v_data,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ NULL,
+ NULL);
+}
+
+static void dbus_cursor_position(DisplayChangeListener *dcl,
+ uint32_t pos_x, uint32_t pos_y)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+ qemu_dbus_display1_listener_call_mouse_set(
+ ddl->proxy, pos_x, pos_y, true,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void dbus_release_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf)
+{
+ dbus_scanout_disable(dcl);
+}
+
+static void dbus_scanout_update(DisplayChangeListener *dcl,
+ uint32_t x, uint32_t y,
+ uint32_t w, uint32_t h)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+ dbus_call_update_gl(ddl, x, y, w, h);
+}
+
+static void dbus_gl_refresh(DisplayChangeListener *dcl)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+ graphic_hw_update(dcl->con);
+
+ if (!ddl->ds || qemu_console_is_gl_blocked(ddl->dcl.con)) {
+ return;
+ }
+
+ if (ddl->gl_updates) {
+ dbus_call_update_gl(ddl, 0, 0,
+ surface_width(ddl->ds), surface_height(ddl->ds));
+ ddl->gl_updates = 0;
+ }
+}
+
+static void dbus_refresh(DisplayChangeListener *dcl)
+{
+ graphic_hw_update(dcl->con);
+}
+
+static void dbus_gl_gfx_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+ if (ddl->ds) {
+ surface_gl_update_texture(ddl->gls, ddl->ds, x, y, w, h);
+ }
+
+ ddl->gl_updates++;
+}
+
+static void dbus_gfx_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+ pixman_image_t *img;
+ GVariant *v_data;
+ size_t stride;
+
+ assert(ddl->ds);
+ stride = w * DIV_ROUND_UP(PIXMAN_FORMAT_BPP(surface_format(ddl->ds)), 8);
+
+ trace_dbus_update(x, y, w, h);
+
+ /* make a copy, since gvariant only handles linear data */
+ img = pixman_image_create_bits(surface_format(ddl->ds),
+ w, h, NULL, stride);
+ pixman_image_composite(PIXMAN_OP_SRC, ddl->ds->image, NULL, img,
+ x, y, 0, 0, 0, 0, w, h);
+
+ v_data = g_variant_new_from_data(
+ G_VARIANT_TYPE("ay"),
+ pixman_image_get_data(img),
+ pixman_image_get_stride(img) * h,
+ TRUE,
+ (GDestroyNotify)pixman_image_unref,
+ img);
+ qemu_dbus_display1_listener_call_update(ddl->proxy,
+ x, y, w, h, pixman_image_get_stride(img), pixman_image_get_format(img),
+ v_data,
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL);
+}
+
+static void dbus_gl_gfx_switch(DisplayChangeListener *dcl,
+ struct DisplaySurface *new_surface)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+ if (ddl->ds) {
+ surface_gl_destroy_texture(ddl->gls, ddl->ds);
+ }
+ ddl->ds = new_surface;
+ if (ddl->ds) {
+ int width = surface_width(ddl->ds);
+ int height = surface_height(ddl->ds);
+
+ surface_gl_create_texture(ddl->gls, ddl->ds);
+ /* TODO: lazy send dmabuf (there are unnecessary sent otherwise) */
+ dbus_scanout_texture(&ddl->dcl, ddl->ds->texture, false,
+ width, height, 0, 0, width, height);
+ }
+}
+
+static void dbus_gfx_switch(DisplayChangeListener *dcl,
+ struct DisplaySurface *new_surface)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+ GVariant *v_data = NULL;
+
+ ddl->ds = new_surface;
+ if (!ddl->ds) {
+ /* why not call disable instead? */
+ return;
+ }
+
+ v_data = g_variant_new_from_data(
+ G_VARIANT_TYPE("ay"),
+ surface_data(ddl->ds),
+ surface_stride(ddl->ds) * surface_height(ddl->ds),
+ TRUE,
+ (GDestroyNotify)pixman_image_unref,
+ pixman_image_ref(ddl->ds->image));
+ qemu_dbus_display1_listener_call_scanout(ddl->proxy,
+ surface_width(ddl->ds),
+ surface_height(ddl->ds),
+ surface_stride(ddl->ds),
+ surface_format(ddl->ds),
+ v_data,
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL);
+}
+
+static void dbus_mouse_set(DisplayChangeListener *dcl,
+ int x, int y, int on)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+ qemu_dbus_display1_listener_call_mouse_set(
+ ddl->proxy, x, y, on, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void dbus_cursor_define(DisplayChangeListener *dcl,
+ QEMUCursor *c)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+ GVariant *v_data = NULL;
+
+ cursor_get(c);
+ v_data = g_variant_new_from_data(
+ G_VARIANT_TYPE("ay"),
+ c->data,
+ c->width * c->height * 4,
+ TRUE,
+ (GDestroyNotify)cursor_put,
+ c);
+
+ qemu_dbus_display1_listener_call_cursor_define(
+ ddl->proxy,
+ c->width,
+ c->height,
+ c->hot_x,
+ c->hot_y,
+ v_data,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ NULL,
+ NULL);
+}
+
+const DisplayChangeListenerOps dbus_gl_dcl_ops = {
+ .dpy_name = "dbus-gl",
+ .dpy_gfx_update = dbus_gl_gfx_update,
+ .dpy_gfx_switch = dbus_gl_gfx_switch,
+ .dpy_gfx_check_format = console_gl_check_format,
+ .dpy_refresh = dbus_gl_refresh,
+ .dpy_mouse_set = dbus_mouse_set,
+ .dpy_cursor_define = dbus_cursor_define,
+
+ .dpy_gl_scanout_disable = dbus_scanout_disable,
+ .dpy_gl_scanout_texture = dbus_scanout_texture,
+ .dpy_gl_scanout_dmabuf = dbus_scanout_dmabuf,
+ .dpy_gl_cursor_dmabuf = dbus_cursor_dmabuf,
+ .dpy_gl_cursor_position = dbus_cursor_position,
+ .dpy_gl_release_dmabuf = dbus_release_dmabuf,
+ .dpy_gl_update = dbus_scanout_update,
+};
+
+const DisplayChangeListenerOps dbus_dcl_ops = {
+ .dpy_name = "dbus",
+ .dpy_gfx_update = dbus_gfx_update,
+ .dpy_gfx_switch = dbus_gfx_switch,
+ .dpy_refresh = dbus_refresh,
+ .dpy_mouse_set = dbus_mouse_set,
+ .dpy_cursor_define = dbus_cursor_define,
+};
+
+static void
+dbus_display_listener_dispose(GObject *object)
+{
+ DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object);
+
+ unregister_displaychangelistener(&ddl->dcl);
+ g_clear_object(&ddl->conn);
+ g_clear_pointer(&ddl->bus_name, g_free);
+ g_clear_object(&ddl->proxy);
+ g_clear_pointer(&ddl->gls, qemu_gl_fini_shader);
+
+ G_OBJECT_CLASS(dbus_display_listener_parent_class)->dispose(object);
+}
+
+static void
+dbus_display_listener_constructed(GObject *object)
+{
+ DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object);
+
+ if (display_opengl) {
+ ddl->gls = qemu_gl_init_shader();
+ ddl->dcl.ops = &dbus_gl_dcl_ops;
+ } else {
+ ddl->dcl.ops = &dbus_dcl_ops;
+ }
+
+ G_OBJECT_CLASS(dbus_display_listener_parent_class)->constructed(object);
+}
+
+static void
+dbus_display_listener_class_init(DBusDisplayListenerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->dispose = dbus_display_listener_dispose;
+ object_class->constructed = dbus_display_listener_constructed;
+}
+
+static void
+dbus_display_listener_init(DBusDisplayListener *ddl)
+{
+}
+
+const char *
+dbus_display_listener_get_bus_name(DBusDisplayListener *ddl)
+{
+ return ddl->bus_name ?: "p2p";
+}
+
+DBusDisplayConsole *
+dbus_display_listener_get_console(DBusDisplayListener *ddl)
+{
+ return ddl->console;
+}
+
+DBusDisplayListener *
+dbus_display_listener_new(const char *bus_name,
+ GDBusConnection *conn,
+ DBusDisplayConsole *console)
+{
+ DBusDisplayListener *ddl;
+ QemuConsole *con;
+ g_autoptr(GError) err = NULL;
+
+ ddl = g_object_new(DBUS_DISPLAY_TYPE_LISTENER, NULL);
+ ddl->proxy =
+ qemu_dbus_display1_listener_proxy_new_sync(conn,
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+ NULL,
+ "/org/qemu/Display1/Listener",
+ NULL,
+ &err);
+ if (!ddl->proxy) {
+ error_report("Failed to setup proxy: %s", err->message);
+ g_object_unref(conn);
+ g_object_unref(ddl);
+ return NULL;
+ }
+
+ ddl->bus_name = g_strdup(bus_name);
+ ddl->conn = conn;
+ ddl->console = console;
+
+ con = qemu_console_lookup_by_index(dbus_display_console_get_index(console));
+ assert(con);
+ ddl->dcl.con = con;
+ register_displaychangelistener(&ddl->dcl);
+
+ return ddl;
+}
diff --git a/ui/dbus-module.c b/ui/dbus-module.c
new file mode 100644
index 0000000000..c8771fe48c
--- /dev/null
+++ b/ui/dbus-module.c
@@ -0,0 +1,35 @@
+/*
+ * D-Bus module support.
+ *
+ * Copyright (C) 2021 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "ui/dbus-module.h"
+
+int using_dbus_display;
+
+static bool
+qemu_dbus_display_add_client(int csock, Error **errp)
+{
+ error_setg(errp, "D-Bus display isn't enabled");
+ return false;
+}
+
+struct QemuDBusDisplayOps qemu_dbus_display = {
+ .add_client = qemu_dbus_display_add_client,
+};
diff --git a/ui/dbus.c b/ui/dbus.c
new file mode 100644
index 0000000000..b2c1c9fb52
--- /dev/null
+++ b/ui/dbus.c
@@ -0,0 +1,482 @@
+/*
+ * QEMU DBus display
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * 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 OTHER
+ * 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/cutils.h"
+#include "qemu/dbus.h"
+#include "qemu/main-loop.h"
+#include "qemu/option.h"
+#include "qom/object_interfaces.h"
+#include "sysemu/sysemu.h"
+#include "ui/dbus-module.h"
+#include "ui/egl-helpers.h"
+#include "ui/egl-context.h"
+#include "audio/audio.h"
+#include "audio/audio_int.h"
+#include "qapi/error.h"
+#include "trace.h"
+
+#include "dbus.h"
+
+static DBusDisplay *dbus_display;
+
+static QEMUGLContext dbus_create_context(DisplayGLCtx *dgc,
+ QEMUGLParams *params)
+{
+ eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ qemu_egl_rn_ctx);
+ return qemu_egl_create_context(dgc, params);
+}
+
+static const DisplayGLCtxOps dbus_gl_ops = {
+ .compatible_dcl = &dbus_gl_dcl_ops,
+ .dpy_gl_ctx_create = dbus_create_context,
+ .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
+ .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
+};
+
+static NotifierList dbus_display_notifiers =
+ NOTIFIER_LIST_INITIALIZER(dbus_display_notifiers);
+
+void
+dbus_display_notifier_add(Notifier *notifier)
+{
+ notifier_list_add(&dbus_display_notifiers, notifier);
+}
+
+static void
+dbus_display_notifier_remove(Notifier *notifier)
+{
+ notifier_remove(notifier);
+}
+
+void
+dbus_display_notify(DBusDisplayEvent *event)
+{
+ notifier_list_notify(&dbus_display_notifiers, event);
+}
+
+static void
+dbus_display_init(Object *o)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+ g_autoptr(GDBusObjectSkeleton) vm = NULL;
+
+ dd->glctx.ops = &dbus_gl_ops;
+ dd->iface = qemu_dbus_display1_vm_skeleton_new();
+ dd->consoles = g_ptr_array_new_with_free_func(g_object_unref);
+
+ dd->server = g_dbus_object_manager_server_new(DBUS_DISPLAY1_ROOT);
+
+ vm = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/VM");
+ 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);
+ dbus_chardev_init(dd);
+}
+
+static void
+dbus_display_finalize(Object *o)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ if (dd->notifier.notify) {
+ dbus_display_notifier_remove(&dd->notifier);
+ }
+
+ 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) {
+ g_cancellable_cancel(dd->add_client_cancellable);
+ }
+ g_clear_object(&dd->add_client_cancellable);
+ g_clear_object(&dd->bus);
+ g_clear_object(&dd->iface);
+ g_free(dd->dbus_addr);
+ g_free(dd->audiodev);
+ dbus_display = NULL;
+}
+
+static bool
+dbus_display_add_console(DBusDisplay *dd, int idx, Error **errp)
+{
+ QemuConsole *con;
+ DBusDisplayConsole *dbus_console;
+
+ con = qemu_console_lookup_by_index(idx);
+ assert(con);
+
+ if (qemu_console_is_graphic(con) &&
+ dd->gl_mode != DISPLAYGL_MODE_OFF) {
+ qemu_console_set_display_gl_ctx(con, &dd->glctx);
+ }
+
+ dbus_console = dbus_display_console_new(dd, con);
+ g_ptr_array_insert(dd->consoles, idx, dbus_console);
+ g_dbus_object_manager_server_export(dd->server,
+ G_DBUS_OBJECT_SKELETON(dbus_console));
+ return true;
+}
+
+static void
+dbus_display_complete(UserCreatable *uc, Error **errp)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(uc);
+ g_autoptr(GError) err = NULL;
+ g_autofree char *uuid = qemu_uuid_unparse_strdup(&qemu_uuid);
+ g_autoptr(GArray) consoles = NULL;
+ GVariant *console_ids;
+ int idx;
+
+ if (!object_resolve_path_type("", TYPE_DBUS_DISPLAY, NULL)) {
+ error_setg(errp, "There is already an instance of %s",
+ TYPE_DBUS_DISPLAY);
+ return;
+ }
+
+ if (dd->p2p) {
+ /* wait for dbus_display_add_client() */
+ dbus_display = dd;
+ } else if (dd->dbus_addr && *dd->dbus_addr) {
+ dd->bus = g_dbus_connection_new_for_address_sync(dd->dbus_addr,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+ G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
+ NULL, NULL, &err);
+ } else {
+ dd->bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &err);
+ }
+ if (err) {
+ error_setg(errp, "failed to connect to DBus: %s", err->message);
+ return;
+ }
+
+ if (dd->audiodev && *dd->audiodev) {
+ AudioState *audio_state = audio_state_by_name(dd->audiodev);
+ if (!audio_state) {
+ error_setg(errp, "Audiodev '%s' not found", dd->audiodev);
+ return;
+ }
+ if (!g_str_equal(audio_state->drv->name, "dbus")) {
+ error_setg(errp, "Audiodev '%s' is not compatible with DBus",
+ dd->audiodev);
+ return;
+ }
+ audio_state->drv->set_dbus_server(audio_state, dd->server);
+ }
+
+ consoles = g_array_new(FALSE, FALSE, sizeof(guint32));
+ for (idx = 0;; idx++) {
+ if (!qemu_console_lookup_by_index(idx)) {
+ break;
+ }
+ if (!dbus_display_add_console(dd, idx, errp)) {
+ return;
+ }
+ g_array_append_val(consoles, idx);
+ }
+
+ console_ids = g_variant_new_from_data(
+ G_VARIANT_TYPE("au"),
+ consoles->data, consoles->len * sizeof(guint32), TRUE,
+ (GDestroyNotify)g_array_unref, consoles);
+ g_steal_pointer(&consoles);
+ g_object_set(dd->iface,
+ "name", qemu_name ?: "QEMU " QEMU_VERSION,
+ "uuid", uuid,
+ "console-ids", console_ids,
+ NULL);
+
+ if (dd->bus) {
+ g_dbus_object_manager_server_set_connection(dd->server, dd->bus);
+ g_bus_own_name_on_connection(dd->bus, "org.qemu",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ NULL, NULL, NULL, NULL);
+ }
+}
+
+static void
+dbus_display_add_client_ready(GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GDBusConnection) conn = NULL;
+
+ g_clear_object(&dbus_display->add_client_cancellable);
+
+ conn = g_dbus_connection_new_finish(res, &err);
+ if (!conn) {
+ error_printf("Failed to accept D-Bus client: %s", err->message);
+ }
+
+ g_dbus_object_manager_server_set_connection(dbus_display->server, conn);
+}
+
+
+static bool
+dbus_display_add_client(int csock, Error **errp)
+{
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GSocket) socket = NULL;
+ g_autoptr(GSocketConnection) conn = NULL;
+ g_autofree char *guid = g_dbus_generate_guid();
+
+ if (!dbus_display) {
+ error_setg(errp, "p2p connections not accepted in bus mode");
+ return false;
+ }
+
+ if (dbus_display->add_client_cancellable) {
+ g_cancellable_cancel(dbus_display->add_client_cancellable);
+ }
+
+ socket = g_socket_new_from_fd(csock, &err);
+ if (!socket) {
+ error_setg(errp, "Failed to setup D-Bus socket: %s", err->message);
+ return false;
+ }
+
+ conn = g_socket_connection_factory_create_connection(socket);
+
+ dbus_display->add_client_cancellable = g_cancellable_new();
+
+ g_dbus_connection_new(G_IO_STREAM(conn),
+ guid,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
+ NULL,
+ dbus_display->add_client_cancellable,
+ dbus_display_add_client_ready,
+ NULL);
+
+ return true;
+}
+
+static bool
+get_dbus_p2p(Object *o, Error **errp)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ return dd->p2p;
+}
+
+static void
+set_dbus_p2p(Object *o, bool p2p, Error **errp)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ dd->p2p = p2p;
+}
+
+static char *
+get_dbus_addr(Object *o, Error **errp)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ return g_strdup(dd->dbus_addr);
+}
+
+static void
+set_dbus_addr(Object *o, const char *str, Error **errp)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ g_free(dd->dbus_addr);
+ dd->dbus_addr = g_strdup(str);
+}
+
+static char *
+get_audiodev(Object *o, Error **errp)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ return g_strdup(dd->audiodev);
+}
+
+static void
+set_audiodev(Object *o, const char *str, Error **errp)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ g_free(dd->audiodev);
+ dd->audiodev = g_strdup(str);
+}
+
+
+static int
+get_gl_mode(Object *o, Error **errp)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ return dd->gl_mode;
+}
+
+static void
+set_gl_mode(Object *o, int val, Error **errp)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ dd->gl_mode = val;
+}
+
+static void
+dbus_display_class_init(ObjectClass *oc, void *data)
+{
+ UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
+
+ ucc->complete = dbus_display_complete;
+ object_class_property_add_bool(oc, "p2p", get_dbus_p2p, set_dbus_p2p);
+ object_class_property_add_str(oc, "addr", get_dbus_addr, set_dbus_addr);
+ object_class_property_add_str(oc, "audiodev", get_audiodev, set_audiodev);
+ object_class_property_add_enum(oc, "gl-mode",
+ "DisplayGLMode", &DisplayGLMode_lookup,
+ get_gl_mode, set_gl_mode);
+}
+
+#define TYPE_CHARDEV_VC "chardev-vc"
+
+typedef struct DBusVCClass {
+ DBusChardevClass parent_class;
+
+ void (*parent_parse)(QemuOpts *opts, ChardevBackend *b, Error **errp);
+} DBusVCClass;
+
+DECLARE_CLASS_CHECKERS(DBusVCClass, DBUS_VC,
+ TYPE_CHARDEV_VC)
+
+static void
+dbus_vc_parse(QemuOpts *opts, ChardevBackend *backend,
+ Error **errp)
+{
+ DBusVCClass *klass = DBUS_VC_CLASS(object_class_by_name(TYPE_CHARDEV_VC));
+ const char *name = qemu_opt_get(opts, "name");
+ const char *id = qemu_opts_id(opts);
+
+ if (name == NULL) {
+ if (g_str_has_prefix(id, "compat_monitor")) {
+ name = "org.qemu.monitor.hmp.0";
+ } else if (g_str_has_prefix(id, "serial")) {
+ name = "org.qemu.console.serial.0";
+ } else {
+ name = "";
+ }
+ if (!qemu_opt_set(opts, "name", name, errp)) {
+ return;
+ }
+ }
+
+ klass->parent_parse(opts, backend, errp);
+}
+
+static void
+dbus_vc_class_init(ObjectClass *oc, void *data)
+{
+ DBusVCClass *klass = DBUS_VC_CLASS(oc);
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ klass->parent_parse = cc->parse;
+ cc->parse = dbus_vc_parse;
+}
+
+static const TypeInfo dbus_vc_type_info = {
+ .name = TYPE_CHARDEV_VC,
+ .parent = TYPE_CHARDEV_DBUS,
+ .class_init = dbus_vc_class_init,
+};
+
+static void
+early_dbus_init(DisplayOptions *opts)
+{
+ DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAYGL_MODE_OFF;
+
+ if (mode != DISPLAYGL_MODE_OFF) {
+ if (egl_rendernode_init(opts->u.dbus.rendernode, mode) < 0) {
+ error_report("dbus: render node init failed");
+ exit(1);
+ }
+
+ display_opengl = 1;
+ }
+
+ type_register(&dbus_vc_type_info);
+}
+
+static void
+dbus_init(DisplayState *ds, DisplayOptions *opts)
+{
+ DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAYGL_MODE_OFF;
+
+ if (opts->u.dbus.addr && opts->u.dbus.p2p) {
+ error_report("dbus: can't accept both addr=X and p2p=yes options");
+ exit(1);
+ }
+
+ using_dbus_display = 1;
+
+ object_new_with_props(TYPE_DBUS_DISPLAY,
+ object_get_objects_root(),
+ "dbus-display", &error_fatal,
+ "addr", opts->u.dbus.addr ?: "",
+ "audiodev", opts->u.dbus.audiodev ?: "",
+ "gl-mode", DisplayGLMode_str(mode),
+ "p2p", yes_no(opts->u.dbus.p2p),
+ NULL);
+}
+
+static const TypeInfo dbus_display_info = {
+ .name = TYPE_DBUS_DISPLAY,
+ .parent = TYPE_OBJECT,
+ .instance_size = sizeof(DBusDisplay),
+ .instance_init = dbus_display_init,
+ .instance_finalize = dbus_display_finalize,
+ .class_init = dbus_display_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_USER_CREATABLE },
+ { }
+ }
+};
+
+static QemuDisplay qemu_display_dbus = {
+ .type = DISPLAY_TYPE_DBUS,
+ .early_init = early_dbus_init,
+ .init = dbus_init,
+};
+
+static void register_dbus(void)
+{
+ qemu_dbus_display = (struct QemuDBusDisplayOps) {
+ .add_client = dbus_display_add_client,
+ };
+ type_register_static(&dbus_display_info);
+ qemu_display_register(&qemu_display_dbus);
+}
+
+type_init(register_dbus);
+
+#ifdef CONFIG_OPENGL
+module_dep("ui-opengl");
+#endif
diff --git a/ui/dbus.h b/ui/dbus.h
new file mode 100644
index 0000000000..64c77cab44
--- /dev/null
+++ b/ui/dbus.h
@@ -0,0 +1,144 @@
+/*
+ * QEMU DBus display
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * 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 OTHER
+ * 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.
+ */
+#ifndef UI_DBUS_H_
+#define UI_DBUS_H_
+
+#include "chardev/char-socket.h"
+#include "qemu/dbus.h"
+#include "qom/object.h"
+#include "ui/console.h"
+#include "ui/clipboard.h"
+
+#include "dbus-display1.h"
+
+typedef struct DBusClipboardRequest {
+ GDBusMethodInvocation *invocation;
+ QemuClipboardType type;
+ guint timeout_id;
+} DBusClipboardRequest;
+
+struct DBusDisplay {
+ Object parent;
+
+ DisplayGLMode gl_mode;
+ bool p2p;
+ char *dbus_addr;
+ char *audiodev;
+ DisplayGLCtx glctx;
+
+ GDBusConnection *bus;
+ GDBusObjectManagerServer *server;
+ QemuDBusDisplay1VM *iface;
+ GPtrArray *consoles;
+ GCancellable *add_client_cancellable;
+
+ QemuClipboardPeer clipboard_peer;
+ QemuDBusDisplay1Clipboard *clipboard;
+ QemuDBusDisplay1Clipboard *clipboard_proxy;
+ DBusClipboardRequest clipboard_request[QEMU_CLIPBOARD_SELECTION__COUNT];
+
+ Notifier notifier;
+};
+
+#define TYPE_DBUS_DISPLAY "dbus-display"
+OBJECT_DECLARE_SIMPLE_TYPE(DBusDisplay, DBUS_DISPLAY)
+
+void dbus_display_notifier_add(Notifier *notifier);
+
+#define DBUS_DISPLAY_TYPE_CONSOLE dbus_display_console_get_type()
+G_DECLARE_FINAL_TYPE(DBusDisplayConsole,
+ dbus_display_console,
+ DBUS_DISPLAY,
+ CONSOLE,
+ GDBusObjectSkeleton)
+
+DBusDisplayConsole *
+dbus_display_console_new(DBusDisplay *display, QemuConsole *con);
+
+int
+dbus_display_console_get_index(DBusDisplayConsole *ddc);
+
+#define DBUS_DISPLAY_TYPE_LISTENER dbus_display_listener_get_type()
+G_DECLARE_FINAL_TYPE(DBusDisplayListener,
+ dbus_display_listener,
+ DBUS_DISPLAY,
+ LISTENER,
+ GObject)
+
+DBusDisplayListener *
+dbus_display_listener_new(const char *bus_name,
+ GDBusConnection *conn,
+ DBusDisplayConsole *console);
+
+DBusDisplayConsole *
+dbus_display_listener_get_console(DBusDisplayListener *ddl);
+
+const char *
+dbus_display_listener_get_bus_name(DBusDisplayListener *ddl);
+
+extern const DisplayChangeListenerOps dbus_gl_dcl_ops;
+extern const DisplayChangeListenerOps dbus_dcl_ops;
+
+#define TYPE_CHARDEV_DBUS "chardev-dbus"
+
+typedef struct DBusChardevClass {
+ SocketChardevClass parent_class;
+
+ void (*parent_chr_be_event)(Chardev *s, QEMUChrEvent event);
+} DBusChardevClass;
+
+DECLARE_CLASS_CHECKERS(DBusChardevClass, DBUS_CHARDEV,
+ TYPE_CHARDEV_DBUS)
+
+typedef struct DBusChardev {
+ SocketChardev parent;
+
+ bool exported;
+ QemuDBusDisplay1Chardev *iface;
+} DBusChardev;
+
+DECLARE_INSTANCE_CHECKER(DBusChardev, DBUS_CHARDEV, TYPE_CHARDEV_DBUS)
+
+#define CHARDEV_IS_DBUS(chr) \
+ object_dynamic_cast(OBJECT(chr), TYPE_CHARDEV_DBUS)
+
+typedef enum {
+ DBUS_DISPLAY_CHARDEV_OPEN,
+ DBUS_DISPLAY_CHARDEV_CLOSE,
+} DBusDisplayEventType;
+
+typedef struct DBusDisplayEvent {
+ DBusDisplayEventType type;
+ union {
+ DBusChardev *chardev;
+ };
+} DBusDisplayEvent;
+
+void dbus_display_notify(DBusDisplayEvent *event);
+
+void dbus_chardev_init(DBusDisplay *dpy);
+
+void dbus_clipboard_init(DBusDisplay *dpy);
+
+#endif /* UI_DBUS_H_ */
diff --git a/ui/egl-context.c b/ui/egl-context.c
index 368ffa49d8..eb5f520fc4 100644
--- a/ui/egl-context.c
+++ b/ui/egl-context.c
@@ -1,7 +1,7 @@
#include "qemu/osdep.h"
#include "ui/egl-context.h"
-QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl,
+QEMUGLContext qemu_egl_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params)
{
EGLContext ctx;
@@ -24,12 +24,12 @@ QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl,
return ctx;
}
-void qemu_egl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx)
+void qemu_egl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx)
{
eglDestroyContext(qemu_egl_display, ctx);
}
-int qemu_egl_make_context_current(DisplayChangeListener *dcl,
+int qemu_egl_make_context_current(DisplayGLCtx *dgc,
QEMUGLContext ctx)
{
return eglMakeCurrent(qemu_egl_display,
diff --git a/ui/egl-headless.c b/ui/egl-headless.c
index a26a2520c4..94082a9da9 100644
--- a/ui/egl-headless.c
+++ b/ui/egl-headless.c
@@ -38,12 +38,12 @@ static void egl_gfx_switch(DisplayChangeListener *dcl,
edpy->ds = new_surface;
}
-static QEMUGLContext egl_create_context(DisplayChangeListener *dcl,
+static QEMUGLContext egl_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params)
{
eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
qemu_egl_rn_ctx);
- return qemu_egl_create_context(dcl, params);
+ return qemu_egl_create_context(dgc, params);
}
static void egl_scanout_disable(DisplayChangeListener *dcl)
@@ -157,10 +157,6 @@ static const DisplayChangeListenerOps egl_ops = {
.dpy_gfx_update = egl_gfx_update,
.dpy_gfx_switch = egl_gfx_switch,
- .dpy_gl_ctx_create = egl_create_context,
- .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
- .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
-
.dpy_gl_scanout_disable = egl_scanout_disable,
.dpy_gl_scanout_texture = egl_scanout_texture,
.dpy_gl_scanout_dmabuf = egl_scanout_dmabuf,
@@ -170,6 +166,13 @@ static const DisplayChangeListenerOps egl_ops = {
.dpy_gl_update = egl_scanout_flush,
};
+static const DisplayGLCtxOps eglctx_ops = {
+ .compatible_dcl = &egl_ops,
+ .dpy_gl_ctx_create = egl_create_context,
+ .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
+ .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
+};
+
static void early_egl_headless_init(DisplayOptions *opts)
{
display_opengl = 1;
@@ -188,6 +191,8 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts)
}
for (idx = 0;; idx++) {
+ DisplayGLCtx *ctx;
+
con = qemu_console_lookup_by_index(idx);
if (!con || !qemu_console_is_graphic(con)) {
break;
@@ -197,6 +202,9 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts)
edpy->dcl.con = con;
edpy->dcl.ops = &egl_ops;
edpy->gls = qemu_gl_init_shader();
+ ctx = g_new0(DisplayGLCtx, 1);
+ ctx->ops = &eglctx_ops;
+ qemu_console_set_display_gl_ctx(con, ctx);
register_displaychangelistener(&edpy->dcl);
}
}
diff --git a/ui/gtk-clipboard.c b/ui/gtk-clipboard.c
index 35b7a2c228..e0b8b283fe 100644
--- a/ui/gtk-clipboard.c
+++ b/ui/gtk-clipboard.c
@@ -74,10 +74,9 @@ static void gd_clipboard_clear(GtkClipboard *clipboard,
gd->cbowner[s] = false;
}
-static void gd_clipboard_notify(Notifier *notifier, void *data)
+static void gd_clipboard_update_info(GtkDisplayState *gd,
+ QemuClipboardInfo *info)
{
- GtkDisplayState *gd = container_of(notifier, GtkDisplayState, cbpeer.update);
- QemuClipboardInfo *info = data;
QemuClipboardSelection s = info->selection;
bool self_update = info->owner == &gd->cbpeer;
@@ -118,6 +117,22 @@ static void gd_clipboard_notify(Notifier *notifier, void *data)
*/
}
+static void gd_clipboard_notify(Notifier *notifier, void *data)
+{
+ GtkDisplayState *gd =
+ container_of(notifier, GtkDisplayState, cbpeer.notifier);
+ QemuClipboardNotify *notify = data;
+
+ switch (notify->type) {
+ case QEMU_CLIPBOARD_UPDATE_INFO:
+ gd_clipboard_update_info(gd, notify->info);
+ return;
+ case QEMU_CLIPBOARD_RESET_SERIAL:
+ /* ignore */
+ return;
+ }
+}
+
static void gd_clipboard_request(QemuClipboardInfo *info,
QemuClipboardType type)
{
@@ -172,7 +187,7 @@ static void gd_owner_change(GtkClipboard *clipboard,
void gd_clipboard_init(GtkDisplayState *gd)
{
gd->cbpeer.name = "gtk";
- gd->cbpeer.update.notify = gd_clipboard_notify;
+ gd->cbpeer.notifier.notify = gd_clipboard_notify;
gd->cbpeer.request = gd_clipboard_request;
qemu_clipboard_peer_register(&gd->cbpeer);
diff --git a/ui/gtk-egl.c b/ui/gtk-egl.c
index 45cb67712d..e3bd4bc274 100644
--- a/ui/gtk-egl.c
+++ b/ui/gtk-egl.c
@@ -119,8 +119,6 @@ void gd_egl_draw(VirtualConsole *vc)
glFlush();
}
-
- graphic_hw_gl_flushed(vc->gfx.dcl.con);
}
void gd_egl_update(DisplayChangeListener *dcl,
@@ -199,14 +197,14 @@ void gd_egl_switch(DisplayChangeListener *dcl,
}
}
-QEMUGLContext gd_egl_create_context(DisplayChangeListener *dcl,
+QEMUGLContext gd_egl_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params)
{
- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+ VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc);
eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
vc->gfx.esurface, vc->gfx.ectx);
- return qemu_egl_create_context(dcl, params);
+ return qemu_egl_create_context(dgc, params);
}
void gd_egl_scanout_disable(DisplayChangeListener *dcl)
@@ -362,10 +360,10 @@ void gtk_egl_init(DisplayGLMode mode)
display_opengl = 1;
}
-int gd_egl_make_current(DisplayChangeListener *dcl,
+int gd_egl_make_current(DisplayGLCtx *dgc,
QEMUGLContext ctx)
{
- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+ VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc);
return eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
vc->gfx.esurface, ctx);
diff --git a/ui/gtk-gl-area.c b/ui/gtk-gl-area.c
index 01e4e74ee3..fc5a082eb8 100644
--- a/ui/gtk-gl-area.c
+++ b/ui/gtk-gl-area.c
@@ -101,8 +101,6 @@ void gd_gl_area_draw(VirtualConsole *vc)
surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, ww, wh);
surface_gl_render_texture(vc->gfx.gls, vc->gfx.ds);
}
-
- graphic_hw_gl_flushed(vc->gfx.dcl.con);
}
void gd_gl_area_update(DisplayChangeListener *dcl,
@@ -172,10 +170,10 @@ void gd_gl_area_switch(DisplayChangeListener *dcl,
}
}
-QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl,
+QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params)
{
- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+ VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc);
GdkWindow *window;
GdkGLContext *ctx;
GError *err = NULL;
@@ -201,7 +199,7 @@ QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl,
return ctx;
}
-void gd_gl_area_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx)
+void gd_gl_area_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx)
{
/* FIXME */
}
@@ -280,7 +278,7 @@ void gtk_gl_area_init(void)
display_opengl = 1;
}
-int gd_gl_area_make_current(DisplayChangeListener *dcl,
+int gd_gl_area_make_current(DisplayGLCtx *dgc,
QEMUGLContext ctx)
{
gdk_gl_context_make_current(ctx);
diff --git a/ui/gtk.c b/ui/gtk.c
index 428f02f2df..6a1f65d518 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -593,7 +593,6 @@ void gd_hw_gl_flushed(void *vcon)
close(dmabuf->fence_fd);
dmabuf->fence_fd = -1;
graphic_hw_gl_block(vc->gfx.dcl.con, false);
- graphic_hw_gl_flushed(vc->gfx.dcl.con);
}
/** DisplayState Callbacks (opengl version) **/
@@ -607,9 +606,6 @@ static const DisplayChangeListenerOps dcl_gl_area_ops = {
.dpy_mouse_set = gd_mouse_set,
.dpy_cursor_define = gd_cursor_define,
- .dpy_gl_ctx_create = gd_gl_area_create_context,
- .dpy_gl_ctx_destroy = gd_gl_area_destroy_context,
- .dpy_gl_ctx_make_current = gd_gl_area_make_current,
.dpy_gl_scanout_texture = gd_gl_area_scanout_texture,
.dpy_gl_scanout_disable = gd_gl_area_scanout_disable,
.dpy_gl_update = gd_gl_area_scanout_flush,
@@ -618,8 +614,14 @@ static const DisplayChangeListenerOps dcl_gl_area_ops = {
.dpy_has_dmabuf = gd_has_dmabuf,
};
-#ifdef CONFIG_X11
+static const DisplayGLCtxOps gl_area_ctx_ops = {
+ .compatible_dcl = &dcl_gl_area_ops,
+ .dpy_gl_ctx_create = gd_gl_area_create_context,
+ .dpy_gl_ctx_destroy = gd_gl_area_destroy_context,
+ .dpy_gl_ctx_make_current = gd_gl_area_make_current,
+};
+#ifdef CONFIG_X11
static const DisplayChangeListenerOps dcl_egl_ops = {
.dpy_name = "gtk-egl",
.dpy_gfx_update = gd_egl_update,
@@ -629,9 +631,6 @@ static const DisplayChangeListenerOps dcl_egl_ops = {
.dpy_mouse_set = gd_mouse_set,
.dpy_cursor_define = gd_cursor_define,
- .dpy_gl_ctx_create = gd_egl_create_context,
- .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
- .dpy_gl_ctx_make_current = gd_egl_make_current,
.dpy_gl_scanout_disable = gd_egl_scanout_disable,
.dpy_gl_scanout_texture = gd_egl_scanout_texture,
.dpy_gl_scanout_dmabuf = gd_egl_scanout_dmabuf,
@@ -642,6 +641,12 @@ static const DisplayChangeListenerOps dcl_egl_ops = {
.dpy_has_dmabuf = gd_has_dmabuf,
};
+static const DisplayGLCtxOps egl_ctx_ops = {
+ .compatible_dcl = &dcl_egl_ops,
+ .dpy_gl_ctx_create = gd_egl_create_context,
+ .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
+ .dpy_gl_ctx_make_current = gd_egl_make_current,
+};
#endif
#endif /* CONFIG_OPENGL */
@@ -698,7 +703,7 @@ static void gd_set_ui_info(VirtualConsole *vc, gint width, gint height)
memset(&info, 0, sizeof(info));
info.width = width;
info.height = height;
- dpy_set_ui_info(vc->gfx.dcl.con, &info);
+ dpy_set_ui_info(vc->gfx.dcl.con, &info, true);
}
#if defined(CONFIG_OPENGL)
@@ -2035,6 +2040,7 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
g_signal_connect(vc->gfx.drawing_area, "realize",
G_CALLBACK(gl_area_realize), vc);
vc->gfx.dcl.ops = &dcl_gl_area_ops;
+ vc->gfx.dgc.ops = &gl_area_ctx_ops;
} else {
#ifdef CONFIG_X11
vc->gfx.drawing_area = gtk_drawing_area_new();
@@ -2049,6 +2055,7 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
gtk_widget_set_double_buffered(vc->gfx.drawing_area, FALSE);
#pragma GCC diagnostic pop
vc->gfx.dcl.ops = &dcl_egl_ops;
+ vc->gfx.dgc.ops = &egl_ctx_ops;
vc->gfx.has_dmabuf = qemu_egl_has_dmabuf();
#else
abort();
@@ -2083,6 +2090,9 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
vc->gfx.kbd = qkbd_state_init(con);
vc->gfx.dcl.con = con;
+ if (display_opengl) {
+ qemu_console_set_display_gl_ctx(con, &vc->gfx.dgc);
+ }
register_displaychangelistener(&vc->gfx.dcl);
gd_connect_vc_gfx_signals(vc);
diff --git a/ui/meson.build b/ui/meson.build
index ee8ef27714..64286ba150 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -12,7 +12,11 @@ softmmu_ss.add(files(
'kbd-state.c',
'keymaps.c',
'qemu-pixman.c',
+ 'util.c',
))
+if dbus_display
+ softmmu_ss.add(files('dbus-module.c'))
+endif
softmmu_ss.add([spice_headers, files('spice-module.c')])
softmmu_ss.add(when: spice_protocol, if_true: files('vdagent.c'))
@@ -64,6 +68,30 @@ if config_host.has_key('CONFIG_OPENGL') and gbm.found()
ui_modules += {'egl-headless' : egl_headless_ss}
endif
+if dbus_display
+ dbus_ss = ss.source_set()
+ dbus_display1 = custom_target('dbus-display gdbus-codegen',
+ output: ['dbus-display1.h', 'dbus-display1.c'],
+ input: files('dbus-display1.xml'),
+ command: [config_host['GDBUS_CODEGEN'],
+ '@INPUT@',
+ '--glib-min-required', '2.64',
+ '--output-directory', meson.current_build_dir(),
+ '--interface-prefix', 'org.qemu.',
+ '--c-namespace', 'QemuDBus',
+ '--generate-c-code', '@BASENAME@'])
+ dbus_ss.add(when: [gio, pixman, opengl, 'CONFIG_GIO'],
+ if_true: [files(
+ 'dbus-chardev.c',
+ 'dbus-clipboard.c',
+ 'dbus-console.c',
+ 'dbus-error.c',
+ 'dbus-listener.c',
+ 'dbus.c',
+ ), dbus_display1])
+ ui_modules += {'dbus' : dbus_ss}
+endif
+
if gtk.found()
softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('win32-kbd-hook.c'))
diff --git a/ui/sdl2-gl.c b/ui/sdl2-gl.c
index a21d2deed9..39cab8cde7 100644
--- a/ui/sdl2-gl.c
+++ b/ui/sdl2-gl.c
@@ -58,7 +58,6 @@ static void sdl2_gl_render_surface(struct sdl2_console *scon)
surface_gl_render_texture(scon->gls, scon->surface);
SDL_GL_SwapWindow(scon->real_window);
- graphic_hw_gl_flushed(scon->dcl.con);
}
void sdl2_gl_update(DisplayChangeListener *dcl,
@@ -133,10 +132,10 @@ void sdl2_gl_redraw(struct sdl2_console *scon)
}
}
-QEMUGLContext sdl2_gl_create_context(DisplayChangeListener *dcl,
+QEMUGLContext sdl2_gl_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params)
{
- struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
+ struct sdl2_console *scon = container_of(dgc, struct sdl2_console, dgc);
SDL_GLContext ctx;
assert(scon->opengl);
@@ -168,17 +167,17 @@ QEMUGLContext sdl2_gl_create_context(DisplayChangeListener *dcl,
return (QEMUGLContext)ctx;
}
-void sdl2_gl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx)
+void sdl2_gl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx)
{
SDL_GLContext sdlctx = (SDL_GLContext)ctx;
SDL_GL_DeleteContext(sdlctx);
}
-int sdl2_gl_make_context_current(DisplayChangeListener *dcl,
+int sdl2_gl_make_context_current(DisplayGLCtx *dgc,
QEMUGLContext ctx)
{
- struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
+ struct sdl2_console *scon = container_of(dgc, struct sdl2_console, dgc);
SDL_GLContext sdlctx = (SDL_GLContext)ctx;
assert(scon->opengl);
@@ -241,5 +240,4 @@ void sdl2_gl_scanout_flush(DisplayChangeListener *dcl,
egl_fb_blit(&scon->win_fb, &scon->guest_fb, !scon->y0_top);
SDL_GL_SwapWindow(scon->real_window);
- graphic_hw_gl_flushed(dcl->con);
}
diff --git a/ui/sdl2.c b/ui/sdl2.c
index 17c0ec30eb..0bd30504cf 100644
--- a/ui/sdl2.c
+++ b/ui/sdl2.c
@@ -561,7 +561,7 @@ static void handle_windowevent(SDL_Event *ev)
memset(&info, 0, sizeof(info));
info.width = ev->window.data1;
info.height = ev->window.data2;
- dpy_set_ui_info(scon->dcl.con, &info);
+ dpy_set_ui_info(scon->dcl.con, &info, true);
}
sdl2_redraw(scon);
break;
@@ -778,13 +778,17 @@ static const DisplayChangeListenerOps dcl_gl_ops = {
.dpy_mouse_set = sdl_mouse_warp,
.dpy_cursor_define = sdl_mouse_define,
- .dpy_gl_ctx_create = sdl2_gl_create_context,
- .dpy_gl_ctx_destroy = sdl2_gl_destroy_context,
- .dpy_gl_ctx_make_current = sdl2_gl_make_context_current,
.dpy_gl_scanout_disable = sdl2_gl_scanout_disable,
.dpy_gl_scanout_texture = sdl2_gl_scanout_texture,
.dpy_gl_update = sdl2_gl_scanout_flush,
};
+
+static const DisplayGLCtxOps gl_ctx_ops = {
+ .compatible_dcl = &dcl_gl_ops,
+ .dpy_gl_ctx_create = sdl2_gl_create_context,
+ .dpy_gl_ctx_destroy = sdl2_gl_destroy_context,
+ .dpy_gl_ctx_make_current = sdl2_gl_make_context_current,
+};
#endif
static void sdl2_display_early_init(DisplayOptions *o)
@@ -860,12 +864,16 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
#ifdef CONFIG_OPENGL
sdl2_console[i].opengl = display_opengl;
sdl2_console[i].dcl.ops = display_opengl ? &dcl_gl_ops : &dcl_2d_ops;
+ sdl2_console[i].dgc.ops = display_opengl ? &gl_ctx_ops : NULL;
#else
sdl2_console[i].opengl = 0;
sdl2_console[i].dcl.ops = &dcl_2d_ops;
#endif
sdl2_console[i].dcl.con = con;
sdl2_console[i].kbd = qkbd_state_init(con);
+ if (display_opengl) {
+ qemu_console_set_display_gl_ctx(con, &sdl2_console[i].dgc);
+ }
register_displaychangelistener(&sdl2_console[i].dcl);
#if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_X11)
diff --git a/ui/spice-core.c b/ui/spice-core.c
index 31974b8d6c..c3ac20ad43 100644
--- a/ui/spice-core.c
+++ b/ui/spice-core.c
@@ -884,56 +884,6 @@ bool qemu_spice_have_display_interface(QemuConsole *con)
return false;
}
-/*
- * Recursively (in reverse order) appends addresses of PCI devices as it moves
- * up in the PCI hierarchy.
- *
- * @returns true on success, false when the buffer wasn't large enough
- */
-static bool append_pci_address(char *buf, size_t buf_size, const PCIDevice *pci)
-{
- PCIBus *bus = pci_get_bus(pci);
- /*
- * equivalent to if (!pci_bus_is_root(bus)), but the function is not built
- * with PCI_CONFIG=n, avoid using an #ifdef by checking directly
- */
- if (bus->parent_dev != NULL) {
- append_pci_address(buf, buf_size, bus->parent_dev);
- }
-
- size_t len = strlen(buf);
- ssize_t written = snprintf(buf + len, buf_size - len, "/%02x.%x",
- PCI_SLOT(pci->devfn), PCI_FUNC(pci->devfn));
-
- return written > 0 && written < buf_size - len;
-}
-
-bool qemu_spice_fill_device_address(QemuConsole *con,
- char *device_address,
- size_t size)
-{
- DeviceState *dev = DEVICE(object_property_get_link(OBJECT(con),
- "device",
- &error_abort));
- PCIDevice *pci = (PCIDevice *) object_dynamic_cast(OBJECT(dev),
- TYPE_PCI_DEVICE);
-
- if (pci == NULL) {
- warn_report("Setting device address of a display device to SPICE: "
- "Not a PCI device.");
- return false;
- }
-
- strncpy(device_address, "pci/0000", size);
- if (!append_pci_address(device_address, size, pci)) {
- warn_report("Setting device address of a display device to SPICE: "
- "Too many PCI devices in the chain.");
- return false;
- }
-
- return true;
-}
-
int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con)
{
if (g_slist_find(spice_consoles, con)) {
diff --git a/ui/spice-display.c b/ui/spice-display.c
index f59c69882d..1043f47f94 100644
--- a/ui/spice-display.c
+++ b/ui/spice-display.c
@@ -692,7 +692,7 @@ static int interface_client_monitors_config(QXLInstance *sin,
}
trace_qemu_spice_ui_info(ssd->qxl.id, info.width, info.height);
- dpy_set_ui_info(ssd->dcl.con, &info);
+ dpy_set_ui_info(ssd->dcl.con, &info, false);
return 1;
}
@@ -830,7 +830,6 @@ static void qemu_spice_gl_unblock_bh(void *opaque)
SimpleSpiceDisplay *ssd = opaque;
qemu_spice_gl_block(ssd, false);
- graphic_hw_gl_flushed(ssd->dcl.con);
}
static void qemu_spice_gl_block_timer(void *opaque)
@@ -909,12 +908,12 @@ static void spice_gl_switch(DisplayChangeListener *dcl,
}
}
-static QEMUGLContext qemu_spice_gl_create_context(DisplayChangeListener *dcl,
+static QEMUGLContext qemu_spice_gl_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params)
{
eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
qemu_egl_rn_ctx);
- return qemu_egl_create_context(dcl, params);
+ return qemu_egl_create_context(dgc, params);
}
static void qemu_spice_gl_scanout_disable(DisplayChangeListener *dcl)
@@ -1106,10 +1105,6 @@ static const DisplayChangeListenerOps display_listener_gl_ops = {
.dpy_mouse_set = display_mouse_set,
.dpy_cursor_define = display_mouse_define,
- .dpy_gl_ctx_create = qemu_spice_gl_create_context,
- .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
- .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
-
.dpy_gl_scanout_disable = qemu_spice_gl_scanout_disable,
.dpy_gl_scanout_texture = qemu_spice_gl_scanout_texture,
.dpy_gl_scanout_dmabuf = qemu_spice_gl_scanout_dmabuf,
@@ -1119,6 +1114,13 @@ static const DisplayChangeListenerOps display_listener_gl_ops = {
.dpy_gl_update = qemu_spice_gl_update,
};
+static const DisplayGLCtxOps gl_ctx_ops = {
+ .compatible_dcl = &display_listener_gl_ops,
+ .dpy_gl_ctx_create = qemu_spice_gl_create_context,
+ .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
+ .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
+};
+
#endif /* HAVE_SPICE_GL */
static void qemu_spice_display_init_one(QemuConsole *con)
@@ -1131,6 +1133,7 @@ static void qemu_spice_display_init_one(QemuConsole *con)
#ifdef HAVE_SPICE_GL
if (spice_opengl) {
ssd->dcl.ops = &display_listener_gl_ops;
+ ssd->dgc.ops = &gl_ctx_ops;
ssd->gl_unblock_bh = qemu_bh_new(qemu_spice_gl_unblock_bh, ssd);
ssd->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
qemu_spice_gl_block_timer, ssd);
@@ -1145,17 +1148,23 @@ static void qemu_spice_display_init_one(QemuConsole *con)
qemu_spice_add_display_interface(&ssd->qxl, con);
#if SPICE_SERVER_VERSION >= 0x000e02 /* release 0.14.2 */
+ Error *err = NULL;
char device_address[256] = "";
- if (qemu_spice_fill_device_address(con, device_address, 256)) {
+ if (qemu_console_fill_device_address(con, device_address, 256, &err)) {
spice_qxl_set_device_info(&ssd->qxl,
device_address,
qemu_console_get_head(con),
1);
+ } else {
+ error_report_err(err);
}
#endif
qemu_spice_create_host_memslot(ssd);
+ if (spice_opengl) {
+ qemu_console_set_display_gl_ctx(con, &ssd->dgc);
+ }
register_displaychangelistener(&ssd->dcl);
}
diff --git a/ui/trace-events b/ui/trace-events
index b9c0dd0fa1..f78b5e6606 100644
--- a/ui/trace-events
+++ b/ui/trace-events
@@ -135,3 +135,18 @@ vdagent_recv_msg(const char *name, uint32_t size) "msg %s, size %d"
vdagent_peer_cap(const char *name) "cap %s"
vdagent_cb_grab_selection(const char *name) "selection %s"
vdagent_cb_grab_type(const char *name) "type %s"
+vdagent_cb_serial_discard(uint32_t current, uint32_t received) "current=%u, received=%u"
+
+# dbus.c
+dbus_registered_listener(const char *bus_name) "peer %s"
+dbus_listener_vanished(const char *bus_name) "peer %s"
+dbus_kbd_press(unsigned int keycode) "keycode %u"
+dbus_kbd_release(unsigned int keycode) "keycode %u"
+dbus_mouse_press(unsigned int button) "button %u"
+dbus_mouse_release(unsigned int button) "button %u"
+dbus_mouse_set_pos(unsigned int x, unsigned int y) "x=%u, y=%u"
+dbus_mouse_rel_motion(int dx, int dy) "dx=%d, dy=%d"
+dbus_update(int x, int y, int w, int h) "x=%d, y=%d, w=%d, h=%d"
+dbus_clipboard_grab_failed(void) ""
+dbus_clipboard_register(const char *bus_name) "peer %s"
+dbus_clipboard_unregister(const char *bus_name) "peer %s"
diff --git a/ui/util.c b/ui/util.c
new file mode 100644
index 0000000000..7e8fc1ea53
--- /dev/null
+++ b/ui/util.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_bus.h"
+#include "qapi/error.h"
+#include "ui/console.h"
+
+/*
+ * Recursively (in reverse order) appends addresses of PCI devices as it moves
+ * up in the PCI hierarchy.
+ *
+ * @returns true on success, false when the buffer wasn't large enough
+ */
+static bool append_pci_address(char *buf, size_t buf_size, const PCIDevice *pci)
+{
+ PCIBus *bus = pci_get_bus(pci);
+ /*
+ * equivalent to if (!pci_bus_is_root(bus)), but the function is not built
+ * with PCI_CONFIG=n, avoid using an #ifdef by checking directly
+ */
+ if (bus->parent_dev != NULL) {
+ append_pci_address(buf, buf_size, bus->parent_dev);
+ }
+
+ size_t len = strlen(buf);
+ ssize_t written = snprintf(buf + len, buf_size - len, "/%02x.%x",
+ PCI_SLOT(pci->devfn), PCI_FUNC(pci->devfn));
+
+ return written > 0 && written < buf_size - len;
+}
+
+bool qemu_console_fill_device_address(QemuConsole *con,
+ char *device_address,
+ size_t size,
+ Error **errp)
+{
+ ERRP_GUARD();
+ DeviceState *dev = DEVICE(object_property_get_link(OBJECT(con),
+ "device",
+ &error_abort));
+ PCIDevice *pci = (PCIDevice *) object_dynamic_cast(OBJECT(dev),
+ TYPE_PCI_DEVICE);
+
+ if (pci == NULL) {
+ error_setg(errp, "Setting device address of a display device: "
+ "Not a PCI device.");
+ return false;
+ }
+
+ strncpy(device_address, "pci/0000", size);
+ if (!append_pci_address(device_address, size, pci)) {
+ error_setg(errp, "Setting device address of a display device: "
+ "Too many PCI devices in the chain.");
+ return false;
+ }
+
+ return true;
+}
diff --git a/ui/vdagent.c b/ui/vdagent.c
index 19e8fbfc96..7ea4bc5d9a 100644
--- a/ui/vdagent.c
+++ b/ui/vdagent.c
@@ -17,6 +17,14 @@
#include "spice/vd_agent.h"
+#define CHECK_SPICE_PROTOCOL_VERSION(major, minor, micro) \
+ (CONFIG_SPICE_PROTOCOL_MAJOR > (major) || \
+ (CONFIG_SPICE_PROTOCOL_MAJOR == (major) && \
+ CONFIG_SPICE_PROTOCOL_MINOR > (minor)) || \
+ (CONFIG_SPICE_PROTOCOL_MAJOR == (major) && \
+ CONFIG_SPICE_PROTOCOL_MINOR == (minor) && \
+ CONFIG_SPICE_PROTOCOL_MICRO >= (micro)))
+
#define VDAGENT_BUFFER_LIMIT (1 * MiB)
#define VDAGENT_MOUSE_DEFAULT true
#define VDAGENT_CLIPBOARD_DEFAULT false
@@ -51,6 +59,7 @@ struct VDAgentChardev {
/* clipboard */
QemuClipboardPeer cbpeer;
+ uint32_t last_serial[QEMU_CLIPBOARD_SELECTION__COUNT];
uint32_t cbpending[QEMU_CLIPBOARD_SELECTION__COUNT];
};
typedef struct VDAgentChardev VDAgentChardev;
@@ -79,8 +88,10 @@ static const char *cap_name[] = {
[VD_AGENT_CAP_MONITORS_CONFIG_POSITION] = "monitors-config-position",
[VD_AGENT_CAP_FILE_XFER_DISABLED] = "file-xfer-disabled",
[VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS] = "file-xfer-detailed-errors",
-#if 0
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 0)
[VD_AGENT_CAP_GRAPHICS_DEVICE_INFO] = "graphics-device-info",
+#endif
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1)
[VD_AGENT_CAP_CLIPBOARD_NO_RELEASE_ON_REGRAB] = "clipboard-no-release-on-regrab",
[VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL] = "clipboard-grab-serial",
#endif
@@ -102,7 +113,7 @@ static const char *msg_name[] = {
[VD_AGENT_CLIENT_DISCONNECTED] = "client-disconnected",
[VD_AGENT_MAX_CLIPBOARD] = "max-clipboard",
[VD_AGENT_AUDIO_VOLUME_SYNC] = "audio-volume-sync",
-#if 0
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 0)
[VD_AGENT_GRAPHICS_DEVICE_INFO] = "graphics-device-info",
#endif
};
@@ -120,7 +131,7 @@ static const char *type_name[] = {
[VD_AGENT_CLIPBOARD_IMAGE_BMP] = "bmp",
[VD_AGENT_CLIPBOARD_IMAGE_TIFF] = "tiff",
[VD_AGENT_CLIPBOARD_IMAGE_JPG] = "jpg",
-#if 0
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 3)
[VD_AGENT_CLIPBOARD_FILE_LIST] = "files",
#endif
};
@@ -193,6 +204,9 @@ static void vdagent_send_caps(VDAgentChardev *vd)
if (vd->clipboard) {
caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION);
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1)
+ caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL);
+#endif
}
vdagent_send_msg(vd, msg);
@@ -323,7 +337,8 @@ static void vdagent_send_clipboard_grab(VDAgentChardev *vd,
{
g_autofree VDAgentMessage *msg =
g_malloc0(sizeof(VDAgentMessage) +
- sizeof(uint32_t) * (QEMU_CLIPBOARD_TYPE__COUNT + 1));
+ sizeof(uint32_t) * (QEMU_CLIPBOARD_TYPE__COUNT + 1) +
+ sizeof(uint32_t));
uint8_t *s = msg->data;
uint32_t *data = (uint32_t *)msg->data;
uint32_t q, type;
@@ -336,6 +351,19 @@ static void vdagent_send_clipboard_grab(VDAgentChardev *vd,
return;
}
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1)
+ if (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL)) {
+ if (!info->has_serial) {
+ /* client should win */
+ info->serial = vd->last_serial[info->selection]++;
+ info->has_serial = true;
+ }
+ *data = info->serial;
+ data++;
+ msg->size += sizeof(uint32_t);
+ }
+#endif
+
for (q = 0; q < QEMU_CLIPBOARD_TYPE__COUNT; q++) {
type = type_qemu_to_vdagent(q);
if (type != VD_AGENT_CLIPBOARD_NONE && info->types[q].available) {
@@ -407,10 +435,9 @@ static void vdagent_send_empty_clipboard_data(VDAgentChardev *vd,
vdagent_send_clipboard_data(vd, info, type);
}
-static void vdagent_clipboard_notify(Notifier *notifier, void *data)
+static void vdagent_clipboard_update_info(VDAgentChardev *vd,
+ QemuClipboardInfo *info)
{
- VDAgentChardev *vd = container_of(notifier, VDAgentChardev, cbpeer.update);
- QemuClipboardInfo *info = data;
QemuClipboardSelection s = info->selection;
QemuClipboardType type;
bool self_update = info->owner == &vd->cbpeer;
@@ -439,6 +466,31 @@ static void vdagent_clipboard_notify(Notifier *notifier, void *data)
}
}
+static void vdagent_clipboard_reset_serial(VDAgentChardev *vd)
+{
+ Chardev *chr = CHARDEV(vd);
+
+ /* reopen the agent connection to reset the serial state */
+ qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
+ qemu_chr_be_event(chr, CHR_EVENT_OPENED);
+}
+
+static void vdagent_clipboard_notify(Notifier *notifier, void *data)
+{
+ VDAgentChardev *vd =
+ container_of(notifier, VDAgentChardev, cbpeer.notifier);
+ QemuClipboardNotify *notify = data;
+
+ switch (notify->type) {
+ case QEMU_CLIPBOARD_UPDATE_INFO:
+ vdagent_clipboard_update_info(vd, notify->info);
+ return;
+ case QEMU_CLIPBOARD_RESET_SERIAL:
+ vdagent_clipboard_reset_serial(vd);
+ return;
+ }
+}
+
static void vdagent_clipboard_request(QemuClipboardInfo *info,
QemuClipboardType qtype)
{
@@ -472,6 +524,24 @@ static void vdagent_clipboard_recv_grab(VDAgentChardev *vd, uint8_t s, uint32_t
trace_vdagent_cb_grab_selection(GET_NAME(sel_name, s));
info = qemu_clipboard_info_new(&vd->cbpeer, s);
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1)
+ if (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL)) {
+ if (size < sizeof(uint32_t)) {
+ /* this shouldn't happen! */
+ return;
+ }
+
+ info->has_serial = true;
+ info->serial = *(uint32_t *)data;
+ if (info->serial < vd->last_serial[s]) {
+ /* discard lower-ordering guest grab */
+ return;
+ }
+ vd->last_serial[s] = info->serial;
+ data += sizeof(uint32_t);
+ size -= sizeof(uint32_t);
+ }
+#endif
if (size > sizeof(uint32_t) * 10) {
/*
* spice has 6 types as of 2021. Limiting to 10 entries
@@ -648,9 +718,10 @@ static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg)
if (have_mouse(vd) && vd->mouse_hs) {
qemu_input_handler_activate(vd->mouse_hs);
}
- if (have_clipboard(vd) && vd->cbpeer.update.notify == NULL) {
+ if (have_clipboard(vd) && vd->cbpeer.notifier.notify == NULL) {
+ memset(vd->last_serial, 0, sizeof(vd->last_serial));
vd->cbpeer.name = "vdagent";
- vd->cbpeer.update.notify = vdagent_clipboard_notify;
+ vd->cbpeer.notifier.notify = vdagent_clipboard_notify;
vd->cbpeer.request = vdagent_clipboard_request;
qemu_clipboard_peer_register(&vd->cbpeer);
}
@@ -789,7 +860,7 @@ static void vdagent_disconnect(VDAgentChardev *vd)
if (vd->mouse_hs) {
qemu_input_handler_deactivate(vd->mouse_hs);
}
- if (vd->cbpeer.update.notify) {
+ if (vd->cbpeer.notifier.notify) {
qemu_clipboard_peer_unregister(&vd->cbpeer);
memset(&vd->cbpeer, 0, sizeof(vd->cbpeer));
}
@@ -797,11 +868,8 @@ static void vdagent_disconnect(VDAgentChardev *vd)
static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open)
{
- VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr);
-
if (!fe_open) {
trace_vdagent_close();
- vdagent_disconnect(vd);
return;
}
diff --git a/ui/vnc-clipboard.c b/ui/vnc-clipboard.c
index 67284b556c..d48f75eb1a 100644
--- a/ui/vnc-clipboard.c
+++ b/ui/vnc-clipboard.c
@@ -189,10 +189,8 @@ static void vnc_clipboard_provide(VncState *vs,
vnc_flush(vs);
}
-static void vnc_clipboard_notify(Notifier *notifier, void *data)
+static void vnc_clipboard_update_info(VncState *vs, QemuClipboardInfo *info)
{
- VncState *vs = container_of(notifier, VncState, cbpeer.update);
- QemuClipboardInfo *info = data;
QemuClipboardType type;
bool self_update = info->owner == &vs->cbpeer;
uint32_t flags = 0;
@@ -223,6 +221,21 @@ static void vnc_clipboard_notify(Notifier *notifier, void *data)
}
}
+static void vnc_clipboard_notify(Notifier *notifier, void *data)
+{
+ VncState *vs = container_of(notifier, VncState, cbpeer.notifier);
+ QemuClipboardNotify *notify = data;
+
+ switch (notify->type) {
+ case QEMU_CLIPBOARD_UPDATE_INFO:
+ vnc_clipboard_update_info(vs, notify->info);
+ return;
+ case QEMU_CLIPBOARD_RESET_SERIAL:
+ /* ignore */
+ return;
+ }
+}
+
static void vnc_clipboard_request(QemuClipboardInfo *info,
QemuClipboardType type)
{
@@ -316,9 +329,9 @@ void vnc_server_cut_text_caps(VncState *vs)
caps[1] = 0;
vnc_clipboard_send(vs, 2, caps);
- if (!vs->cbpeer.update.notify) {
+ if (!vs->cbpeer.notifier.notify) {
vs->cbpeer.name = "vnc";
- vs->cbpeer.update.notify = vnc_clipboard_notify;
+ vs->cbpeer.notifier.notify = vnc_clipboard_notify;
vs->cbpeer.request = vnc_clipboard_request;
qemu_clipboard_peer_register(&vs->cbpeer);
}
diff --git a/ui/vnc.c b/ui/vnc.c
index af02522e84..1ed1c7efc6 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -1354,7 +1354,7 @@ void vnc_disconnect_finish(VncState *vs)
/* last client gone */
vnc_update_server_surface(vs->vd);
}
- if (vs->cbpeer.update.notify) {
+ if (vs->cbpeer.notifier.notify) {
qemu_clipboard_peer_unregister(&vs->cbpeer);
}
@@ -2596,7 +2596,7 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
memset(&info, 0, sizeof(info));
info.width = w;
info.height = h;
- dpy_set_ui_info(vs->vd->dcl.con, &info);
+ dpy_set_ui_info(vs->vd->dcl.con, &info, false);
vnc_desktop_resize_ext(vs, 4 /* Request forwarded */);
} else {
vnc_desktop_resize_ext(vs, 3 /* Invalid screen layout */);