shl: move dlist implementation to shl_dlist_*
[platform/upstream/kmscon.git] / src / main.c
1 /*
2  * kmscon - KMS Console
3  *
4  * Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files
8  * (the "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included
15  * in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24  */
25
26 #include <errno.h>
27 #include <paths.h>
28 #include <signal.h>
29 #include <stdbool.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <sys/signalfd.h>
34 #include "conf.h"
35 #include "eloop.h"
36 #include "log.h"
37 #include "main.h"
38 #include "static_misc.h"
39 #include "text.h"
40 #include "ui.h"
41 #include "uterm.h"
42
43 struct kmscon_app {
44         struct ev_eloop *eloop;
45         struct ev_eloop *vt_eloop;
46
47         struct uterm_vt_master *vtm;
48         struct uterm_monitor *mon;
49         struct shl_dlist seats;
50 };
51
52 struct kmscon_seat {
53         struct shl_dlist list;
54         struct kmscon_app *app;
55
56         struct uterm_monitor_seat *useat;
57         char *sname;
58
59         bool awake;
60         struct uterm_vt *vt;
61         struct uterm_input *input;
62         struct kmscon_ui *ui;
63         struct shl_dlist videos;
64 };
65
66 struct kmscon_video {
67         struct shl_dlist list;
68         struct uterm_monitor_dev *vdev;
69         struct uterm_video *video;
70 };
71
72 static void sig_generic(struct ev_eloop *eloop, struct signalfd_siginfo *info,
73                         void *data)
74 {
75         struct kmscon_app *app = data;
76
77         ev_eloop_exit(app->eloop);
78         log_info("terminating due to caught signal %d", info->ssi_signo);
79 }
80
81 static int vt_event(struct uterm_vt *vt, unsigned int action, void *data)
82 {
83         struct kmscon_seat *seat = data;
84         struct shl_dlist *iter;
85         struct kmscon_video *vid;
86
87         if (action == UTERM_VT_ACTIVATE) {
88                 seat->awake = true;
89                 uterm_input_wake_up(seat->input);
90
91                 shl_dlist_for_each(iter, &seat->videos) {
92                         vid = shl_dlist_entry(iter, struct kmscon_video, list);
93                         uterm_video_wake_up(vid->video);
94                 }
95                 kmscon_ui_wake_up(seat->ui);
96         } else if (action == UTERM_VT_DEACTIVATE) {
97                 kmscon_ui_sleep(seat->ui);
98                 shl_dlist_for_each(iter, &seat->videos) {
99                         vid = shl_dlist_entry(iter, struct kmscon_video, list);
100                         uterm_video_sleep(vid->video);
101                 }
102
103                 uterm_input_sleep(seat->input);
104                 seat->awake = false;
105         }
106
107         return 0;
108 }
109
110 static void seat_new(struct kmscon_app *app,
111                      struct uterm_monitor_seat *useat,
112                      const char *sname)
113 {
114         struct kmscon_seat *seat;
115         int ret;
116         unsigned int i;
117         bool found;
118
119         found = false;
120         if (kmscon_conf.all_seats) {
121                 found = true;
122         } else {
123                 for (i = 0; kmscon_conf.seats[i]; ++i) {
124                         if (!strcmp(kmscon_conf.seats[i], sname)) {
125                                 found = true;
126                                 break;
127                         }
128                 }
129         }
130
131         if (!found) {
132                 log_info("ignoring seat %s as not specified in seat-list",
133                          sname);
134                 return;
135         }
136
137         seat = malloc(sizeof(*seat));
138         if (!seat)
139                 return;
140         memset(seat, 0, sizeof(*seat));
141         seat->app = app;
142         seat->useat = useat;
143         shl_dlist_init(&seat->videos);
144
145         seat->sname = strdup(sname);
146         if (!seat->sname) {
147                 log_err("cannot allocate memory for seat name");
148                 goto err_free;
149         }
150
151         ret = uterm_input_new(&seat->input, app->eloop,
152                               kmscon_conf.xkb_layout,
153                               kmscon_conf.xkb_variant,
154                               kmscon_conf.xkb_options);
155         if (ret)
156                 goto err_name;
157
158         ret = uterm_vt_allocate(app->vtm, &seat->vt, seat->sname,
159                                 seat->input, vt_event, seat);
160         if (ret)
161                 goto err_input;
162
163         ret = kmscon_ui_new(&seat->ui, app->eloop, seat->input);
164         if (ret)
165                 goto err_vt;
166
167         uterm_monitor_set_seat_data(seat->useat, seat);
168         shl_dlist_link(&app->seats, &seat->list);
169
170         log_info("new seat %s", seat->sname);
171         return;
172
173 err_vt:
174         uterm_vt_deallocate(seat->vt);
175 err_input:
176         uterm_input_unref(seat->input);
177 err_name:
178         free(seat->sname);
179 err_free:
180         free(seat);
181 }
182
183 static void seat_free(struct kmscon_seat *seat)
184 {
185         log_info("free seat %s", seat->sname);
186
187         shl_dlist_unlink(&seat->list);
188         uterm_monitor_set_seat_data(seat->useat, NULL);
189         kmscon_ui_free(seat->ui);
190         uterm_input_unref(seat->input);
191         uterm_vt_deallocate(seat->vt);
192         free(seat->sname);
193         free(seat);
194 }
195
196 static void seat_add_video(struct kmscon_seat *seat,
197                            struct uterm_monitor_dev *dev,
198                            unsigned int type,
199                            const char *node)
200 {
201         int ret;
202         unsigned int mode;
203         struct kmscon_video *vid;
204
205         if ((type == UTERM_MONITOR_FBDEV) != !!kmscon_conf.use_fbdev)
206                 return;
207
208         vid = malloc(sizeof(*vid));
209         if (!vid)
210                 return;
211         memset(vid, 0, sizeof(*vid));
212         vid->vdev = dev;
213
214         if (kmscon_conf.use_fbdev)
215                 mode = UTERM_VIDEO_FBDEV;
216         else if (kmscon_conf.dumb)
217                 mode = UTERM_VIDEO_DUMB;
218         else
219                 mode = UTERM_VIDEO_DRM;
220
221         ret = uterm_video_new(&vid->video, seat->app->eloop, mode, node);
222         if (ret) {
223                 if (mode == UTERM_VIDEO_DRM) {
224                         log_info("cannot create drm device; trying dumb drm mode");
225                         ret = uterm_video_new(&vid->video, seat->app->eloop,
226                                               UTERM_VIDEO_DUMB, node);
227                         if (ret)
228                                 goto err_free;
229                 } else {
230                         goto err_free;
231                 }
232         }
233
234         kmscon_ui_add_video(seat->ui, vid->video);
235         if (seat->awake)
236                 uterm_video_wake_up(vid->video);
237         shl_dlist_link(&seat->videos, &vid->list);
238
239         log_debug("new graphics device on seat %s", seat->sname);
240         return;
241
242 err_free:
243         free(vid);
244         log_warning("cannot add video object %s on %s", node, seat->sname);
245         return;
246 }
247
248 static void seat_rm_video(struct kmscon_seat *seat,
249                           struct uterm_monitor_dev *dev)
250 {
251         struct shl_dlist *iter;
252         struct kmscon_video *vid;
253
254         shl_dlist_for_each(iter, &seat->videos) {
255                 vid = shl_dlist_entry(iter, struct kmscon_video, list);
256                 if (vid->vdev != dev)
257                         continue;
258
259                 log_debug("free graphics device on seat %s", seat->sname);
260
261                 kmscon_ui_remove_video(seat->ui, vid->video);
262                 uterm_video_unref(vid->video);
263                 shl_dlist_unlink(&vid->list);
264                 free(vid);
265
266                 break;
267         }
268 }
269
270 static void seat_hotplug_video(struct kmscon_seat *seat,
271                                struct uterm_monitor_dev *dev)
272 {
273         struct shl_dlist *iter;
274         struct kmscon_video *vid;
275
276         shl_dlist_for_each(iter, &seat->videos) {
277                 vid = shl_dlist_entry(iter, struct kmscon_video, list);
278                 if (vid->vdev != dev)
279                         continue;
280
281                 uterm_video_poll(vid->video);
282                 break;
283         }
284 }
285
286 static void monitor_event(struct uterm_monitor *mon,
287                           struct uterm_monitor_event *ev,
288                           void *data)
289 {
290         struct kmscon_app *app = data;
291         struct kmscon_seat *seat;
292
293         switch (ev->type) {
294         case UTERM_MONITOR_NEW_SEAT:
295                 seat_new(app, ev->seat, ev->seat_name);
296                 break;
297         case UTERM_MONITOR_FREE_SEAT:
298                 if (ev->seat_data)
299                         seat_free(ev->seat_data);
300                 break;
301         case UTERM_MONITOR_NEW_DEV:
302                 seat = ev->seat_data;
303                 if (!seat)
304                         break;
305                 if (ev->dev_type == UTERM_MONITOR_DRM ||
306                     ev->dev_type == UTERM_MONITOR_FBDEV)
307                         seat_add_video(seat, ev->dev, ev->dev_type,
308                                        ev->dev_node);
309                 else if (ev->dev_type == UTERM_MONITOR_INPUT)
310                         uterm_input_add_dev(seat->input, ev->dev_node);
311                 break;
312         case UTERM_MONITOR_FREE_DEV:
313                 seat = ev->seat_data;
314                 if (!seat)
315                         break;
316                 if (ev->dev_type == UTERM_MONITOR_DRM ||
317                     ev->dev_type == UTERM_MONITOR_FBDEV)
318                         seat_rm_video(seat, ev->dev);
319                 else if (ev->dev_type == UTERM_MONITOR_INPUT)
320                         uterm_input_remove_dev(seat->input, ev->dev_node);
321                 break;
322         case UTERM_MONITOR_HOTPLUG_DEV:
323                 seat = ev->seat_data;
324                 if (!seat)
325                         break;
326                 seat_hotplug_video(seat, ev->dev);
327                 break;
328         }
329 }
330
331 static void destroy_app(struct kmscon_app *app)
332 {
333         uterm_monitor_unref(app->mon);
334         uterm_vt_master_unref(app->vtm);
335         ev_eloop_unregister_signal_cb(app->eloop, SIGINT, sig_generic, app);
336         ev_eloop_unregister_signal_cb(app->eloop, SIGTERM, sig_generic, app);
337         ev_eloop_rm_eloop(app->vt_eloop);
338         ev_eloop_unref(app->eloop);
339 }
340
341 static int setup_app(struct kmscon_app *app)
342 {
343         int ret;
344
345         ret = ev_eloop_new(&app->eloop, log_llog);
346         if (ret)
347                 goto err_app;
348
349         ret = ev_eloop_register_signal_cb(app->eloop, SIGTERM,
350                                                 sig_generic, app);
351         if (ret)
352                 goto err_app;
353
354         ret = ev_eloop_register_signal_cb(app->eloop, SIGINT,
355                                                 sig_generic, app);
356         if (ret)
357                 goto err_app;
358
359         ret = ev_eloop_new_eloop(app->eloop, &app->vt_eloop);
360         if (ret)
361                 goto err_app;
362
363         ret = uterm_vt_master_new(&app->vtm, app->vt_eloop);
364         if (ret)
365                 goto err_app;
366
367         shl_dlist_init(&app->seats);
368
369         ret = uterm_monitor_new(&app->mon, app->eloop, monitor_event, app);
370         if (ret)
371                 goto err_app;
372
373         uterm_monitor_scan(app->mon);
374
375         return 0;
376
377 err_app:
378         destroy_app(app);
379         return ret;
380 }
381
382 struct kmscon_conf_t kmscon_conf;
383
384 static void print_help()
385 {
386         /*
387          * Usage/Help information
388          * This should be scaled to a maximum of 80 characters per line:
389          *
390          * 80 char line:
391          *       |   10   |    20   |    30   |    40   |    50   |    60   |    70   |    80   |
392          *      "12345678901234567890123456789012345678901234567890123456789012345678901234567890\n"
393          * 80 char line starting with tab:
394          *       |10|    20   |    30   |    40   |    50   |    60   |    70   |    80   |
395          *      "\t901234567890123456789012345678901234567890123456789012345678901234567890\n"
396          */
397         fprintf(stderr,
398                 "Usage:\n"
399                 "\t%1$s [options]\n"
400                 "\t%1$s -h [options]\n"
401                 "\t%1$s -l [options] -- /bin/sh [sh-arguments]\n"
402                 "\n"
403                 "You can prefix boolean options with \"no-\" to negate it. If an argument is\n"
404                 "given multiple times, only the last argument matters if not otherwise stated.\n"
405                 "\n"
406                 "General Options:\n"
407                 "\t-h, --help                  [off]   Print this help and exit\n"
408                 "\t-v, --verbose               [off]   Print verbose messages\n"
409                 "\t    --debug                 [off]   Enable debug mode\n"
410                 "\t    --silent                [off]   Suppress notices and warnings\n"
411                 "\t-s, --switchvt              [off]   Automatically switch to VT\n"
412                 "\t    --seats <list,of,seats> [seat0] Select seats or pass 'all' to make\n"
413                 "\t                                    kmscon run on all seats\n"
414                 "\n"
415                 "Terminal Options:\n"
416                 "\t-l, --login                 [/bin/sh]\n"
417                 "\t                              Start the given login process instead\n"
418                 "\t                              of the default process; all arguments\n"
419                 "\t                              following '--' will be be parsed as\n"
420                 "\t                              argv to this process. No more options\n"
421                 "\t                              after '--' will be parsed so use it at\n"
422                 "\t                              the end of the argument string\n"
423                 "\t-t, --term <TERM>           [xterm-256color]\n"
424                 "\t                              Value of the TERM environment variable\n"
425                 "\t                              for the child process\n"
426                 "\t    --palette <name>        [default]\n"
427                 "\t                              Select the used color palette\n"
428                 "\t    --sb-size <num>         [1000]\n"
429                 "\t                              Size of the scrollback-buffer in lines\n"
430                 "\n"
431                 "Video Options:\n"
432                 "\t    --fbdev                 [off]   Use fbdev instead of DRM\n"
433                 "\t    --dumb                  [off]   Use dumb DRM instead of hardware-\n"
434                 "\t                                    accelerated DRM devices\n"
435                 "\t    --fps                   [50]    Limit frame-rate\n"
436                 "\t    --render-engine <eng>   [-]     Console renderer\n"
437                 "\t    --render-timing         [off]   Print renderer timing information\n"
438                 "\n"
439                 "Input Device Options:\n"
440                 "\t    --xkb-layout <layout>   [us]    Set XkbLayout for input devices\n"
441                 "\t    --xkb-variant <variant> [-]     Set XkbVariant for input devices\n"
442                 "\t    --xkb-options <options> [-]     Set XkbOptions for input devices\n"
443                 "\n"
444                 "\t    --grab-scroll-up <grab>   [<Shift>Up]\n"
445                 "\t                                Shortcut to scroll up\n"
446                 "\t    --grab-scroll-down <grab> [<Shift>Down]\n"
447                 "\t                                Shortcut to scroll down\n"
448                 "\t    --grab-page-up <grab>     [<Shift>Prior]\n"
449                 "\t                                Shortcut to scroll page up\n"
450                 "\t    --grab-page-down <grab>   [<Shift>Next]\n"
451                 "\t                                Shortcut to scroll page down\n"
452                 "\n"
453                 "Font Options:\n"
454                 "\t    --font-engine <engine>  [pango]\n"
455                 "\t                              Font engine\n"
456                 "\t    --font-size <points>    [15]\n"
457                 "\t                              Font size in points\n"
458                 "\t    --font-name <name>      [monospace]\n"
459                 "\t                              Font name\n",
460                 "kmscon");
461         /*
462          * 80 char line:
463          *       |   10   |    20   |    30   |    40   |    50   |    60   |    70   |    80   |
464          *      "12345678901234567890123456789012345678901234567890123456789012345678901234567890\n"
465          * 80 char line starting with tab:
466          *       |10|    20   |    30   |    40   |    50   |    60   |    70   |    80   |
467          *      "\t901234567890123456789012345678901234567890123456789012345678901234567890\n"
468          */
469 }
470
471 static int aftercheck_debug(struct conf_option *opt, int argc, char **argv,
472                             int idx)
473 {
474         /* --debug implies --verbose */
475         if (kmscon_conf.debug)
476                 kmscon_conf.verbose = 1;
477
478         return 0;
479 }
480
481 static int aftercheck_help(struct conf_option *opt, int argc, char **argv,
482                            int idx)
483 {
484         /* exit after printing --help information */
485         if (kmscon_conf.help) {
486                 print_help();
487                 kmscon_conf.exit = true;
488         }
489
490         return 0;
491 }
492
493 static char *def_argv[] = { NULL, "-i", NULL };
494
495 static int aftercheck_login(struct conf_option *opt, int argc, char **argv,
496                             int idx)
497 {
498         int ret;
499
500         /* parse "--login [...] -- args" arguments */
501         if (kmscon_conf.login) {
502                 if (idx >= argc) {
503                         fprintf(stderr, "Arguments for --login missing\n");
504                         return -EFAULT;
505                 }
506
507                 kmscon_conf.argv = &argv[idx];
508                 ret = argc - idx;
509         } else {
510                 def_argv[0] = getenv("SHELL") ? : _PATH_BSHELL;
511                 kmscon_conf.argv = def_argv;
512                 ret = 0;
513         }
514
515         return ret;
516 }
517
518 static int aftercheck_seats(struct conf_option *opt, int argc, char **argv,
519                             int idx)
520 {
521         if (kmscon_conf.seats[0] &&
522             !kmscon_conf.seats[1] &&
523             !strcmp(kmscon_conf.seats[0], "all"))
524                 kmscon_conf.all_seats = true;
525
526         return 0;
527 }
528
529 static char *def_seats[] = { "seat0", NULL };
530
531 static struct uterm_input_grab def_grab_scroll_up = {
532         .mods = UTERM_SHIFT_MASK,
533         .keysym = XKB_KEY_Up,
534 };
535
536 static struct uterm_input_grab def_grab_scroll_down = {
537         .mods = UTERM_SHIFT_MASK,
538         .keysym = XKB_KEY_Down,
539 };
540
541 static struct uterm_input_grab def_grab_page_up = {
542         .mods = UTERM_SHIFT_MASK,
543         .keysym = XKB_KEY_Prior,
544 };
545
546 static struct uterm_input_grab def_grab_page_down = {
547         .mods = UTERM_SHIFT_MASK,
548         .keysym = XKB_KEY_Next,
549 };
550
551 struct conf_option options[] = {
552         CONF_OPTION_BOOL('h', "help", aftercheck_help, &kmscon_conf.help, false),
553         CONF_OPTION_BOOL('v', "verbose", NULL, &kmscon_conf.verbose, false),
554         CONF_OPTION_BOOL(0, "debug", aftercheck_debug, &kmscon_conf.debug, false),
555         CONF_OPTION_BOOL(0, "silent", NULL, &kmscon_conf.silent, false),
556         CONF_OPTION_BOOL(0, "fbdev", NULL, &kmscon_conf.use_fbdev, false),
557         CONF_OPTION_BOOL(0, "dumb", NULL, &kmscon_conf.dumb, false),
558         CONF_OPTION_UINT(0, "fps", NULL, &kmscon_conf.fps, 50),
559         CONF_OPTION_STRING(0, "render-engine", NULL, &kmscon_conf.render_engine, NULL),
560         CONF_OPTION_BOOL(0, "render-timing", NULL, &kmscon_conf.render_timing, false),
561         CONF_OPTION_BOOL('s', "switchvt", NULL, &kmscon_conf.switchvt, false),
562         CONF_OPTION_BOOL('l', "login", aftercheck_login, &kmscon_conf.login, false),
563         CONF_OPTION_STRING('t', "term", NULL, &kmscon_conf.term, "xterm-256color"),
564         CONF_OPTION_STRING(0, "palette", NULL, &kmscon_conf.palette, NULL),
565         CONF_OPTION_UINT(0, "sb-size", NULL, &kmscon_conf.sb_size, 1000),
566         CONF_OPTION_GRAB(0, "grab-scroll-up", NULL, &kmscon_conf.grab_scroll_up, &def_grab_scroll_up),
567         CONF_OPTION_GRAB(0, "grab-scroll-down", NULL, &kmscon_conf.grab_scroll_down, &def_grab_scroll_down),
568         CONF_OPTION_GRAB(0, "grab-page-up", NULL, &kmscon_conf.grab_page_up, &def_grab_page_up),
569         CONF_OPTION_GRAB(0, "grab-page-down", NULL, &kmscon_conf.grab_page_down, &def_grab_page_down),
570         CONF_OPTION_STRING(0, "xkb-layout", NULL, &kmscon_conf.xkb_layout, "us"),
571         CONF_OPTION_STRING(0, "xkb-variant", NULL, &kmscon_conf.xkb_variant, ""),
572         CONF_OPTION_STRING(0, "xkb-options", NULL, &kmscon_conf.xkb_options, ""),
573         CONF_OPTION_STRING(0, "font-engine", NULL, &kmscon_conf.font_engine, "pango"),
574         CONF_OPTION_UINT(0, "font-size", NULL, &kmscon_conf.font_size, 15),
575         CONF_OPTION_STRING(0, "font-name", NULL, &kmscon_conf.font_name, "monospace"),
576         CONF_OPTION_STRING_LIST(0, "seats", aftercheck_seats, &kmscon_conf.seats, def_seats),
577 };
578
579 int main(int argc, char **argv)
580 {
581         int ret;
582         struct kmscon_app app;
583         size_t onum;
584
585         onum = sizeof(options) / sizeof(*options);
586         ret = conf_parse_argv(options, onum, argc, argv);
587         if (ret)
588                 goto err_out;
589
590         if (kmscon_conf.exit) {
591                 conf_free(options, onum);
592                 return EXIT_SUCCESS;
593         }
594
595         if (!kmscon_conf.debug && !kmscon_conf.verbose && kmscon_conf.silent)
596                 log_set_config(&LOG_CONFIG_WARNING(0, 0, 0, 0));
597         else
598                 log_set_config(&LOG_CONFIG_INFO(kmscon_conf.debug,
599                                                 kmscon_conf.verbose));
600
601         log_print_init("kmscon");
602
603         ret = conf_parse_all_files(options, onum);
604         if (ret)
605                 goto err_out;
606
607         kmscon_font_unifont_load();
608         kmscon_font_8x16_load();
609         kmscon_font_pango_load();
610         kmscon_font_freetype2_load();
611         kmscon_text_bbulk_load();
612         kmscon_text_bblit_load();
613         kmscon_text_gltex_load();
614
615         memset(&app, 0, sizeof(app));
616         ret = setup_app(&app);
617         if (ret)
618                 goto err_unload;
619
620         if (kmscon_conf.switchvt) {
621                 /* TODO: implement automatic VT switching */
622         }
623
624         ev_eloop_run(app.eloop, -1);
625
626         if (kmscon_conf.switchvt) {
627                 /* The VT subsystem needs to acknowledge the VT-leave so if it
628                  * returns -EINPROGRESS we need to wait for the VT-leave SIGUSR2
629                  * signal to arrive. Therefore, we use a separate eloop object
630                  * which is used by the VT system only. Therefore, waiting on
631                  * this eloop allows us to safely wait 50ms for the SIGUSR2 to
632                  * arrive.
633                  * We use a timeout of 100ms to avoid haning on exit.
634                  * We could also wait on app.eloop but this would allow other
635                  * subsystems to continue receiving events and this is not what
636                  * we want.
637                  */
638                 if (ret == -EINPROGRESS)
639                         ev_eloop_run(app.vt_eloop, 50);
640         }
641
642         destroy_app(&app);
643         kmscon_text_gltex_unload();
644         kmscon_text_bblit_unload();
645         kmscon_text_bbulk_unload();
646         kmscon_font_freetype2_unload();
647         kmscon_font_pango_unload();
648         kmscon_font_8x16_unload();
649         kmscon_font_unifont_unload();
650         conf_free(options, onum);
651         log_info("exiting");
652
653         return EXIT_SUCCESS;
654
655 err_unload:
656         kmscon_text_gltex_unload();
657         kmscon_text_bblit_unload();
658         kmscon_text_bbulk_unload();
659         kmscon_font_freetype2_unload();
660         kmscon_font_pango_unload();
661         kmscon_font_8x16_unload();
662         kmscon_font_unifont_unload();
663 err_out:
664         conf_free(options, onum);
665         log_err("cannot initialize kmscon, errno %d: %s", ret, strerror(-ret));
666         return -ret;
667 }