qemu/ui/dbus.c
Marc-André Lureau 589089feee ui/dbus: fix texture sharing
The DBus listener naively create, update and destroy textures without
taking into account other listeners. The texture were shared, but
texture update was unnecessarily duplicated.

Teach DisplayGLCtx to do optionally shared texture handling. This is
only implemented for DBus display at this point, however the same
infrastructure could potentially be used for other future combinations.

Reported-by: Akihiko Odaki <akihiko.odaki@gmail.com>
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Acked-by: Gerd Hoffmann <kraxel@redhat.com>
2022-03-15 12:54:55 +04:00

516 lines
14 KiB
C

/*
* 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 bool
dbus_is_compatible_dcl(DisplayGLCtx *dgc,
DisplayChangeListener *dcl)
{
return dcl->ops == &dbus_gl_dcl_ops || dcl->ops == &dbus_console_dcl_ops;
}
static void
dbus_create_texture(DisplayGLCtx *ctx, DisplaySurface *surface)
{
surface_gl_create_texture(ctx->gls, surface);
}
static void
dbus_destroy_texture(DisplayGLCtx *ctx, DisplaySurface *surface)
{
surface_gl_destroy_texture(ctx->gls, surface);
}
static void
dbus_update_texture(DisplayGLCtx *ctx, DisplaySurface *surface,
int x, int y, int w, int h)
{
surface_gl_update_texture(ctx->gls, surface, x, y, w, h);
}
static const DisplayGLCtxOps dbus_gl_ops = {
.dpy_gl_ctx_is_compatible_dcl = dbus_is_compatible_dcl,
.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,
.dpy_gl_ctx_create_texture = dbus_create_texture,
.dpy_gl_ctx_destroy_texture = dbus_destroy_texture,
.dpy_gl_ctx_update_texture = dbus_update_texture,
};
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;
if (display_opengl) {
dd->glctx.gls = qemu_gl_init_shader();
}
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);
g_clear_pointer(&dd->glctx.gls, qemu_gl_fini_shader);
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_size = sizeof(DBusVCClass),
.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