xref: /openbmc/qemu/ui/dbus-console.c (revision 4f7b1ecb)
1 /*
2  * QEMU DBus display console
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/error-report.h"
26 #include "qapi/error.h"
27 #include "ui/input.h"
28 #include "ui/kbd-state.h"
29 #include "trace.h"
30 
31 #ifdef G_OS_UNIX
32 #include <gio/gunixfdlist.h>
33 #endif
34 
35 #include "dbus.h"
36 
37 static struct touch_slot touch_slots[INPUT_EVENT_SLOTS_MAX];
38 
39 struct _DBusDisplayConsole {
40     GDBusObjectSkeleton parent_instance;
41     DisplayChangeListener dcl;
42 
43     DBusDisplay *display;
44     GHashTable *listeners;
45     QemuDBusDisplay1Console *iface;
46 
47     QemuDBusDisplay1Keyboard *iface_kbd;
48     QKbdState *kbd;
49 
50     QemuDBusDisplay1Mouse *iface_mouse;
51     QemuDBusDisplay1MultiTouch *iface_touch;
52     gboolean last_set;
53     guint last_x;
54     guint last_y;
55     Notifier mouse_mode_notifier;
56 };
57 
58 G_DEFINE_TYPE(DBusDisplayConsole,
59               dbus_display_console,
60               G_TYPE_DBUS_OBJECT_SKELETON)
61 
62 static void
63 dbus_display_console_set_size(DBusDisplayConsole *ddc,
64                               uint32_t width, uint32_t height)
65 {
66     g_object_set(ddc->iface,
67                  "width", width,
68                  "height", height,
69                  NULL);
70 }
71 
72 static void
73 dbus_gfx_switch(DisplayChangeListener *dcl,
74                 struct DisplaySurface *new_surface)
75 {
76     DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
77 
78     dbus_display_console_set_size(ddc,
79                                   surface_width(new_surface),
80                                   surface_height(new_surface));
81 }
82 
83 static void
84 dbus_gfx_update(DisplayChangeListener *dcl,
85                 int x, int y, int w, int h)
86 {
87 }
88 
89 static void
90 dbus_gl_scanout_disable(DisplayChangeListener *dcl)
91 {
92 }
93 
94 static void
95 dbus_gl_scanout_texture(DisplayChangeListener *dcl,
96                         uint32_t tex_id,
97                         bool backing_y_0_top,
98                         uint32_t backing_width,
99                         uint32_t backing_height,
100                         uint32_t x, uint32_t y,
101                         uint32_t w, uint32_t h,
102                         void *d3d_tex2d)
103 {
104     DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
105 
106     dbus_display_console_set_size(ddc, w, h);
107 }
108 
109 static void
110 dbus_gl_scanout_dmabuf(DisplayChangeListener *dcl,
111                        QemuDmaBuf *dmabuf)
112 {
113     uint32_t width, height;
114 
115     DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
116 
117     width = qemu_dmabuf_get_width(dmabuf);
118     height = qemu_dmabuf_get_height(dmabuf);
119 
120     dbus_display_console_set_size(ddc, width, height);
121 }
122 
123 static void
124 dbus_gl_scanout_update(DisplayChangeListener *dcl,
125                        uint32_t x, uint32_t y,
126                        uint32_t w, uint32_t h)
127 {
128 }
129 
130 const DisplayChangeListenerOps dbus_console_dcl_ops = {
131     .dpy_name                = "dbus-console",
132     .dpy_gfx_switch          = dbus_gfx_switch,
133     .dpy_gfx_update          = dbus_gfx_update,
134     .dpy_gl_scanout_disable  = dbus_gl_scanout_disable,
135     .dpy_gl_scanout_texture  = dbus_gl_scanout_texture,
136     .dpy_gl_scanout_dmabuf   = dbus_gl_scanout_dmabuf,
137     .dpy_gl_update           = dbus_gl_scanout_update,
138 };
139 
140 static void
141 dbus_display_console_init(DBusDisplayConsole *object)
142 {
143     DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object);
144 
145     ddc->listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
146                                             NULL, g_object_unref);
147     ddc->dcl.ops = &dbus_console_dcl_ops;
148 }
149 
150 static void
151 dbus_display_console_dispose(GObject *object)
152 {
153     DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object);
154 
155     unregister_displaychangelistener(&ddc->dcl);
156     g_clear_object(&ddc->iface_touch);
157     g_clear_object(&ddc->iface_mouse);
158     g_clear_object(&ddc->iface_kbd);
159     g_clear_object(&ddc->iface);
160     g_clear_pointer(&ddc->listeners, g_hash_table_unref);
161     g_clear_pointer(&ddc->kbd, qkbd_state_free);
162 
163     G_OBJECT_CLASS(dbus_display_console_parent_class)->dispose(object);
164 }
165 
166 static void
167 dbus_display_console_class_init(DBusDisplayConsoleClass *klass)
168 {
169     GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
170 
171     gobject_class->dispose = dbus_display_console_dispose;
172 }
173 
174 static void
175 listener_vanished_cb(DBusDisplayListener *listener)
176 {
177     DBusDisplayConsole *ddc = dbus_display_listener_get_console(listener);
178     const char *name = dbus_display_listener_get_bus_name(listener);
179 
180     trace_dbus_listener_vanished(name);
181 
182     g_hash_table_remove(ddc->listeners, name);
183     qkbd_state_lift_all_keys(ddc->kbd);
184 }
185 
186 static gboolean
187 dbus_console_set_ui_info(DBusDisplayConsole *ddc,
188                          GDBusMethodInvocation *invocation,
189                          guint16 arg_width_mm,
190                          guint16 arg_height_mm,
191                          gint arg_xoff,
192                          gint arg_yoff,
193                          guint arg_width,
194                          guint arg_height)
195 {
196     QemuUIInfo info = {
197         .width_mm = arg_width_mm,
198         .height_mm = arg_height_mm,
199         .xoff = arg_xoff,
200         .yoff = arg_yoff,
201         .width = arg_width,
202         .height = arg_height,
203     };
204 
205     if (!dpy_ui_info_supported(ddc->dcl.con)) {
206         g_dbus_method_invocation_return_error(invocation,
207                                               DBUS_DISPLAY_ERROR,
208                                               DBUS_DISPLAY_ERROR_UNSUPPORTED,
209                                               "SetUIInfo is not supported");
210         return DBUS_METHOD_INVOCATION_HANDLED;
211     }
212 
213     dpy_set_ui_info(ddc->dcl.con, &info, false);
214     qemu_dbus_display1_console_complete_set_uiinfo(ddc->iface, invocation);
215     return DBUS_METHOD_INVOCATION_HANDLED;
216 }
217 
218 #ifdef G_OS_WIN32
219 bool
220 dbus_win32_import_socket(GDBusMethodInvocation *invocation,
221                          GVariant *arg_listener, int *socket)
222 {
223     gsize n;
224     WSAPROTOCOL_INFOW *info = (void *)g_variant_get_fixed_array(arg_listener, &n, 1);
225 
226     if (!info || n != sizeof(*info)) {
227         g_dbus_method_invocation_return_error(
228             invocation,
229             DBUS_DISPLAY_ERROR,
230             DBUS_DISPLAY_ERROR_FAILED,
231             "Failed to get socket infos");
232         return false;
233     }
234 
235     *socket = WSASocketW(FROM_PROTOCOL_INFO,
236                          FROM_PROTOCOL_INFO,
237                          FROM_PROTOCOL_INFO,
238                          info, 0, 0);
239     if (*socket == INVALID_SOCKET) {
240         g_autofree gchar *emsg = g_win32_error_message(WSAGetLastError());
241         g_dbus_method_invocation_return_error(
242             invocation,
243             DBUS_DISPLAY_ERROR,
244             DBUS_DISPLAY_ERROR_FAILED,
245             "Couldn't create socket: %s", emsg);
246         return false;
247     }
248 
249     return true;
250 }
251 #endif
252 
253 static gboolean
254 dbus_console_register_listener(DBusDisplayConsole *ddc,
255                                GDBusMethodInvocation *invocation,
256 #ifdef G_OS_UNIX
257                                GUnixFDList *fd_list,
258 #endif
259                                GVariant *arg_listener)
260 {
261     const char *sender = g_dbus_method_invocation_get_sender(invocation);
262     GDBusConnection *listener_conn;
263     g_autoptr(GError) err = NULL;
264     g_autoptr(GSocket) socket = NULL;
265     g_autoptr(GSocketConnection) socket_conn = NULL;
266     g_autofree char *guid = g_dbus_generate_guid();
267     DBusDisplayListener *listener;
268     int fd;
269 
270     if (sender && g_hash_table_contains(ddc->listeners, sender)) {
271         g_dbus_method_invocation_return_error(
272             invocation,
273             DBUS_DISPLAY_ERROR,
274             DBUS_DISPLAY_ERROR_INVALID,
275             "`%s` is already registered!",
276             sender);
277         return DBUS_METHOD_INVOCATION_HANDLED;
278     }
279 
280 #ifdef G_OS_WIN32
281     if (!dbus_win32_import_socket(invocation, arg_listener, &fd)) {
282         return DBUS_METHOD_INVOCATION_HANDLED;
283     }
284 #else
285     fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err);
286     if (err) {
287         g_dbus_method_invocation_return_error(
288             invocation,
289             DBUS_DISPLAY_ERROR,
290             DBUS_DISPLAY_ERROR_FAILED,
291             "Couldn't get peer fd: %s", err->message);
292         return DBUS_METHOD_INVOCATION_HANDLED;
293     }
294 #endif
295 
296     socket = g_socket_new_from_fd(fd, &err);
297     if (err) {
298         g_dbus_method_invocation_return_error(
299             invocation,
300             DBUS_DISPLAY_ERROR,
301             DBUS_DISPLAY_ERROR_FAILED,
302             "Couldn't make a socket: %s", err->message);
303 #ifdef G_OS_WIN32
304         closesocket(fd);
305 #else
306         close(fd);
307 #endif
308         return DBUS_METHOD_INVOCATION_HANDLED;
309     }
310     socket_conn = g_socket_connection_factory_create_connection(socket);
311 
312     qemu_dbus_display1_console_complete_register_listener(
313         ddc->iface, invocation
314 #ifdef G_OS_UNIX
315         , NULL
316 #endif
317     );
318 
319     listener_conn = g_dbus_connection_new_sync(
320         G_IO_STREAM(socket_conn),
321         guid,
322         G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
323         NULL, NULL, &err);
324     if (err) {
325         error_report("Failed to setup peer connection: %s", err->message);
326         return DBUS_METHOD_INVOCATION_HANDLED;
327     }
328 
329     listener = dbus_display_listener_new(sender, listener_conn, ddc);
330     if (!listener) {
331         return DBUS_METHOD_INVOCATION_HANDLED;
332     }
333 
334     g_hash_table_insert(ddc->listeners,
335                         (gpointer)dbus_display_listener_get_bus_name(listener),
336                         listener);
337     g_object_connect(listener_conn,
338                      "swapped-signal::closed", listener_vanished_cb, listener,
339                      NULL);
340 
341     trace_dbus_registered_listener(sender);
342     return DBUS_METHOD_INVOCATION_HANDLED;
343 }
344 
345 static gboolean
346 dbus_kbd_press(DBusDisplayConsole *ddc,
347                GDBusMethodInvocation *invocation,
348                guint arg_keycode)
349 {
350     QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode);
351 
352     trace_dbus_kbd_press(arg_keycode);
353 
354     qkbd_state_key_event(ddc->kbd, qcode, true);
355 
356     qemu_dbus_display1_keyboard_complete_press(ddc->iface_kbd, invocation);
357 
358     return DBUS_METHOD_INVOCATION_HANDLED;
359 }
360 
361 static gboolean
362 dbus_kbd_release(DBusDisplayConsole *ddc,
363                  GDBusMethodInvocation *invocation,
364                  guint arg_keycode)
365 {
366     QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode);
367 
368     trace_dbus_kbd_release(arg_keycode);
369 
370     qkbd_state_key_event(ddc->kbd, qcode, false);
371 
372     qemu_dbus_display1_keyboard_complete_release(ddc->iface_kbd, invocation);
373 
374     return DBUS_METHOD_INVOCATION_HANDLED;
375 }
376 
377 static void
378 dbus_kbd_qemu_leds_updated(void *data, int ledstate)
379 {
380     DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(data);
381 
382     qemu_dbus_display1_keyboard_set_modifiers(ddc->iface_kbd, ledstate);
383 }
384 
385 static gboolean
386 dbus_mouse_rel_motion(DBusDisplayConsole *ddc,
387                       GDBusMethodInvocation *invocation,
388                       int dx, int dy)
389 {
390     trace_dbus_mouse_rel_motion(dx, dy);
391 
392     if (qemu_input_is_absolute(ddc->dcl.con)) {
393         g_dbus_method_invocation_return_error(
394             invocation, DBUS_DISPLAY_ERROR,
395             DBUS_DISPLAY_ERROR_INVALID,
396             "Mouse is not relative");
397         return DBUS_METHOD_INVOCATION_HANDLED;
398     }
399 
400     qemu_input_queue_rel(ddc->dcl.con, INPUT_AXIS_X, dx);
401     qemu_input_queue_rel(ddc->dcl.con, INPUT_AXIS_Y, dy);
402     qemu_input_event_sync();
403 
404     qemu_dbus_display1_mouse_complete_rel_motion(ddc->iface_mouse,
405                                                     invocation);
406 
407     return DBUS_METHOD_INVOCATION_HANDLED;
408 }
409 
410 static gboolean
411 dbus_touch_send_event(DBusDisplayConsole *ddc,
412                       GDBusMethodInvocation *invocation,
413                       guint kind, uint64_t num_slot,
414                       double x, double y)
415 {
416     Error *error = NULL;
417     int width, height;
418     trace_dbus_touch_send_event(kind, num_slot, x, y);
419 
420     if (kind != INPUT_MULTI_TOUCH_TYPE_BEGIN &&
421         kind != INPUT_MULTI_TOUCH_TYPE_UPDATE &&
422         kind != INPUT_MULTI_TOUCH_TYPE_CANCEL &&
423         kind != INPUT_MULTI_TOUCH_TYPE_END)
424     {
425         g_dbus_method_invocation_return_error(
426             invocation, DBUS_DISPLAY_ERROR,
427             DBUS_DISPLAY_ERROR_INVALID,
428             "Invalid touch event kind");
429         return DBUS_METHOD_INVOCATION_HANDLED;
430     }
431     width = qemu_console_get_width(ddc->dcl.con, 0);
432     height = qemu_console_get_height(ddc->dcl.con, 0);
433 
434     console_handle_touch_event(ddc->dcl.con, touch_slots,
435                                num_slot, width, height,
436                                x, y, kind, &error);
437     if (error != NULL) {
438         g_dbus_method_invocation_return_error(
439             invocation, DBUS_DISPLAY_ERROR,
440             DBUS_DISPLAY_ERROR_INVALID,
441             error_get_pretty(error), NULL);
442         error_free(error);
443     } else {
444         qemu_dbus_display1_multi_touch_complete_send_event(ddc->iface_touch,
445                                                            invocation);
446     }
447     return DBUS_METHOD_INVOCATION_HANDLED;
448 }
449 
450 static gboolean
451 dbus_mouse_set_pos(DBusDisplayConsole *ddc,
452                    GDBusMethodInvocation *invocation,
453                    guint x, guint y)
454 {
455     int width, height;
456 
457     trace_dbus_mouse_set_pos(x, y);
458 
459     if (!qemu_input_is_absolute(ddc->dcl.con)) {
460         g_dbus_method_invocation_return_error(
461             invocation, DBUS_DISPLAY_ERROR,
462             DBUS_DISPLAY_ERROR_INVALID,
463             "Mouse is not absolute");
464         return DBUS_METHOD_INVOCATION_HANDLED;
465     }
466 
467     width = qemu_console_get_width(ddc->dcl.con, 0);
468     height = qemu_console_get_height(ddc->dcl.con, 0);
469     if (x >= width || y >= height) {
470         g_dbus_method_invocation_return_error(
471             invocation, DBUS_DISPLAY_ERROR,
472             DBUS_DISPLAY_ERROR_INVALID,
473             "Invalid mouse position");
474         return DBUS_METHOD_INVOCATION_HANDLED;
475     }
476     qemu_input_queue_abs(ddc->dcl.con, INPUT_AXIS_X, x, 0, width);
477     qemu_input_queue_abs(ddc->dcl.con, INPUT_AXIS_Y, y, 0, height);
478     qemu_input_event_sync();
479 
480     qemu_dbus_display1_mouse_complete_set_abs_position(ddc->iface_mouse,
481                                                           invocation);
482 
483     return DBUS_METHOD_INVOCATION_HANDLED;
484 }
485 
486 static gboolean
487 dbus_mouse_press(DBusDisplayConsole *ddc,
488                  GDBusMethodInvocation *invocation,
489                  guint button)
490 {
491     trace_dbus_mouse_press(button);
492 
493     qemu_input_queue_btn(ddc->dcl.con, button, true);
494     qemu_input_event_sync();
495 
496     qemu_dbus_display1_mouse_complete_press(ddc->iface_mouse, invocation);
497 
498     return DBUS_METHOD_INVOCATION_HANDLED;
499 }
500 
501 static gboolean
502 dbus_mouse_release(DBusDisplayConsole *ddc,
503                    GDBusMethodInvocation *invocation,
504                    guint button)
505 {
506     trace_dbus_mouse_release(button);
507 
508     qemu_input_queue_btn(ddc->dcl.con, button, false);
509     qemu_input_event_sync();
510 
511     qemu_dbus_display1_mouse_complete_release(ddc->iface_mouse, invocation);
512 
513     return DBUS_METHOD_INVOCATION_HANDLED;
514 }
515 
516 static void
517 dbus_mouse_update_is_absolute(DBusDisplayConsole *ddc)
518 {
519     g_object_set(ddc->iface_mouse,
520                  "is-absolute", qemu_input_is_absolute(ddc->dcl.con),
521                  NULL);
522 }
523 
524 static void
525 dbus_mouse_mode_change(Notifier *notify, void *data)
526 {
527     DBusDisplayConsole *ddc =
528         container_of(notify, DBusDisplayConsole, mouse_mode_notifier);
529 
530     dbus_mouse_update_is_absolute(ddc);
531 }
532 
533 int dbus_display_console_get_index(DBusDisplayConsole *ddc)
534 {
535     return qemu_console_get_index(ddc->dcl.con);
536 }
537 
538 DBusDisplayConsole *
539 dbus_display_console_new(DBusDisplay *display, QemuConsole *con)
540 {
541     g_autofree char *path = NULL;
542     g_autofree char *label = NULL;
543     char device_addr[256] = "";
544     DBusDisplayConsole *ddc;
545     int idx, i;
546     const char *interfaces[] = {
547         "org.qemu.Display1.Keyboard",
548         "org.qemu.Display1.Mouse",
549         "org.qemu.Display1.MultiTouch",
550         NULL
551     };
552 
553     assert(display);
554     assert(con);
555 
556     label = qemu_console_get_label(con);
557     idx = qemu_console_get_index(con);
558     path = g_strdup_printf(DBUS_DISPLAY1_ROOT "/Console_%d", idx);
559     ddc = g_object_new(DBUS_DISPLAY_TYPE_CONSOLE,
560                         "g-object-path", path,
561                         NULL);
562     ddc->display = display;
563     ddc->dcl.con = con;
564     /* handle errors, and skip non graphics? */
565     qemu_console_fill_device_address(
566         con, device_addr, sizeof(device_addr), NULL);
567 
568     ddc->iface = qemu_dbus_display1_console_skeleton_new();
569     g_object_set(ddc->iface,
570         "label", label,
571         "type", qemu_console_is_graphic(con) ? "Graphic" : "Text",
572         "head", qemu_console_get_head(con),
573         "width", qemu_console_get_width(con, 0),
574         "height", qemu_console_get_height(con, 0),
575         "device-address", device_addr,
576         "interfaces", interfaces,
577         NULL);
578     g_object_connect(ddc->iface,
579         "swapped-signal::handle-register-listener",
580         dbus_console_register_listener, ddc,
581         "swapped-signal::handle-set-uiinfo",
582         dbus_console_set_ui_info, ddc,
583         NULL);
584     g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
585         G_DBUS_INTERFACE_SKELETON(ddc->iface));
586 
587     ddc->kbd = qkbd_state_init(con);
588     ddc->iface_kbd = qemu_dbus_display1_keyboard_skeleton_new();
589     qemu_add_led_event_handler(dbus_kbd_qemu_leds_updated, ddc);
590     g_object_connect(ddc->iface_kbd,
591         "swapped-signal::handle-press", dbus_kbd_press, ddc,
592         "swapped-signal::handle-release", dbus_kbd_release, ddc,
593         NULL);
594     g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
595         G_DBUS_INTERFACE_SKELETON(ddc->iface_kbd));
596 
597     ddc->iface_mouse = qemu_dbus_display1_mouse_skeleton_new();
598     g_object_connect(ddc->iface_mouse,
599         "swapped-signal::handle-set-abs-position", dbus_mouse_set_pos, ddc,
600         "swapped-signal::handle-rel-motion", dbus_mouse_rel_motion, ddc,
601         "swapped-signal::handle-press", dbus_mouse_press, ddc,
602         "swapped-signal::handle-release", dbus_mouse_release, ddc,
603         NULL);
604     g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
605         G_DBUS_INTERFACE_SKELETON(ddc->iface_mouse));
606 
607     ddc->iface_touch = qemu_dbus_display1_multi_touch_skeleton_new();
608     g_object_connect(ddc->iface_touch,
609         "swapped-signal::handle-send-event", dbus_touch_send_event, ddc,
610         NULL);
611     qemu_dbus_display1_multi_touch_set_max_slots(ddc->iface_touch,
612                                                  INPUT_EVENT_SLOTS_MAX);
613     g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
614         G_DBUS_INTERFACE_SKELETON(ddc->iface_touch));
615 
616     for (i = 0; i < INPUT_EVENT_SLOTS_MAX; i++) {
617         struct touch_slot *slot = &touch_slots[i];
618         slot->tracking_id = -1;
619     }
620 
621     register_displaychangelistener(&ddc->dcl);
622     ddc->mouse_mode_notifier.notify = dbus_mouse_mode_change;
623     qemu_add_mouse_mode_change_notifier(&ddc->mouse_mode_notifier);
624     dbus_mouse_update_is_absolute(ddc);
625 
626     return ddc;
627 }
628