| 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 |