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