master
c 710 lines 19.6 KB
Raw
1 /*
2 * QEMU DBus display
3 *
4 * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 #include "qemu/osdep.h"
25 #include "qemu/cutils.h"
26 #include "qemu/error-report.h"
27 #include "qemu/dbus.h"
28 #include "qemu/main-loop.h"
29 #include "qemu/option.h"
30 #include "qom/object_interfaces.h"
31 #include "qapi-types-char.h"
32 #include "system/system.h"
33 #include "ui/dbus-module.h"
34 #ifdef CONFIG_OPENGL
35 #include "ui/egl-helpers.h"
36 #include "ui/egl-context.h"
37 #endif
38 #include "qemu/audio.h"
39 #include "audio/audio_int.h" /* FIXME: use QOM dynamic cast instead of drv->name */
40 #include "qapi/error.h"
41 #include "trace.h"
42
43 #include "dbus.h"
44
45 static DBusDisplay *dbus_display;
46
47 #ifdef CONFIG_OPENGL
48 static QEMUGLContext dbus_create_context(DisplayGLCtx *dgc,
49 QEMUGLParams *params)
50 {
51 return qemu_egl_create_context(dgc, params, qemu_egl_rn_ctx);
52 }
53
54 static bool
55 dbus_is_compatible_dcl(DisplayGLCtx *dgc,
56 DisplayChangeListener *dcl)
57 {
58 return
59 dcl->ops == &dbus_gl_dcl_ops ||
60 dcl->ops == &dbus_console_dcl_ops;
61 }
62
63 static void
64 dbus_create_texture(DisplayGLCtx *ctx, DisplaySurface *surface)
65 {
66 surface_gl_create_texture(ctx->gls, surface);
67 }
68
69 static void
70 dbus_destroy_texture(DisplayGLCtx *ctx, DisplaySurface *surface)
71 {
72 surface_gl_destroy_texture(ctx->gls, surface);
73 }
74
75 static void
76 dbus_update_texture(DisplayGLCtx *ctx, DisplaySurface *surface,
77 int x, int y, int w, int h)
78 {
79 surface_gl_update_texture(ctx->gls, surface, x, y, w, h);
80 }
81
82 static const DisplayGLCtxOps dbus_gl_ops = {
83 .dpy_gl_ctx_is_compatible_dcl = dbus_is_compatible_dcl,
84 .dpy_gl_ctx_create = dbus_create_context,
85 .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
86 .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
87 .dpy_gl_ctx_create_texture = dbus_create_texture,
88 .dpy_gl_ctx_destroy_texture = dbus_destroy_texture,
89 .dpy_gl_ctx_update_texture = dbus_update_texture,
90 };
91 #endif
92
93 static NotifierList dbus_display_notifiers =
94 NOTIFIER_LIST_INITIALIZER(dbus_display_notifiers);
95
96 void
97 dbus_display_notifier_add(Notifier *notifier)
98 {
99 notifier_list_add(&dbus_display_notifiers, notifier);
100 }
101
102 static void
103 dbus_display_notifier_remove(Notifier *notifier)
104 {
105 notifier_remove(notifier);
106 }
107
108 void
109 dbus_display_notify(DBusDisplayEvent *event)
110 {
111 notifier_list_notify(&dbus_display_notifiers, event);
112 }
113
114 static void
115 dbus_display_init(Object *o)
116 {
117 DBusDisplay *dd = DBUS_DISPLAY(o);
118 g_autoptr(GDBusObjectSkeleton) vm = NULL;
119
120 #ifdef CONFIG_OPENGL
121 dd->glctx.ops = &dbus_gl_ops;
122 if (display_opengl) {
123 dd->glctx.gls = qemu_gl_init_shader();
124 }
125 #endif
126 dd->iface = qemu_dbus_display1_vm_skeleton_new();
127 dd->consoles = g_ptr_array_new_with_free_func(g_object_unref);
128
129 dd->server = g_dbus_object_manager_server_new(DBUS_DISPLAY1_ROOT);
130
131 vm = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/VM");
132 g_dbus_object_skeleton_add_interface(
133 vm, G_DBUS_INTERFACE_SKELETON(dd->iface));
134 g_dbus_object_manager_server_export(dd->server, vm);
135
136 dbus_clipboard_init(dd);
137 dbus_chardev_init(dd);
138 }
139
140 static void
141 dbus_display_finalize(Object *o)
142 {
143 DBusDisplay *dd = DBUS_DISPLAY(o);
144
145 if (dd->console_notifier.notify) {
146 qemu_console_remove_notifier(&dd->console_notifier);
147 }
148 if (dd->notifier.notify) {
149 dbus_display_notifier_remove(&dd->notifier);
150 }
151
152 dbus_clipboard_fini(dd);
153
154 g_clear_object(&dd->server);
155 g_clear_pointer(&dd->consoles, g_ptr_array_unref);
156 if (dd->add_client_cancellable) {
157 g_cancellable_cancel(dd->add_client_cancellable);
158 }
159 g_clear_object(&dd->add_client_cancellable);
160 g_clear_object(&dd->bus);
161 g_clear_object(&dd->iface);
162 g_free(dd->dbus_addr);
163 g_free(dd->audiodev);
164 #ifdef CONFIG_OPENGL
165 g_clear_pointer(&dd->glctx.gls, qemu_gl_fini_shader);
166 #endif
167 dbus_display = NULL;
168 }
169
170 static void
171 dbus_update_console_ids(DBusDisplay *dd)
172 {
173 g_autoptr(GArray) arr = g_array_new(FALSE, FALSE, sizeof(guint32));
174
175 for (guint i = 0; i < dd->consoles->len; i++) {
176 DBusDisplayConsole *ddc = g_ptr_array_index(dd->consoles, i);
177 guint32 idx = dbus_display_console_get_index(ddc);
178 g_array_append_val(arr, idx);
179 }
180
181 g_object_set(dd->iface, "console-ids",
182 g_variant_new_fixed_array(G_VARIANT_TYPE("u"),
183 arr->data, arr->len,
184 sizeof(guint32)),
185 NULL);
186 }
187
188 static bool
189 dbus_display_add_console(DBusDisplay *dd, QemuConsole *con, Error **errp)
190 {
191 DBusDisplayConsole *dbus_console;
192
193 for (guint i = 0; i < dd->consoles->len; i++) {
194 DBusDisplayConsole *ddc = g_ptr_array_index(dd->consoles, i);
195 if (dbus_display_console_get_qemu_console(ddc) == con) {
196 return true;
197 }
198 }
199
200 if (qemu_console_is_graphic(con) &&
201 dd->gl_mode != DISPLAY_GL_MODE_OFF) {
202 qemu_console_set_display_gl_ctx(con, &dd->glctx);
203 }
204
205 dbus_console = dbus_display_console_new(dd, con);
206 g_ptr_array_add(dd->consoles, dbus_console);
207 g_dbus_object_manager_server_export(dd->server,
208 G_DBUS_OBJECT_SKELETON(dbus_console));
209 dbus_update_console_ids(dd);
210 return true;
211 }
212
213 static void
214 dbus_display_remove_console(DBusDisplay *dd, QemuConsole *con)
215 {
216 for (guint i = 0; i < dd->consoles->len; i++) {
217 DBusDisplayConsole *ddc = g_ptr_array_index(dd->consoles, i);
218 if (dbus_display_console_get_qemu_console(ddc) == con) {
219 if (display_opengl) {
220 qemu_console_set_display_gl_ctx(con, NULL);
221 }
222 g_dbus_object_manager_server_unexport(
223 dd->server,
224 g_dbus_object_get_object_path(G_DBUS_OBJECT(ddc)));
225 g_ptr_array_remove_index(dd->consoles, i);
226 dbus_update_console_ids(dd);
227 break;
228 }
229 }
230 }
231
232 static void
233 dbus_console_notify(Notifier *n, void *data)
234 {
235 DBusDisplay *dd = container_of(n, DBusDisplay, console_notifier);
236 QemuConsoleEvent *event = data;
237
238 switch (event->type) {
239 case QEMU_CONSOLE_ADDED: {
240 Error *err = NULL;
241 if (!dbus_display_add_console(dd, event->con, &err)) {
242 error_report_err(err);
243 }
244 break;
245 }
246 case QEMU_CONSOLE_REMOVED:
247 dbus_display_remove_console(dd, event->con);
248 break;
249 }
250 }
251
252 static void
253 dbus_display_complete(UserCreatable *uc, Error **errp)
254 {
255 DBusDisplay *dd = DBUS_DISPLAY(uc);
256 g_autoptr(GError) err = NULL;
257 g_autofree char *uuid = qemu_uuid_unparse_strdup(&qemu_uuid);
258 int idx;
259
260 if (!object_resolve_path_type("", TYPE_DBUS_DISPLAY, NULL)) {
261 error_setg(errp, "There is already an instance of %s",
262 TYPE_DBUS_DISPLAY);
263 return;
264 }
265
266 if (dd->p2p) {
267 /* wait for dbus_display_add_client() */
268 dbus_display = dd;
269 } else if (dd->dbus_addr && *dd->dbus_addr) {
270 dd->bus = g_dbus_connection_new_for_address_sync(dd->dbus_addr,
271 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
272 G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
273 NULL, NULL, &err);
274 } else {
275 dd->bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &err);
276 }
277 if (err) {
278 error_setg(errp, "failed to connect to DBus: %s", err->message);
279 return;
280 }
281
282 {
283 AudioBackend *audio_be = audio_get_default_audio_be(NULL);
284 if (audio_be && !audio_be_can_set_dbus_server(audio_be)) {
285 audio_be = NULL;
286 }
287 if (dd->audiodev && *dd->audiodev) {
288 audio_be = audio_be_by_name(dd->audiodev, errp);
289 if (!audio_be) {
290 return;
291 }
292 }
293 if (audio_be && !audio_be_set_dbus_server(audio_be, dd->server, dd->p2p, errp)) {
294 return;
295 }
296 }
297
298 for (idx = 0;; idx++) {
299 QemuConsole *con = qemu_console_lookup_by_index(idx);
300 if (!con) {
301 break;
302 }
303 if (!dbus_display_add_console(dd, con, errp)) {
304 return;
305 }
306 }
307
308 g_object_set(dd->iface,
309 "name", qemu_name ?: "QEMU " QEMU_VERSION,
310 "uuid", uuid,
311 NULL);
312 dbus_update_console_ids(dd);
313
314 dd->console_notifier.notify = dbus_console_notify;
315 qemu_console_add_notifier(&dd->console_notifier);
316
317 if (dd->bus) {
318 g_dbus_object_manager_server_set_connection(dd->server, dd->bus);
319 g_bus_own_name_on_connection(dd->bus, "org.qemu",
320 G_BUS_NAME_OWNER_FLAGS_NONE,
321 NULL, NULL, NULL, NULL);
322 }
323 }
324
325 typedef struct DBusDisplayAddClientData {
326 DBusDisplay *display;
327 GCancellable *cancellable;
328 } DBusDisplayAddClientData;
329
330 static void dbus_display_add_client_data_free(DBusDisplayAddClientData *data)
331 {
332 if (data->display) {
333 object_unref(OBJECT(data->display));
334 data->display = NULL;
335 }
336 g_clear_object(&data->cancellable);
337 g_free(data);
338 }
339
340 G_DEFINE_AUTOPTR_CLEANUP_FUNC(DBusDisplayAddClientData,
341 dbus_display_add_client_data_free)
342
343 static void
344 dbus_display_add_client_ready(GObject *source_object,
345 GAsyncResult *res,
346 gpointer user_data)
347 {
348 g_autoptr(DBusDisplayAddClientData) data = user_data;
349 DBusDisplay *display = data->display;
350 bool current = display->add_client_cancellable == data->cancellable;
351 g_autoptr(GError) err = NULL;
352 g_autoptr(GDBusConnection) conn = NULL;
353
354 if (current) {
355 g_clear_object(&display->add_client_cancellable);
356 }
357
358 conn = g_dbus_connection_new_finish(res, &err);
359 if (!conn) {
360 if (!g_error_matches(err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
361 error_printf("Failed to accept D-Bus client: %s", err->message);
362 }
363 return;
364 }
365
366 if (!current) {
367 return;
368 }
369
370 g_dbus_object_manager_server_set_connection(display->server, conn);
371 g_dbus_connection_start_message_processing(conn);
372 }
373
374
375 static bool
376 dbus_display_add_client(int csock, Error **errp)
377 {
378 g_autoptr(GError) err = NULL;
379 g_autoptr(GSocket) socket = NULL;
380 g_autoptr(GSocketConnection) conn = NULL;
381 g_autofree char *guid = g_dbus_generate_guid();
382 DBusDisplayAddClientData *data;
383
384 if (!dbus_display) {
385 error_setg(errp, "p2p connections not accepted in bus mode");
386 return false;
387 }
388
389 if (dbus_display->add_client_cancellable) {
390 g_cancellable_cancel(dbus_display->add_client_cancellable);
391 g_clear_object(&dbus_display->add_client_cancellable);
392 }
393
394 #ifdef WIN32
395 socket = g_socket_new_from_fd(_get_osfhandle(csock), &err);
396 #else
397 socket = g_socket_new_from_fd(csock, &err);
398 #endif
399 if (!socket) {
400 error_setg(errp, "Failed to setup D-Bus socket: %s", err->message);
401 close(csock);
402 return false;
403 }
404 #ifdef WIN32
405 /* socket owns the SOCKET handle now, so release our osf handle */
406 qemu_close_socket_osfhandle(csock);
407 #endif
408
409 conn = g_socket_connection_factory_create_connection(socket);
410
411 dbus_display->add_client_cancellable = g_cancellable_new();
412 data = g_new0(DBusDisplayAddClientData, 1);
413 data->display = DBUS_DISPLAY(object_ref(OBJECT(dbus_display)));
414 data->cancellable = g_object_ref(dbus_display->add_client_cancellable);
415
416 GDBusConnectionFlags flags =
417 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER |
418 G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING;
419
420 #ifdef WIN32
421 flags |= G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS;
422 #endif
423
424 g_dbus_connection_new(G_IO_STREAM(conn),
425 guid,
426 flags,
427 NULL,
428 dbus_display->add_client_cancellable,
429 dbus_display_add_client_ready,
430 data);
431
432 return true;
433 }
434
435 static bool
436 get_dbus_p2p(Object *o, Error **errp)
437 {
438 DBusDisplay *dd = DBUS_DISPLAY(o);
439
440 return dd->p2p;
441 }
442
443 static void
444 set_dbus_p2p(Object *o, bool p2p, Error **errp)
445 {
446 DBusDisplay *dd = DBUS_DISPLAY(o);
447
448 dd->p2p = p2p;
449 }
450
451 static char *
452 get_dbus_addr(Object *o, Error **errp)
453 {
454 DBusDisplay *dd = DBUS_DISPLAY(o);
455
456 return g_strdup(dd->dbus_addr);
457 }
458
459 static void
460 set_dbus_addr(Object *o, const char *str, Error **errp)
461 {
462 DBusDisplay *dd = DBUS_DISPLAY(o);
463
464 g_free(dd->dbus_addr);
465 dd->dbus_addr = g_strdup(str);
466 }
467
468 static char *
469 get_audiodev(Object *o, Error **errp)
470 {
471 DBusDisplay *dd = DBUS_DISPLAY(o);
472
473 return g_strdup(dd->audiodev);
474 }
475
476 static void
477 set_audiodev(Object *o, const char *str, Error **errp)
478 {
479 DBusDisplay *dd = DBUS_DISPLAY(o);
480
481 g_free(dd->audiodev);
482 dd->audiodev = g_strdup(str);
483 }
484
485
486 static int
487 get_gl_mode(Object *o, Error **errp)
488 {
489 DBusDisplay *dd = DBUS_DISPLAY(o);
490
491 return dd->gl_mode;
492 }
493
494 static void
495 set_gl_mode(Object *o, int val, Error **errp)
496 {
497 DBusDisplay *dd = DBUS_DISPLAY(o);
498
499 dd->gl_mode = val;
500 }
501
502 static void
503 dbus_display_class_init(ObjectClass *oc, const void *data)
504 {
505 UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
506
507 ucc->complete = dbus_display_complete;
508 object_class_property_add_bool(oc, "p2p", get_dbus_p2p, set_dbus_p2p);
509 object_class_property_add_str(oc, "addr", get_dbus_addr, set_dbus_addr);
510 object_class_property_add_str(oc, "audiodev", get_audiodev, set_audiodev);
511 object_class_property_add_enum(oc, "gl-mode",
512 "DisplayGLMode", &DisplayGLMode_lookup,
513 get_gl_mode, set_gl_mode);
514 }
515
516 #define TYPE_CHARDEV_VC "chardev-vc"
517
518 typedef struct DBusVCChardev {
519 DBusChardev parent;
520
521 ChardevVCEncoding encoding;
522 } DBusVCChardev;
523
524 typedef struct DBusVCClass {
525 DBusChardevClass parent_class;
526
527 void (*parent_parse)(QemuOpts *opts, ChardevBackend *b, Error **errp);
528 } DBusVCClass;
529
530 DECLARE_INSTANCE_CHECKER(DBusVCChardev, DBUS_VC_CHARDEV,
531 TYPE_CHARDEV_VC)
532 DECLARE_CLASS_CHECKERS(DBusVCClass, DBUS_VC,
533 TYPE_CHARDEV_VC)
534
535 static void
536 dbus_vc_parse(QemuOpts *opts, ChardevBackend *backend,
537 Error **errp)
538 {
539 DBusVCClass *klass = DBUS_VC_CLASS(object_class_by_name(TYPE_CHARDEV_VC));
540 const char *name = qemu_opt_get(opts, "name");
541 const char *id = qemu_opts_id(opts);
542 const char *str;
543 ChardevDBus *dbus;
544
545 if (name == NULL) {
546 if (g_str_has_prefix(id, "compat_monitor")) {
547 name = "org.qemu.monitor.hmp.0";
548 } else if (g_str_has_prefix(id, "serial")) {
549 name = "org.qemu.console.serial.0";
550 } else {
551 name = "";
552 }
553 if (!qemu_opt_set(opts, "name", name, errp)) {
554 return;
555 }
556 }
557
558 klass->parent_parse(opts, backend, errp);
559 dbus = backend->u.dbus.data;
560 str = qemu_opt_get(opts, "encoding");
561 if (str) {
562 int cs = qapi_enum_parse(&ChardevVCEncoding_lookup, str, -1, errp);
563 if (cs < 0) {
564 return;
565 }
566 dbus->has_encoding = true;
567 dbus->encoding = cs;
568 }
569 }
570
571 CHARDEV_VC_ENCODING_PROPERTY_DEFINE(DBUS_VC_CHARDEV)
572
573 static bool
574 dbus_vc_open(Chardev *chr, ChardevBackend *backend, Error **errp)
575 {
576 DBusChardev *dc = DBUS_CHARDEV(chr);
577 DBusVCChardev *vc = DBUS_VC_CHARDEV(chr);
578 ChardevClass *parent =
579 CHARDEV_CLASS(object_class_by_name(TYPE_CHARDEV_DBUS));
580 ChardevDBus *be = backend->u.dbus.data;
581
582 if (be->has_encoding) {
583 vc->encoding = be->encoding;
584 }
585 dc->iface_vc_encoding =
586 qemu_dbus_display1_chardev_vcencoding_skeleton_new();
587 qemu_dbus_display1_chardev_vcencoding_set_encoding(
588 dc->iface_vc_encoding,
589 qapi_enum_lookup(&ChardevVCEncoding_lookup, vc->encoding));
590
591 return parent->chr_open(chr, backend, errp);
592 }
593
594 static void
595 dbus_vc_class_init(ObjectClass *oc, const void *data)
596 {
597 DBusVCClass *klass = DBUS_VC_CLASS(oc);
598 ChardevClass *cc = CHARDEV_CLASS(oc);
599
600 klass->parent_parse = cc->chr_parse;
601 cc->chr_parse = dbus_vc_parse;
602 cc->chr_open = dbus_vc_open;
603 cc->supports_encoding_opts = true;
604
605 chardev_vc_add_encoding_prop(oc, get_encoding, set_encoding);
606 }
607
608 static void
609 dbus_vc_init(Object *obj)
610 {
611 DBusVCChardev *vc = DBUS_VC_CHARDEV(obj);
612
613 vc->encoding = CHARDEV_VC_ENCODING_UTF8;
614 }
615
616 static const TypeInfo dbus_vc_type_info = {
617 .name = TYPE_CHARDEV_VC,
618 .parent = TYPE_CHARDEV_DBUS,
619 .instance_size = sizeof(DBusVCChardev),
620 .instance_init = dbus_vc_init,
621 .instance_post_init = object_apply_compat_props,
622 .class_size = sizeof(DBusVCClass),
623 .class_init = dbus_vc_class_init,
624 };
625
626 static void
627 early_dbus_init(DisplayOptions *opts)
628 {
629 DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAY_GL_MODE_OFF;
630
631 if (mode != DISPLAY_GL_MODE_OFF) {
632 #ifdef CONFIG_OPENGL
633 egl_init(opts->u.dbus.rendernode, mode, &error_fatal);
634 #else
635 error_report("dbus: GL rendering is not supported");
636 #endif
637 }
638
639 using_dbus_display = 1;
640
641 type_register_static(&dbus_vc_type_info);
642 }
643
644 static void
645 dbus_init(DisplayState *ds, DisplayOptions *opts)
646 {
647 DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAY_GL_MODE_OFF;
648
649 if (opts->u.dbus.addr && opts->u.dbus.p2p) {
650 error_report("dbus: can't accept both addr=X and p2p=yes options");
651 exit(1);
652 }
653
654 object_new_with_props(TYPE_DBUS_DISPLAY,
655 object_get_objects_root(),
656 "dbus-display", &error_fatal,
657 "addr", opts->u.dbus.addr ?: "",
658 "audiodev", opts->u.dbus.audiodev ?: "",
659 "gl-mode", DisplayGLMode_str(mode),
660 "p2p", yes_no(opts->u.dbus.p2p),
661 NULL);
662 }
663
664 static const TypeInfo dbus_display_info = {
665 .name = TYPE_DBUS_DISPLAY,
666 .parent = TYPE_OBJECT,
667 .instance_size = sizeof(DBusDisplay),
668 .instance_init = dbus_display_init,
669 .instance_finalize = dbus_display_finalize,
670 .class_init = dbus_display_class_init,
671 .interfaces = (const InterfaceInfo[]) {
672 { TYPE_USER_CREATABLE },
673 { }
674 }
675 };
676
677 static void
678 dbus_cleanup(void)
679 {
680 Object *o;
681
682 o = object_resolve_path_component(object_get_objects_root(),
683 "dbus-display");
684 if (o) {
685 object_unparent(o);
686 }
687 }
688
689 static QemuDisplay qemu_display_dbus = {
690 .type = DISPLAY_TYPE_DBUS,
691 .early_init = early_dbus_init,
692 .init = dbus_init,
693 .cleanup = dbus_cleanup,
694 .vc = "vc",
695 };
696
697 static void register_dbus(void)
698 {
699 qemu_dbus_display = (struct QemuDBusDisplayOps) {
700 .add_client = dbus_display_add_client,
701 };
702 type_register_static(&dbus_display_info);
703 qemu_display_register(&qemu_display_dbus);
704 }
705
706 type_init(register_dbus);
707
708 #ifdef CONFIG_OPENGL
709 module_dep("ui-opengl");
710 #endif