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