mirror of
https://gitlab.com/qemu-project/qemu
synced 2024-11-05 20:35:44 +00:00
589089feee
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>
516 lines
14 KiB
C
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
|