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