]>
Commit | Line | Data |
---|---|---|
142ca628 MAL |
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" | |
99997823 | 25 | #include "qemu/cutils.h" |
5feed38c | 26 | #include "qemu/error-report.h" |
142ca628 | 27 | #include "qemu/dbus.h" |
ff1a5810 | 28 | #include "qemu/main-loop.h" |
142ca628 MAL |
29 | #include "qemu/option.h" |
30 | #include "qom/object_interfaces.h" | |
31 | #include "sysemu/sysemu.h" | |
99997823 | 32 | #include "ui/dbus-module.h" |
84a0a2ef | 33 | #ifdef CONFIG_OPENGL |
142ca628 MAL |
34 | #include "ui/egl-helpers.h" |
35 | #include "ui/egl-context.h" | |
84a0a2ef | 36 | #endif |
739362d4 MAL |
37 | #include "audio/audio.h" |
38 | #include "audio/audio_int.h" | |
142ca628 MAL |
39 | #include "qapi/error.h" |
40 | #include "trace.h" | |
41 | ||
42 | #include "dbus.h" | |
43 | ||
99997823 MAL |
44 | static DBusDisplay *dbus_display; |
45 | ||
84a0a2ef | 46 | #ifdef CONFIG_OPENGL |
142ca628 MAL |
47 | static QEMUGLContext dbus_create_context(DisplayGLCtx *dgc, |
48 | QEMUGLParams *params) | |
49 | { | |
50 | eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, | |
51 | qemu_egl_rn_ctx); | |
52 | return qemu_egl_create_context(dgc, params); | |
53 | } | |
54 | ||
a62c4a17 MAL |
55 | static bool |
56 | dbus_is_compatible_dcl(DisplayGLCtx *dgc, | |
57 | DisplayChangeListener *dcl) | |
58 | { | |
4814d3cb | 59 | return |
4814d3cb | 60 | dcl->ops == &dbus_gl_dcl_ops || |
4814d3cb | 61 | dcl->ops == &dbus_console_dcl_ops; |
a62c4a17 MAL |
62 | } |
63 | ||
589089fe MAL |
64 | static void |
65 | dbus_create_texture(DisplayGLCtx *ctx, DisplaySurface *surface) | |
66 | { | |
67 | surface_gl_create_texture(ctx->gls, surface); | |
68 | } | |
69 | ||
70 | static void | |
71 | dbus_destroy_texture(DisplayGLCtx *ctx, DisplaySurface *surface) | |
72 | { | |
73 | surface_gl_destroy_texture(ctx->gls, surface); | |
74 | } | |
75 | ||
76 | static void | |
77 | dbus_update_texture(DisplayGLCtx *ctx, DisplaySurface *surface, | |
78 | int x, int y, int w, int h) | |
79 | { | |
80 | surface_gl_update_texture(ctx->gls, surface, x, y, w, h); | |
81 | } | |
82 | ||
142ca628 | 83 | static const DisplayGLCtxOps dbus_gl_ops = { |
a62c4a17 | 84 | .dpy_gl_ctx_is_compatible_dcl = dbus_is_compatible_dcl, |
142ca628 MAL |
85 | .dpy_gl_ctx_create = dbus_create_context, |
86 | .dpy_gl_ctx_destroy = qemu_egl_destroy_context, | |
87 | .dpy_gl_ctx_make_current = qemu_egl_make_context_current, | |
589089fe MAL |
88 | .dpy_gl_ctx_create_texture = dbus_create_texture, |
89 | .dpy_gl_ctx_destroy_texture = dbus_destroy_texture, | |
90 | .dpy_gl_ctx_update_texture = dbus_update_texture, | |
142ca628 | 91 | }; |
84a0a2ef | 92 | #endif |
142ca628 | 93 | |
3e301c8d MAL |
94 | static NotifierList dbus_display_notifiers = |
95 | NOTIFIER_LIST_INITIALIZER(dbus_display_notifiers); | |
96 | ||
97 | void | |
98 | dbus_display_notifier_add(Notifier *notifier) | |
99 | { | |
100 | notifier_list_add(&dbus_display_notifiers, notifier); | |
101 | } | |
102 | ||
103 | static void | |
104 | dbus_display_notifier_remove(Notifier *notifier) | |
105 | { | |
106 | notifier_remove(notifier); | |
107 | } | |
108 | ||
109 | void | |
110 | dbus_display_notify(DBusDisplayEvent *event) | |
111 | { | |
112 | notifier_list_notify(&dbus_display_notifiers, event); | |
113 | } | |
114 | ||
142ca628 MAL |
115 | static void |
116 | dbus_display_init(Object *o) | |
117 | { | |
118 | DBusDisplay *dd = DBUS_DISPLAY(o); | |
119 | g_autoptr(GDBusObjectSkeleton) vm = NULL; | |
120 | ||
84a0a2ef | 121 | #ifdef CONFIG_OPENGL |
142ca628 | 122 | dd->glctx.ops = &dbus_gl_ops; |
589089fe MAL |
123 | if (display_opengl) { |
124 | dd->glctx.gls = qemu_gl_init_shader(); | |
125 | } | |
84a0a2ef | 126 | #endif |
142ca628 MAL |
127 | dd->iface = qemu_dbus_display1_vm_skeleton_new(); |
128 | dd->consoles = g_ptr_array_new_with_free_func(g_object_unref); | |
129 | ||
130 | dd->server = g_dbus_object_manager_server_new(DBUS_DISPLAY1_ROOT); | |
131 | ||
132 | vm = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/VM"); | |
133 | g_dbus_object_skeleton_add_interface( | |
134 | vm, G_DBUS_INTERFACE_SKELETON(dd->iface)); | |
135 | g_dbus_object_manager_server_export(dd->server, vm); | |
ff1a5810 MAL |
136 | |
137 | dbus_clipboard_init(dd); | |
3e301c8d | 138 | dbus_chardev_init(dd); |
142ca628 MAL |
139 | } |
140 | ||
141 | static void | |
142 | dbus_display_finalize(Object *o) | |
143 | { | |
144 | DBusDisplay *dd = DBUS_DISPLAY(o); | |
145 | ||
3e301c8d MAL |
146 | if (dd->notifier.notify) { |
147 | dbus_display_notifier_remove(&dd->notifier); | |
148 | } | |
149 | ||
ff1a5810 MAL |
150 | qemu_clipboard_peer_unregister(&dd->clipboard_peer); |
151 | g_clear_object(&dd->clipboard); | |
152 | ||
142ca628 MAL |
153 | g_clear_object(&dd->server); |
154 | g_clear_pointer(&dd->consoles, g_ptr_array_unref); | |
99997823 MAL |
155 | if (dd->add_client_cancellable) { |
156 | g_cancellable_cancel(dd->add_client_cancellable); | |
157 | } | |
158 | g_clear_object(&dd->add_client_cancellable); | |
142ca628 MAL |
159 | g_clear_object(&dd->bus); |
160 | g_clear_object(&dd->iface); | |
161 | g_free(dd->dbus_addr); | |
739362d4 | 162 | g_free(dd->audiodev); |
84a0a2ef | 163 | #ifdef CONFIG_OPENGL |
589089fe | 164 | g_clear_pointer(&dd->glctx.gls, qemu_gl_fini_shader); |
84a0a2ef | 165 | #endif |
99997823 | 166 | dbus_display = NULL; |
142ca628 MAL |
167 | } |
168 | ||
169 | static bool | |
170 | dbus_display_add_console(DBusDisplay *dd, int idx, Error **errp) | |
171 | { | |
172 | QemuConsole *con; | |
173 | DBusDisplayConsole *dbus_console; | |
174 | ||
175 | con = qemu_console_lookup_by_index(idx); | |
176 | assert(con); | |
177 | ||
178 | if (qemu_console_is_graphic(con) && | |
179 | dd->gl_mode != DISPLAYGL_MODE_OFF) { | |
180 | qemu_console_set_display_gl_ctx(con, &dd->glctx); | |
181 | } | |
182 | ||
183 | dbus_console = dbus_display_console_new(dd, con); | |
184 | g_ptr_array_insert(dd->consoles, idx, dbus_console); | |
185 | g_dbus_object_manager_server_export(dd->server, | |
186 | G_DBUS_OBJECT_SKELETON(dbus_console)); | |
187 | return true; | |
188 | } | |
189 | ||
190 | static void | |
191 | dbus_display_complete(UserCreatable *uc, Error **errp) | |
192 | { | |
193 | DBusDisplay *dd = DBUS_DISPLAY(uc); | |
194 | g_autoptr(GError) err = NULL; | |
195 | g_autofree char *uuid = qemu_uuid_unparse_strdup(&qemu_uuid); | |
196 | g_autoptr(GArray) consoles = NULL; | |
197 | GVariant *console_ids; | |
198 | int idx; | |
199 | ||
200 | if (!object_resolve_path_type("", TYPE_DBUS_DISPLAY, NULL)) { | |
201 | error_setg(errp, "There is already an instance of %s", | |
202 | TYPE_DBUS_DISPLAY); | |
203 | return; | |
204 | } | |
205 | ||
99997823 MAL |
206 | if (dd->p2p) { |
207 | /* wait for dbus_display_add_client() */ | |
208 | dbus_display = dd; | |
209 | } else if (dd->dbus_addr && *dd->dbus_addr) { | |
142ca628 MAL |
210 | dd->bus = g_dbus_connection_new_for_address_sync(dd->dbus_addr, |
211 | G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | | |
212 | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, | |
213 | NULL, NULL, &err); | |
214 | } else { | |
215 | dd->bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &err); | |
216 | } | |
217 | if (err) { | |
218 | error_setg(errp, "failed to connect to DBus: %s", err->message); | |
219 | return; | |
220 | } | |
221 | ||
739362d4 MAL |
222 | if (dd->audiodev && *dd->audiodev) { |
223 | AudioState *audio_state = audio_state_by_name(dd->audiodev); | |
224 | if (!audio_state) { | |
225 | error_setg(errp, "Audiodev '%s' not found", dd->audiodev); | |
226 | return; | |
227 | } | |
228 | if (!g_str_equal(audio_state->drv->name, "dbus")) { | |
229 | error_setg(errp, "Audiodev '%s' is not compatible with DBus", | |
230 | dd->audiodev); | |
231 | return; | |
232 | } | |
e74fec9a | 233 | audio_state->drv->set_dbus_server(audio_state, dd->server, dd->p2p); |
739362d4 | 234 | } |
142ca628 MAL |
235 | |
236 | consoles = g_array_new(FALSE, FALSE, sizeof(guint32)); | |
237 | for (idx = 0;; idx++) { | |
238 | if (!qemu_console_lookup_by_index(idx)) { | |
239 | break; | |
240 | } | |
241 | if (!dbus_display_add_console(dd, idx, errp)) { | |
242 | return; | |
243 | } | |
244 | g_array_append_val(consoles, idx); | |
245 | } | |
246 | ||
247 | console_ids = g_variant_new_from_data( | |
248 | G_VARIANT_TYPE("au"), | |
249 | consoles->data, consoles->len * sizeof(guint32), TRUE, | |
250 | (GDestroyNotify)g_array_unref, consoles); | |
251 | g_steal_pointer(&consoles); | |
252 | g_object_set(dd->iface, | |
253 | "name", qemu_name ?: "QEMU " QEMU_VERSION, | |
254 | "uuid", uuid, | |
255 | "console-ids", console_ids, | |
256 | NULL); | |
257 | ||
99997823 MAL |
258 | if (dd->bus) { |
259 | g_dbus_object_manager_server_set_connection(dd->server, dd->bus); | |
260 | g_bus_own_name_on_connection(dd->bus, "org.qemu", | |
261 | G_BUS_NAME_OWNER_FLAGS_NONE, | |
262 | NULL, NULL, NULL, NULL); | |
263 | } | |
264 | } | |
265 | ||
266 | static void | |
267 | dbus_display_add_client_ready(GObject *source_object, | |
268 | GAsyncResult *res, | |
269 | gpointer user_data) | |
270 | { | |
271 | g_autoptr(GError) err = NULL; | |
272 | g_autoptr(GDBusConnection) conn = NULL; | |
273 | ||
274 | g_clear_object(&dbus_display->add_client_cancellable); | |
275 | ||
276 | conn = g_dbus_connection_new_finish(res, &err); | |
277 | if (!conn) { | |
278 | error_printf("Failed to accept D-Bus client: %s", err->message); | |
279 | } | |
280 | ||
281 | g_dbus_object_manager_server_set_connection(dbus_display->server, conn); | |
c8ddcdd6 | 282 | g_dbus_connection_start_message_processing(conn); |
99997823 MAL |
283 | } |
284 | ||
285 | ||
286 | static bool | |
287 | dbus_display_add_client(int csock, Error **errp) | |
288 | { | |
289 | g_autoptr(GError) err = NULL; | |
290 | g_autoptr(GSocket) socket = NULL; | |
291 | g_autoptr(GSocketConnection) conn = NULL; | |
292 | g_autofree char *guid = g_dbus_generate_guid(); | |
293 | ||
294 | if (!dbus_display) { | |
295 | error_setg(errp, "p2p connections not accepted in bus mode"); | |
296 | return false; | |
297 | } | |
298 | ||
299 | if (dbus_display->add_client_cancellable) { | |
300 | g_cancellable_cancel(dbus_display->add_client_cancellable); | |
301 | } | |
302 | ||
74bc00c6 MAL |
303 | #ifdef WIN32 |
304 | socket = g_socket_new_from_fd(_get_osfhandle(csock), &err); | |
305 | #else | |
99997823 | 306 | socket = g_socket_new_from_fd(csock, &err); |
74bc00c6 | 307 | #endif |
99997823 MAL |
308 | if (!socket) { |
309 | error_setg(errp, "Failed to setup D-Bus socket: %s", err->message); | |
74bc00c6 | 310 | close(csock); |
99997823 MAL |
311 | return false; |
312 | } | |
74bc00c6 MAL |
313 | #ifdef WIN32 |
314 | /* socket owns the SOCKET handle now, so release our osf handle */ | |
315 | qemu_close_socket_osfhandle(csock); | |
316 | #endif | |
99997823 MAL |
317 | |
318 | conn = g_socket_connection_factory_create_connection(socket); | |
319 | ||
320 | dbus_display->add_client_cancellable = g_cancellable_new(); | |
321 | ||
322 | g_dbus_connection_new(G_IO_STREAM(conn), | |
323 | guid, | |
c8ddcdd6 MAL |
324 | G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER | |
325 | G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING, | |
99997823 MAL |
326 | NULL, |
327 | dbus_display->add_client_cancellable, | |
328 | dbus_display_add_client_ready, | |
329 | NULL); | |
330 | ||
331 | return true; | |
332 | } | |
333 | ||
334 | static bool | |
335 | get_dbus_p2p(Object *o, Error **errp) | |
336 | { | |
337 | DBusDisplay *dd = DBUS_DISPLAY(o); | |
338 | ||
339 | return dd->p2p; | |
340 | } | |
341 | ||
342 | static void | |
343 | set_dbus_p2p(Object *o, bool p2p, Error **errp) | |
344 | { | |
345 | DBusDisplay *dd = DBUS_DISPLAY(o); | |
346 | ||
347 | dd->p2p = p2p; | |
142ca628 MAL |
348 | } |
349 | ||
350 | static char * | |
351 | get_dbus_addr(Object *o, Error **errp) | |
352 | { | |
353 | DBusDisplay *dd = DBUS_DISPLAY(o); | |
354 | ||
355 | return g_strdup(dd->dbus_addr); | |
356 | } | |
357 | ||
358 | static void | |
359 | set_dbus_addr(Object *o, const char *str, Error **errp) | |
360 | { | |
361 | DBusDisplay *dd = DBUS_DISPLAY(o); | |
362 | ||
363 | g_free(dd->dbus_addr); | |
364 | dd->dbus_addr = g_strdup(str); | |
365 | } | |
366 | ||
739362d4 MAL |
367 | static char * |
368 | get_audiodev(Object *o, Error **errp) | |
369 | { | |
370 | DBusDisplay *dd = DBUS_DISPLAY(o); | |
371 | ||
372 | return g_strdup(dd->audiodev); | |
373 | } | |
374 | ||
375 | static void | |
376 | set_audiodev(Object *o, const char *str, Error **errp) | |
377 | { | |
378 | DBusDisplay *dd = DBUS_DISPLAY(o); | |
379 | ||
380 | g_free(dd->audiodev); | |
381 | dd->audiodev = g_strdup(str); | |
382 | } | |
383 | ||
ff1a5810 | 384 | |
142ca628 MAL |
385 | static int |
386 | get_gl_mode(Object *o, Error **errp) | |
387 | { | |
388 | DBusDisplay *dd = DBUS_DISPLAY(o); | |
389 | ||
390 | return dd->gl_mode; | |
391 | } | |
392 | ||
393 | static void | |
394 | set_gl_mode(Object *o, int val, Error **errp) | |
395 | { | |
396 | DBusDisplay *dd = DBUS_DISPLAY(o); | |
397 | ||
398 | dd->gl_mode = val; | |
399 | } | |
400 | ||
401 | static void | |
402 | dbus_display_class_init(ObjectClass *oc, void *data) | |
403 | { | |
404 | UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); | |
405 | ||
406 | ucc->complete = dbus_display_complete; | |
99997823 | 407 | object_class_property_add_bool(oc, "p2p", get_dbus_p2p, set_dbus_p2p); |
142ca628 | 408 | object_class_property_add_str(oc, "addr", get_dbus_addr, set_dbus_addr); |
739362d4 | 409 | object_class_property_add_str(oc, "audiodev", get_audiodev, set_audiodev); |
142ca628 MAL |
410 | object_class_property_add_enum(oc, "gl-mode", |
411 | "DisplayGLMode", &DisplayGLMode_lookup, | |
412 | get_gl_mode, set_gl_mode); | |
413 | } | |
414 | ||
7f767ca3 MAL |
415 | #define TYPE_CHARDEV_VC "chardev-vc" |
416 | ||
417 | typedef struct DBusVCClass { | |
418 | DBusChardevClass parent_class; | |
419 | ||
420 | void (*parent_parse)(QemuOpts *opts, ChardevBackend *b, Error **errp); | |
421 | } DBusVCClass; | |
422 | ||
423 | DECLARE_CLASS_CHECKERS(DBusVCClass, DBUS_VC, | |
424 | TYPE_CHARDEV_VC) | |
425 | ||
426 | static void | |
427 | dbus_vc_parse(QemuOpts *opts, ChardevBackend *backend, | |
428 | Error **errp) | |
429 | { | |
430 | DBusVCClass *klass = DBUS_VC_CLASS(object_class_by_name(TYPE_CHARDEV_VC)); | |
431 | const char *name = qemu_opt_get(opts, "name"); | |
432 | const char *id = qemu_opts_id(opts); | |
433 | ||
434 | if (name == NULL) { | |
435 | if (g_str_has_prefix(id, "compat_monitor")) { | |
436 | name = "org.qemu.monitor.hmp.0"; | |
437 | } else if (g_str_has_prefix(id, "serial")) { | |
438 | name = "org.qemu.console.serial.0"; | |
439 | } else { | |
440 | name = ""; | |
441 | } | |
442 | if (!qemu_opt_set(opts, "name", name, errp)) { | |
443 | return; | |
444 | } | |
445 | } | |
446 | ||
447 | klass->parent_parse(opts, backend, errp); | |
448 | } | |
449 | ||
450 | static void | |
451 | dbus_vc_class_init(ObjectClass *oc, void *data) | |
452 | { | |
453 | DBusVCClass *klass = DBUS_VC_CLASS(oc); | |
454 | ChardevClass *cc = CHARDEV_CLASS(oc); | |
455 | ||
456 | klass->parent_parse = cc->parse; | |
457 | cc->parse = dbus_vc_parse; | |
458 | } | |
459 | ||
460 | static const TypeInfo dbus_vc_type_info = { | |
461 | .name = TYPE_CHARDEV_VC, | |
462 | .parent = TYPE_CHARDEV_DBUS, | |
fc94d115 | 463 | .class_size = sizeof(DBusVCClass), |
7f767ca3 MAL |
464 | .class_init = dbus_vc_class_init, |
465 | }; | |
466 | ||
142ca628 MAL |
467 | static void |
468 | early_dbus_init(DisplayOptions *opts) | |
469 | { | |
470 | DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAYGL_MODE_OFF; | |
471 | ||
472 | if (mode != DISPLAYGL_MODE_OFF) { | |
4814d3cb | 473 | #ifdef CONFIG_OPENGL |
0e1be59e | 474 | egl_init(opts->u.dbus.rendernode, mode, &error_fatal); |
4814d3cb MAL |
475 | #else |
476 | error_report("dbus: GL rendering is not supported"); | |
477 | #endif | |
142ca628 | 478 | } |
7f767ca3 MAL |
479 | |
480 | type_register(&dbus_vc_type_info); | |
142ca628 MAL |
481 | } |
482 | ||
483 | static void | |
484 | dbus_init(DisplayState *ds, DisplayOptions *opts) | |
485 | { | |
486 | DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAYGL_MODE_OFF; | |
487 | ||
99997823 MAL |
488 | if (opts->u.dbus.addr && opts->u.dbus.p2p) { |
489 | error_report("dbus: can't accept both addr=X and p2p=yes options"); | |
490 | exit(1); | |
491 | } | |
492 | ||
493 | using_dbus_display = 1; | |
494 | ||
142ca628 MAL |
495 | object_new_with_props(TYPE_DBUS_DISPLAY, |
496 | object_get_objects_root(), | |
497 | "dbus-display", &error_fatal, | |
498 | "addr", opts->u.dbus.addr ?: "", | |
739362d4 | 499 | "audiodev", opts->u.dbus.audiodev ?: "", |
142ca628 | 500 | "gl-mode", DisplayGLMode_str(mode), |
99997823 | 501 | "p2p", yes_no(opts->u.dbus.p2p), |
142ca628 MAL |
502 | NULL); |
503 | } | |
504 | ||
505 | static const TypeInfo dbus_display_info = { | |
506 | .name = TYPE_DBUS_DISPLAY, | |
507 | .parent = TYPE_OBJECT, | |
508 | .instance_size = sizeof(DBusDisplay), | |
509 | .instance_init = dbus_display_init, | |
510 | .instance_finalize = dbus_display_finalize, | |
511 | .class_init = dbus_display_class_init, | |
512 | .interfaces = (InterfaceInfo[]) { | |
513 | { TYPE_USER_CREATABLE }, | |
514 | { } | |
515 | } | |
516 | }; | |
517 | ||
518 | static QemuDisplay qemu_display_dbus = { | |
519 | .type = DISPLAY_TYPE_DBUS, | |
520 | .early_init = early_dbus_init, | |
521 | .init = dbus_init, | |
522 | }; | |
523 | ||
524 | static void register_dbus(void) | |
525 | { | |
99997823 MAL |
526 | qemu_dbus_display = (struct QemuDBusDisplayOps) { |
527 | .add_client = dbus_display_add_client, | |
528 | }; | |
142ca628 MAL |
529 | type_register_static(&dbus_display_info); |
530 | qemu_display_register(&qemu_display_dbus); | |
531 | } | |
532 | ||
533 | type_init(register_dbus); | |
534 | ||
535 | #ifdef CONFIG_OPENGL | |
536 | module_dep("ui-opengl"); | |
537 | #endif |