7d2a351024d94ae2fff8b818fbf46fa25c350a14
[platform/upstream/libxkbcommon.git] / tools / interactive-wayland.c
1 /*
2  * Copyright © 2012 Collabora, Ltd.
3  * Copyright © 2013 Ran Benita <ran234@gmail.com>
4  * Copyright © 2016 Daniel Stone <daniel@fooishbar.org>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the "Software"),
8  * to deal in the Software without restriction, including without limitation
9  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10  * and/or sell copies of the Software, and to permit persons to whom the
11  * Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice (including the next
14  * paragraph) shall be included in all copies or substantial portions of the
15  * Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
20  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23  * DEALINGS IN THE SOFTWARE.
24  */
25
26 #include "config.h"
27
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <locale.h>
31 #include <stdbool.h>
32 #include <stdint.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <sys/mman.h>
36 #include <unistd.h>
37
38 #include "xkbcommon/xkbcommon.h"
39 #include "tools-common.h"
40
41 #include <wayland-client.h>
42 #include "xdg-shell-client-protocol.h"
43 #include <wayland-util.h>
44
45 #define MIN(a, b) ((a) < (b) ? (a) : (b))
46
47 /* Offset between evdev keycodes (where KEY_ESCAPE is 1), and the evdev XKB
48  * keycode set (where ESC is 9). */
49 #define EVDEV_OFFSET 8
50
51 struct interactive_dpy {
52     struct wl_display *dpy;
53     struct wl_compositor *compositor;
54     struct xdg_wm_base *shell;
55     struct wl_shm *shm;
56     uint32_t shm_format;
57
58     struct xkb_context *ctx;
59
60     struct wl_surface *wl_surf;
61     struct xdg_surface *xdg_surf;
62     struct xdg_toplevel *xdg_top;
63
64     struct wl_list seats;
65 };
66
67 struct interactive_seat {
68     struct interactive_dpy *inter;
69
70     struct wl_seat *wl_seat;
71     struct wl_keyboard *wl_kbd;
72     struct wl_pointer *wl_pointer;
73     uint32_t version; /* ... of wl_seat */
74     uint32_t global_name; /* an ID of sorts */
75     char *name_str; /* a descriptor */
76
77     struct xkb_keymap *keymap;
78     struct xkb_state *state;
79
80     struct wl_list link;
81 };
82
83 static bool terminate;
84
85 #ifdef HAVE_MKOSTEMP
86 static int
87 create_tmpfile_cloexec(char *tmpname)
88 {
89     int fd = mkostemp(tmpname, O_CLOEXEC);
90     if (fd >= 0)
91         unlink(tmpname);
92     return fd;
93 }
94 #else
95 /* The following utility functions are taken from Weston's
96  * shared/os-compatibility.c. */
97 static int
98 os_fd_set_cloexec(int fd)
99 {
100     long flags;
101
102     if (fd == -1)
103         return -1;
104
105     flags = fcntl(fd, F_GETFD);
106     if (flags == -1)
107         return -1;
108
109     if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
110         return -1;
111
112     return 0;
113 }
114
115 static int
116 set_cloexec_or_close(int fd)
117 {
118     if (os_fd_set_cloexec(fd) != 0) {
119         close(fd);
120         return -1;
121     }
122     return fd;
123 }
124
125 static int
126 create_tmpfile_cloexec(char *tmpname)
127 {
128     int fd = mkstemp(tmpname);
129     if (fd >= 0) {
130         fd = set_cloexec_or_close(fd);
131         unlink(tmpname);
132     }
133     return fd;
134 }
135 #endif
136
137 static int
138 os_resize_anonymous_file(int fd, off_t size)
139 {
140     int ret;
141 #ifdef HAVE_POSIX_FALLOCATE
142     ret = posix_fallocate(fd, 0, size);
143     if (ret == 0)
144         return 0;
145     /*
146      * Filesystems that do support fallocate will return EINVAL
147      * or EOPNOTSUPP, fallback to ftruncate() then.
148      */
149     if (ret != EINVAL && ret != EOPNOTSUPP)
150         return ret;
151 #endif
152     ret = ftruncate(fd, size);
153     if (ret != 0)
154         return errno;
155     return 0;
156 }
157
158 /*
159  * Create a new, unique, anonymous file of the given size, and
160  * return the file descriptor for it. The file descriptor is set
161  * CLOEXEC. The file is immediately suitable for mmap()'ing
162  * the given size at offset zero.
163  *
164  * The file should not have a permanent backing store like a disk,
165  * but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
166  *
167  * The file name is deleted from the file system.
168  *
169  * The file is suitable for buffer sharing between processes by
170  * transmitting the file descriptor over Unix sockets using the
171  * SCM_RIGHTS methods.
172  *
173  * If the C library implements posix_fallocate(), it is used to
174  * guarantee that disk space is available for the file at the
175  * given size. If disk space is insufficent, errno is set to ENOSPC.
176  * If posix_fallocate() is not supported, program will fallback
177  * to ftruncate() instead.
178  */
179 static int
180 os_create_anonymous_file(off_t size)
181 {
182     static const char template[] = "/weston-shared-XXXXXX";
183     const char *path;
184     char *name;
185     int fd;
186     int ret;
187
188     path = getenv("XDG_RUNTIME_DIR");
189     if (!path) {
190         errno = ENOENT;
191         return -1;
192     }
193
194     name = malloc(strlen(path) + sizeof(template));
195     if (!name)
196         return -1;
197
198     strcpy(name, path);
199     strcat(name, template);
200
201     fd = create_tmpfile_cloexec(name);
202
203     free(name);
204
205     if (fd < 0)
206         return -1;
207
208     ret = os_resize_anonymous_file(fd, size);
209     if (ret != 0) {
210         close(fd);
211         errno = ret;
212         return -1;
213     }
214
215     return fd;
216 }
217
218 static void
219 buffer_release(void *data, struct wl_buffer *buffer)
220 {
221     wl_buffer_destroy(buffer);
222 }
223
224 static const struct wl_buffer_listener buffer_listener = {
225     buffer_release
226 };
227
228 static void
229 buffer_create(struct interactive_dpy *inter, uint32_t width, uint32_t height)
230 {
231     struct wl_shm_pool *pool;
232     struct wl_buffer *buf;
233     struct wl_region *opaque;
234     uint32_t stride;
235     size_t size;
236     void *map;
237     int fd;
238
239     switch (inter->shm_format) {
240     case WL_SHM_FORMAT_ARGB8888:
241     case WL_SHM_FORMAT_XRGB8888:
242     case WL_SHM_FORMAT_ABGR8888:
243     case WL_SHM_FORMAT_XBGR8888:
244         stride = width * 4;
245         break;
246     case WL_SHM_FORMAT_RGB565:
247     case WL_SHM_FORMAT_BGR565:
248         stride = width * 2;
249         break;
250     default:
251         fprintf(stderr, "Unsupported SHM format %d\n", inter->shm_format);
252         exit(1);
253     }
254
255     size = stride * height;
256     fd = os_create_anonymous_file(size);
257     if (fd < 0) {
258         fprintf(stderr, "Couldn't create surface buffer\n");
259         exit(1);
260     }
261
262     map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
263     if (map == MAP_FAILED) {
264         fprintf(stderr, "Couldn't mmap surface buffer\n");
265         exit(1);
266     }
267     memset(map, 0xff, size);
268     munmap(map, size);
269
270     pool = wl_shm_create_pool(inter->shm, fd, size);
271     buf = wl_shm_pool_create_buffer(pool, 0, width, height, stride,
272                                     inter->shm_format);
273     wl_buffer_add_listener(buf, &buffer_listener, inter);
274
275     wl_surface_attach(inter->wl_surf, buf, 0, 0);
276     wl_surface_damage(inter->wl_surf, 0, 0, width, height);
277
278     opaque = wl_compositor_create_region(inter->compositor);
279     wl_region_add(opaque, 0, 0, width, height);
280     wl_surface_set_opaque_region(inter->wl_surf, opaque);
281     wl_region_destroy(opaque);
282
283     wl_shm_pool_destroy(pool);
284     close(fd);
285 }
286
287 static void
288 surface_configure(void *data, struct xdg_surface *surface,
289                   uint32_t serial)
290 {
291     struct interactive_dpy *inter = data;
292
293     xdg_surface_ack_configure(inter->xdg_surf, serial);
294     wl_surface_commit(inter->wl_surf);
295 }
296
297 static const struct xdg_surface_listener surface_listener = {
298     surface_configure,
299 };
300
301 static void
302 toplevel_configure(void *data, struct xdg_toplevel *toplevel,
303                    int32_t width, int32_t height, struct wl_array *states)
304 {
305     struct interactive_dpy *inter = data;
306
307     if (width == 0)
308         width = 200;
309     if (height == 0)
310         height = 200;
311
312     buffer_create(inter, width, height);
313 }
314
315 static void
316 toplevel_close(void *data, struct xdg_toplevel *toplevel)
317 {
318     terminate = true;
319 }
320
321 static const struct xdg_toplevel_listener toplevel_listener = {
322     toplevel_configure,
323     toplevel_close
324 };
325
326 static void surface_create(struct interactive_dpy *inter)
327 {
328     inter->wl_surf = wl_compositor_create_surface(inter->compositor);
329     inter->xdg_surf = xdg_wm_base_get_xdg_surface(inter->shell, inter->wl_surf);
330     xdg_surface_add_listener(inter->xdg_surf, &surface_listener, inter);
331     inter->xdg_top = xdg_surface_get_toplevel(inter->xdg_surf);
332     xdg_toplevel_add_listener(inter->xdg_top, &toplevel_listener, inter);
333     xdg_toplevel_set_title(inter->xdg_top, "xkbcommon event tester");
334     xdg_toplevel_set_app_id(inter->xdg_top,
335                             "org.xkbcommon.test.interactive-wayland");
336     wl_surface_commit(inter->wl_surf);
337 }
338
339 static void
340 shell_ping(void *data, struct xdg_wm_base *shell, uint32_t serial)
341 {
342     xdg_wm_base_pong(shell, serial);
343 }
344
345 static const struct xdg_wm_base_listener shell_listener = {
346     shell_ping
347 };
348
349 static void
350 kbd_keymap(void *data, struct wl_keyboard *wl_kbd, uint32_t format,
351            int fd, uint32_t size)
352 {
353     struct interactive_seat *seat = data;
354     void *buf;
355
356     buf = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
357     if (buf == MAP_FAILED) {
358         fprintf(stderr, "Failed to mmap keymap: %d\n", errno);
359         close(fd);
360         return;
361     }
362
363     seat->keymap = xkb_keymap_new_from_buffer(seat->inter->ctx, buf, size - 1,
364                                               XKB_KEYMAP_FORMAT_TEXT_V1,
365                                               XKB_KEYMAP_COMPILE_NO_FLAGS);
366     munmap(buf, size);
367     close(fd);
368     if (!seat->keymap) {
369         fprintf(stderr, "Failed to compile keymap!\n");
370         return;
371     }
372
373     seat->state = xkb_state_new(seat->keymap);
374     if (!seat->state) {
375         fprintf(stderr, "Failed to create XKB state!\n");
376         return;
377     }
378 }
379
380 static void
381 kbd_enter(void *data, struct wl_keyboard *wl_kbd, uint32_t serial,
382           struct wl_surface *surf, struct wl_array *keys)
383 {
384 }
385
386 static void
387 kbd_leave(void *data, struct wl_keyboard *wl_kbd, uint32_t serial,
388           struct wl_surface *surf)
389 {
390 }
391
392 static void
393 kbd_key(void *data, struct wl_keyboard *wl_kbd, uint32_t serial, uint32_t time,
394         uint32_t key, uint32_t state)
395 {
396     struct interactive_seat *seat = data;
397
398     if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
399         return;
400
401     printf("%s: ", seat->name_str);
402     tools_print_keycode_state(seat->state, NULL, key + EVDEV_OFFSET,
403                               XKB_CONSUMED_MODE_XKB);
404
405     /* Exit on ESC. */
406     if (xkb_state_key_get_one_sym(seat->state, key + EVDEV_OFFSET) == XKB_KEY_Escape)
407         terminate = true;
408 }
409
410 static void
411 kbd_modifiers(void *data, struct wl_keyboard *wl_kbd, uint32_t serial,
412               uint32_t mods_depressed, uint32_t mods_latched,
413               uint32_t mods_locked, uint32_t group)
414 {
415     struct interactive_seat *seat = data;
416
417     xkb_state_update_mask(seat->state, mods_depressed, mods_latched,
418                           mods_locked, 0, 0, group);
419 }
420
421 static void
422 kbd_repeat_info(void *data, struct wl_keyboard *wl_kbd, int32_t rate,
423                 int32_t delay)
424 {
425 }
426
427 static const struct wl_keyboard_listener kbd_listener = {
428     kbd_keymap,
429     kbd_enter,
430     kbd_leave,
431     kbd_key,
432     kbd_modifiers,
433     kbd_repeat_info
434 };
435
436 static void
437 pointer_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
438               struct wl_surface *surf, wl_fixed_t fsx, wl_fixed_t fsy)
439 {
440 }
441
442 static void
443 pointer_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
444               struct wl_surface *surf)
445 {
446 }
447
448 static void
449 pointer_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time,
450                wl_fixed_t fsx, wl_fixed_t fsy)
451 {
452 }
453
454 static void
455 pointer_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
456                uint32_t time, uint32_t button, uint32_t state)
457 {
458     struct interactive_seat *seat = data;
459
460     xdg_toplevel_move(seat->inter->xdg_top, seat->wl_seat, serial);
461 }
462
463 static void
464 pointer_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time,
465              uint32_t axis, wl_fixed_t value)
466 {
467 }
468
469 static void
470 pointer_frame(void *data, struct wl_pointer *wl_pointer)
471 {
472 }
473
474 static void
475 pointer_axis_source(void *data, struct wl_pointer *wl_pointer, uint32_t source)
476 {
477 }
478
479 static void
480 pointer_axis_stop(void *data, struct wl_pointer *wl_pointer, uint32_t time,
481                   uint32_t axis)
482 {
483 }
484
485 static void
486 pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer, uint32_t time,
487                       int32_t discrete)
488 {
489 }
490
491 static const struct wl_pointer_listener pointer_listener = {
492     pointer_enter,
493     pointer_leave,
494     pointer_motion,
495     pointer_button,
496     pointer_axis,
497     pointer_frame,
498     pointer_axis_source,
499     pointer_axis_stop,
500     pointer_axis_discrete
501 };
502
503 static void
504 seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t caps)
505 {
506     struct interactive_seat *seat = data;
507
508     if (!seat->wl_kbd && (caps & WL_SEAT_CAPABILITY_KEYBOARD)) {
509         seat->wl_kbd = wl_seat_get_keyboard(seat->wl_seat);
510         wl_keyboard_add_listener(seat->wl_kbd, &kbd_listener, seat);
511     }
512     else if (seat->wl_kbd && !(caps & WL_SEAT_CAPABILITY_KEYBOARD)) {
513         if (seat->version >= WL_SEAT_RELEASE_SINCE_VERSION)
514             wl_keyboard_release(seat->wl_kbd);
515         else
516             wl_keyboard_destroy(seat->wl_kbd);
517
518         xkb_state_unref(seat->state);
519         xkb_keymap_unref(seat->keymap);
520
521         seat->state = NULL;
522         seat->keymap = NULL;
523         seat->wl_kbd = NULL;
524     }
525
526     if (!seat->wl_pointer && (caps & WL_SEAT_CAPABILITY_POINTER)) {
527         seat->wl_pointer = wl_seat_get_pointer(seat->wl_seat);
528         wl_pointer_add_listener(seat->wl_pointer, &pointer_listener,
529                                 seat);
530     }
531     else if (seat->wl_pointer && !(caps & WL_SEAT_CAPABILITY_POINTER)) {
532         if (seat->version >= WL_SEAT_RELEASE_SINCE_VERSION)
533             wl_pointer_release(seat->wl_pointer);
534         else
535             wl_pointer_destroy(seat->wl_pointer);
536         seat->wl_pointer = NULL;
537     }
538 }
539
540 static void
541 seat_name(void *data, struct wl_seat *wl_seat, const char *name)
542 {
543     struct interactive_seat *seat = data;
544
545     free(seat->name_str);
546     seat->name_str = strdup(name);
547 }
548
549 static const struct wl_seat_listener seat_listener = {
550     seat_capabilities,
551     seat_name
552 };
553
554 static void
555 seat_create(struct interactive_dpy *inter, struct wl_registry *registry,
556             uint32_t name, uint32_t version)
557 {
558     int ret;
559     struct interactive_seat *seat = calloc(1, sizeof(*seat));
560
561     seat->global_name = name;
562     seat->inter = inter;
563     seat->wl_seat = wl_registry_bind(registry, name, &wl_seat_interface,
564                                      MIN(version, 5));
565     wl_seat_add_listener(seat->wl_seat, &seat_listener, seat);
566     ret = asprintf(&seat->name_str, "seat:%d",
567                    wl_proxy_get_id((struct wl_proxy *) seat->wl_seat));
568     assert(ret >= 0);
569     wl_list_insert(&inter->seats, &seat->link);
570 }
571
572 static void
573 seat_destroy(struct interactive_seat *seat)
574 {
575     if (seat->wl_kbd) {
576         if (seat->version >= WL_SEAT_RELEASE_SINCE_VERSION)
577             wl_keyboard_release(seat->wl_kbd);
578         else
579             wl_keyboard_destroy(seat->wl_kbd);
580
581         xkb_state_unref(seat->state);
582         xkb_keymap_unref(seat->keymap);
583     }
584
585     if (seat->wl_pointer) {
586         if (seat->version >= WL_SEAT_RELEASE_SINCE_VERSION)
587             wl_pointer_release(seat->wl_pointer);
588         else
589             wl_pointer_destroy(seat->wl_pointer);
590     }
591
592     if (seat->version >= WL_SEAT_RELEASE_SINCE_VERSION)
593         wl_seat_release(seat->wl_seat);
594     else
595         wl_seat_destroy(seat->wl_seat);
596
597     free(seat->name_str);
598     wl_list_remove(&seat->link);
599     free(seat);
600 }
601
602 static void
603 registry_global(void *data, struct wl_registry *registry, uint32_t name,
604                 const char *interface, uint32_t version)
605 {
606     struct interactive_dpy *inter = data;
607
608     if (strcmp(interface, "wl_seat") == 0) {
609         seat_create(inter, registry, name, version);
610     }
611     else if (strcmp(interface, "xdg_wm_base") == 0) {
612         inter->shell = wl_registry_bind(registry, name,
613                                         &xdg_wm_base_interface,
614                                         MIN(version, 2));
615         xdg_wm_base_add_listener(inter->shell, &shell_listener, inter);
616     }
617     else if (strcmp(interface, "wl_compositor") == 0) {
618         inter->compositor = wl_registry_bind(registry, name,
619                                              &wl_compositor_interface,
620                                              MIN(version, 1));
621     }
622     else if (strcmp(interface, "wl_shm") == 0) {
623         inter->shm = wl_registry_bind(registry, name, &wl_shm_interface,
624                                       MIN(version, 1));
625     }
626 }
627
628 static void
629 registry_delete(void *data, struct wl_registry *registry, uint32_t name)
630 {
631     struct interactive_dpy *inter = data;
632     struct interactive_seat *seat, *tmp;
633
634     wl_list_for_each_safe(seat, tmp, &inter->seats, link) {
635         if (seat->global_name != name)
636             continue;
637
638         seat_destroy(seat);
639     }
640 }
641
642 static const struct wl_registry_listener registry_listener = {
643     registry_global,
644     registry_delete
645 };
646
647 static void
648 dpy_disconnect(struct interactive_dpy *inter)
649 {
650     struct interactive_seat *seat, *tmp;
651
652     wl_list_for_each_safe(seat, tmp, &inter->seats, link)
653         seat_destroy(seat);
654
655     if (inter->xdg_surf)
656         xdg_surface_destroy(inter->xdg_surf);
657     if (inter->xdg_top)
658         xdg_toplevel_destroy(inter->xdg_top);
659     if (inter->wl_surf)
660         wl_surface_destroy(inter->wl_surf);
661     if (inter->shell)
662         xdg_wm_base_destroy(inter->shell);
663     if (inter->compositor)
664         wl_compositor_destroy(inter->compositor);
665     if (inter->shm)
666         wl_shm_destroy(inter->shm);
667
668     /* Do one last roundtrip to try to destroy our wl_buffer. */
669     wl_display_roundtrip(inter->dpy);
670
671     xkb_context_unref(inter->ctx);
672     wl_display_disconnect(inter->dpy);
673 }
674
675 int
676 main(int argc, char *argv[])
677 {
678     int ret;
679     struct interactive_dpy inter;
680     struct wl_registry *registry;
681
682     if (argc != 1) {
683         ret = strcmp(argv[1], "--help");
684         fprintf(ret ? stderr : stdout, "Usage: %s [--help]\n", argv[0]);
685         if (ret)
686             fprintf(stderr, "unrecognized option: %s\n", argv[1]);
687         return ret ? EXIT_INVALID_USAGE : EXIT_SUCCESS;
688     }
689
690     setlocale(LC_ALL, "");
691
692     memset(&inter, 0, sizeof(inter));
693     wl_list_init(&inter.seats);
694
695     inter.dpy = wl_display_connect(NULL);
696     if (!inter.dpy) {
697         fprintf(stderr, "Couldn't connect to Wayland server\n");
698         ret = -1;
699         goto err_out;
700     }
701
702     inter.ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
703     if (!inter.ctx) {
704         ret = -1;
705         fprintf(stderr, "Couldn't create xkb context\n");
706         goto err_out;
707     }
708
709     registry = wl_display_get_registry(inter.dpy);
710     wl_registry_add_listener(registry, &registry_listener, &inter);
711
712     /* The first roundtrip gets the list of advertised globals. */
713     wl_display_roundtrip(inter.dpy);
714
715     /* The second roundtrip dispatches the events sent after binding, e.g.
716      * after binding to wl_seat globals in the first roundtrip, we will get
717      * the wl_seat::capabilities event in this roundtrip. */
718     wl_display_roundtrip(inter.dpy);
719
720     if (!inter.shell || !inter.shm || !inter.compositor) {
721         fprintf(stderr, "Required Wayland interfaces %s%s%s unsupported\n",
722                 (inter.shell) ? "" : "xdg_shell ",
723                 (inter.shm) ? "" : "wl_shm",
724                 (inter.compositor) ? "" : "wl_compositor");
725         ret = -1;
726         goto err_conn;
727     }
728
729     surface_create(&inter);
730
731     tools_disable_stdin_echo();
732     do {
733         ret = wl_display_dispatch(inter.dpy);
734     } while (ret >= 0 && !terminate);
735     tools_enable_stdin_echo();
736
737     wl_registry_destroy(registry);
738 err_conn:
739     dpy_disconnect(&inter);
740 err_out:
741     exit(ret >= 0 ? EXIT_SUCCESS : EXIT_FAILURE);
742 }