aboutsummaryrefslogtreecommitdiff
path: root/ui
diff options
context:
space:
mode:
Diffstat (limited to 'ui')
-rw-r--r--ui/dbus-console.c497
-rw-r--r--ui/dbus-display1.xml378
-rw-r--r--ui/dbus-error.c48
-rw-r--r--ui/dbus-listener.c486
-rw-r--r--ui/dbus.c262
-rw-r--r--ui/dbus.h83
-rw-r--r--ui/meson.build22
-rw-r--r--ui/trace-events11
8 files changed, 1787 insertions, 0 deletions
diff --git a/ui/dbus-console.c b/ui/dbus-console.c
new file mode 100644
index 0000000000..1ccf638c10
--- /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 (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
index e69de29bb2..0f0ae92e4d 100644
--- a/ui/dbus-display1.xml
+++ b/ui/dbus-display1.xml
@@ -0,0 +1,378 @@
+<?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>
+</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..20094fc18a
--- /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;
+}
+
+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.c b/ui/dbus.c
new file mode 100644
index 0000000000..12da8ffe31
--- /dev/null
+++ b/ui/dbus.c
@@ -0,0 +1,262 @@
+/*
+ * 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/option.h"
+#include "qom/object_interfaces.h"
+#include "sysemu/sysemu.h"
+#include "ui/egl-helpers.h"
+#include "ui/egl-context.h"
+#include "qapi/error.h"
+#include "trace.h"
+
+#include "dbus.h"
+
+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 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);
+}
+
+static void
+dbus_display_finalize(Object *o)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ g_clear_object(&dd->server);
+ g_clear_pointer(&dd->consoles, g_ptr_array_unref);
+ g_clear_object(&dd->bus);
+ g_clear_object(&dd->iface);
+ g_free(dd->dbus_addr);
+}
+
+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->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;
+ }
+
+
+ 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);
+
+ 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 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 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_str(oc, "addr", get_dbus_addr, set_dbus_addr);
+ object_class_property_add_enum(oc, "gl-mode",
+ "DisplayGLMode", &DisplayGLMode_lookup,
+ get_gl_mode, set_gl_mode);
+}
+
+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;
+ }
+}
+
+static void
+dbus_init(DisplayState *ds, DisplayOptions *opts)
+{
+ DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAYGL_MODE_OFF;
+
+ object_new_with_props(TYPE_DBUS_DISPLAY,
+ object_get_objects_root(),
+ "dbus-display", &error_fatal,
+ "addr", opts->u.dbus.addr ?: "",
+ "gl-mode", DisplayGLMode_str(mode),
+ 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)
+{
+ 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..d3c9598dd1
--- /dev/null
+++ b/ui/dbus.h
@@ -0,0 +1,83 @@
+/*
+ * 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 "qemu/dbus.h"
+#include "qom/object.h"
+#include "ui/console.h"
+
+#include "dbus-display1.h"
+
+struct DBusDisplay {
+ Object parent;
+
+ DisplayGLMode gl_mode;
+ char *dbus_addr;
+ DisplayGLCtx glctx;
+
+ GDBusConnection *bus;
+ GDBusObjectManagerServer *server;
+ QemuDBusDisplay1VM *iface;
+ GPtrArray *consoles;
+};
+
+#define TYPE_DBUS_DISPLAY "dbus-display"
+OBJECT_DECLARE_SIMPLE_TYPE(DBusDisplay, DBUS_DISPLAY)
+
+#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;
+
+#endif /* UI_DBUS_H_ */
diff --git a/ui/meson.build b/ui/meson.build
index a9df5b911e..6270aa768b 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -65,6 +65,28 @@ 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-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/trace-events b/ui/trace-events
index e832c3e365..b1ae30159a 100644
--- a/ui/trace-events
+++ b/ui/trace-events
@@ -136,3 +136,14 @@ 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"