5efa17b80beeee78e026029277d56e327e9060f8
[platform/upstream/kmscon.git] / src / kmscon_seat.c
1 /*
2  * Seats
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 /*
27  * Seats
28  * A seat is a single session that is self-hosting and provides all the
29  * interaction for a single logged-in user.
30  */
31
32 #include <errno.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include "conf.h"
36 #include "eloop.h"
37 #include "kmscon_cdev.h"
38 #include "kmscon_conf.h"
39 #include "kmscon_dummy.h"
40 #include "kmscon_seat.h"
41 #include "kmscon_terminal.h"
42 #include "shl_dlist.h"
43 #include "shl_log.h"
44 #include "uterm_input.h"
45 #include "uterm_video.h"
46 #include "uterm_vt.h"
47
48 #define LOG_SUBSYSTEM "seat"
49
50 struct kmscon_session {
51         struct shl_dlist list;
52         unsigned long ref;
53         struct kmscon_seat *seat;
54
55         bool enabled;
56         bool foreground;
57         bool deactivating;
58
59         struct ev_timer *timer;
60
61         kmscon_session_cb_t cb;
62         void *data;
63 };
64
65 struct kmscon_display {
66         struct shl_dlist list;
67         struct kmscon_seat *seat;
68         struct uterm_display *disp;
69         bool activated;
70 };
71
72 enum kmscon_async_schedule {
73         SCHEDULE_SWITCH,
74         SCHEDULE_VT,
75         SCHEDULE_UNREGISTER,
76 };
77
78 struct kmscon_seat {
79         struct ev_eloop *eloop;
80         struct uterm_vt_master *vtm;
81         struct conf_ctx *conf_ctx;
82         struct kmscon_conf_t *conf;
83
84         char *name;
85         struct uterm_input *input;
86         struct uterm_vt *vt;
87         struct shl_dlist displays;
88
89         size_t session_count;
90         struct shl_dlist sessions;
91
92         bool awake;
93         bool foreground;
94         struct kmscon_session *current_sess;
95         struct kmscon_session *scheduled_sess;
96         struct kmscon_session *dummy_sess;
97
98         unsigned int async_schedule;
99
100         kmscon_seat_cb_t cb;
101         void *data;
102 };
103
104 static int session_call(struct kmscon_session *sess, unsigned int event,
105                         struct uterm_display *disp)
106 {
107         struct kmscon_session_event ev;
108
109         if (!sess->cb)
110                 return 0;
111
112         memset(&ev, 0, sizeof(ev));
113         ev.type = event;
114         ev.disp = disp;
115         return sess->cb(sess, &ev, sess->data);
116 }
117
118 static int session_call_activate(struct kmscon_session *sess)
119 {
120         log_debug("activate session %p", sess);
121         return session_call(sess, KMSCON_SESSION_ACTIVATE, NULL);
122 }
123
124 static int session_call_deactivate(struct kmscon_session *sess)
125 {
126         log_debug("deactivate session %p", sess);
127         return session_call(sess, KMSCON_SESSION_DEACTIVATE, NULL);
128 }
129
130 static void session_call_display_new(struct kmscon_session *sess,
131                                      struct uterm_display *disp)
132 {
133         session_call(sess, KMSCON_SESSION_DISPLAY_NEW, disp);
134 }
135
136 static void session_call_display_gone(struct kmscon_session *sess,
137                                       struct uterm_display *disp)
138 {
139         session_call(sess, KMSCON_SESSION_DISPLAY_GONE, disp);
140 }
141
142 static void session_call_display_refresh(struct kmscon_session *sess,
143                                          struct uterm_display *disp)
144 {
145         session_call(sess, KMSCON_SESSION_DISPLAY_REFRESH, disp);
146 }
147
148 static void activate_display(struct kmscon_display *d)
149 {
150         int ret;
151         struct shl_dlist *iter, *tmp;
152         struct kmscon_session *s;
153         struct kmscon_seat *seat = d->seat;
154
155         if (d->activated || !d->seat->awake || !d->seat->foreground)
156                 return;
157
158         /* TODO: We always use the default mode for new displays but we should
159          * rather allow the user to specify different modes in the configuration
160          * files. */
161         if (uterm_display_get_state(d->disp) == UTERM_DISPLAY_INACTIVE) {
162                 ret = uterm_display_activate(d->disp, NULL);
163                 if (ret)
164                         return;
165
166                 d->activated = true;
167
168                 ret = uterm_display_set_dpms(d->disp, UTERM_DPMS_ON);
169                 if (ret)
170                         log_warning("cannot set DPMS state to on for display: %d",
171                                     ret);
172
173                 shl_dlist_for_each_safe(iter, tmp, &seat->sessions) {
174                         s = shl_dlist_entry(iter, struct kmscon_session, list);
175                         session_call_display_new(s, d->disp);
176                 }
177         }
178 }
179
180 static int seat_go_foreground(struct kmscon_seat *seat, bool force)
181 {
182         int ret;
183         struct shl_dlist *iter;
184         struct kmscon_display *d;
185
186         if (seat->foreground)
187                 return 0;
188         if (!seat->awake || (!force && seat->current_sess))
189                 return -EBUSY;
190
191         if (seat->cb) {
192                 ret = seat->cb(seat, KMSCON_SEAT_FOREGROUND, seat->data);
193                 if (ret) {
194                         log_warning("cannot put seat %s into foreground: %d",
195                                     seat->name, ret);
196                         return ret;
197                 }
198         }
199
200         seat->foreground = true;
201
202         shl_dlist_for_each(iter, &seat->displays) {
203                 d = shl_dlist_entry(iter, struct kmscon_display, list);
204                 activate_display(d);
205         }
206
207         return 0;
208 }
209
210 static int seat_go_background(struct kmscon_seat *seat, bool force)
211 {
212         int ret;
213
214         if (!seat->foreground)
215                 return 0;
216         if (!seat->awake || (!force && seat->current_sess))
217                 return -EBUSY;
218
219         if (seat->cb) {
220                 ret = seat->cb(seat, KMSCON_SEAT_BACKGROUND, seat->data);
221                 if (ret) {
222                         log_warning("cannot put seat %s into background: %d",
223                                     seat->name, ret);
224                         return ret;
225                 }
226         }
227
228         seat->foreground = false;
229         return 0;
230 }
231
232 static int seat_go_asleep(struct kmscon_seat *seat, bool force)
233 {
234         int ret, err = 0;
235
236         if (!seat->awake)
237                 return 0;
238         if (seat->current_sess || seat->foreground) {
239                 if (force) {
240                         seat->foreground = false;
241                         seat->current_sess = NULL;
242                         err = -EBUSY;
243                 } else {
244                         return -EBUSY;
245                 }
246         }
247
248         if (seat->cb) {
249                 ret = seat->cb(seat, KMSCON_SEAT_SLEEP, seat->data);
250                 if (ret) {
251                         log_warning("cannot put seat %s asleep: %d",
252                                     seat->name, ret);
253                         if (!force)
254                                 return ret;
255                 }
256         }
257
258         seat->awake = false;
259         uterm_input_sleep(seat->input);
260
261         return err;
262 }
263
264 static int seat_go_awake(struct kmscon_seat *seat)
265 {
266         int ret;
267
268         if (seat->awake)
269                 return 0;
270
271         if (seat->cb) {
272                 ret = seat->cb(seat, KMSCON_SEAT_WAKE_UP, seat->data);
273                 if (ret) {
274                         log_warning("cannot wake up seat %s: %d", seat->name,
275                                     ret);
276                         return ret;
277                 }
278         }
279
280         seat->awake = true;
281         uterm_input_wake_up(seat->input);
282
283         return 0;
284 }
285
286 static int seat_run(struct kmscon_seat *seat)
287 {
288         int ret;
289         struct kmscon_session *session;
290
291         if (!seat->awake)
292                 return -EBUSY;
293         if (seat->current_sess)
294                 return 0;
295
296         if (!seat->scheduled_sess) {
297                 log_debug("no session scheduled to run (num %zu)",
298                           seat->session_count);
299                 return -ENOENT;
300         }
301         session = seat->scheduled_sess;
302
303         if (session->foreground && !seat->foreground) {
304                 ret = seat_go_foreground(seat, false);
305                 if (ret) {
306                         log_warning("cannot put seat %s into foreground for session %p",
307                                     seat->name, session);
308                         return ret;
309                 }
310         } else if (!session->foreground && seat->foreground) {
311                 ret = seat_go_background(seat, false);
312                 if (ret) {
313                         log_warning("cannot put seat %s into background for session %p",
314                                     seat->name, session);
315                         return ret;
316                 }
317         }
318
319         ret = session_call_activate(session);
320         if (ret) {
321                 log_warning("cannot activate session %p: %d", session, ret);
322                 return ret;
323         }
324
325         seat->current_sess = session;
326
327         return 0;
328 }
329
330 static void session_deactivate(struct kmscon_session *sess)
331 {
332         if (sess->seat->current_sess != sess)
333                 return;
334
335         sess->seat->async_schedule = SCHEDULE_SWITCH;
336         sess->deactivating = false;
337         sess->seat->current_sess = NULL;
338 }
339
340 static int seat_pause(struct kmscon_seat *seat, bool force)
341 {
342         int ret;
343
344         if (!seat->current_sess)
345                 return 0;
346
347         seat->current_sess->deactivating = true;
348         ret = session_call_deactivate(seat->current_sess);
349         if (ret) {
350                 if (ret == -EINPROGRESS)
351                         log_debug("pending deactivation for session %p",
352                                   seat->current_sess);
353                 else
354                         log_warning("cannot deactivate session %p: %d",
355                                     seat->current_sess, ret);
356                 if (!force)
357                         return ret;
358         }
359
360         session_deactivate(seat->current_sess);
361
362         return ret;
363 }
364
365 static void seat_reschedule(struct kmscon_seat *seat)
366 {
367         struct shl_dlist *iter, *start;
368         struct kmscon_session *sess;
369
370         if (seat->scheduled_sess && seat->scheduled_sess->enabled)
371                 return;
372
373         if (seat->current_sess && seat->current_sess->enabled) {
374                 seat->scheduled_sess = seat->current_sess;
375                 return;
376         }
377
378         if (seat->current_sess)
379                 start = &seat->current_sess->list;
380         else
381                 start = &seat->sessions;
382
383         shl_dlist_for_each_but_one(iter, start, &seat->sessions) {
384                 sess = shl_dlist_entry(iter, struct kmscon_session, list);
385                 if (sess == seat->dummy_sess || !sess->enabled)
386                         continue;
387                 seat->scheduled_sess = sess;
388                 return;
389         }
390
391         if (seat->dummy_sess && seat->dummy_sess->enabled)
392                 seat->scheduled_sess = seat->dummy_sess;
393         else
394                 seat->scheduled_sess = NULL;
395 }
396
397 static bool seat_has_schedule(struct kmscon_seat *seat)
398 {
399         return seat->scheduled_sess &&
400                seat->scheduled_sess != seat->current_sess;
401 }
402
403 static int seat_switch(struct kmscon_seat *seat)
404 {
405         int ret;
406
407         seat->async_schedule = SCHEDULE_SWITCH;
408         ret = seat_pause(seat, false);
409         if (ret)
410                 return ret;
411
412         return seat_run(seat);
413 }
414
415 static void seat_next(struct kmscon_seat *seat)
416 {
417         struct shl_dlist *cur, *iter;
418         struct kmscon_session *s, *next;
419
420         if (seat->current_sess)
421                 cur = &seat->current_sess->list;
422         else if (seat->session_count)
423                 cur = &seat->sessions;
424         else
425                 return;
426
427         next = NULL;
428         if (!seat->current_sess && seat->dummy_sess &&
429             seat->dummy_sess->enabled)
430                 next = seat->dummy_sess;
431
432         shl_dlist_for_each_but_one(iter, cur, &seat->sessions) {
433                 s = shl_dlist_entry(iter, struct kmscon_session, list);
434                 if (!s->enabled || seat->dummy_sess == s)
435                         continue;
436
437                 next = s;
438                 break;
439         }
440
441         if (!next)
442                 return;
443
444         seat->scheduled_sess = next;
445         seat_switch(seat);
446 }
447
448 static void seat_prev(struct kmscon_seat *seat)
449 {
450         struct shl_dlist *cur, *iter;
451         struct kmscon_session *s, *prev;
452
453         if (seat->current_sess)
454                 cur = &seat->current_sess->list;
455         else if (seat->session_count)
456                 cur = &seat->sessions;
457         else
458                 return;
459
460         prev = NULL;
461         if (!seat->current_sess && seat->dummy_sess &&
462             seat->dummy_sess->enabled)
463                 prev = seat->dummy_sess;
464
465         shl_dlist_for_each_reverse_but_one(iter, cur, &seat->sessions) {
466                 s = shl_dlist_entry(iter, struct kmscon_session, list);
467                 if (!s->enabled || seat->dummy_sess == s)
468                         continue;
469
470                 prev = s;
471                 break;
472         }
473
474         if (!prev)
475                 return;
476
477         seat->scheduled_sess = prev;
478         seat_switch(seat);
479 }
480
481 static int seat_add_display(struct kmscon_seat *seat,
482                             struct uterm_display *disp)
483 {
484         struct kmscon_display *d;
485
486         log_debug("add display %p to seat %s", disp, seat->name);
487
488         d = malloc(sizeof(*d));
489         if (!d)
490                 return -ENOMEM;
491         memset(d, 0, sizeof(*d));
492         d->disp = disp;
493         d->seat = seat;
494
495         uterm_display_ref(d->disp);
496         shl_dlist_link(&seat->displays, &d->list);
497         activate_display(d);
498         return 0;
499 }
500
501 static void seat_remove_display(struct kmscon_seat *seat,
502                                 struct kmscon_display *d)
503 {
504         struct shl_dlist *iter, *tmp;
505         struct kmscon_session *s;
506
507         log_debug("remove display %p from seat %s", d->disp, seat->name);
508
509         shl_dlist_unlink(&d->list);
510
511         if (d->activated) {
512                 shl_dlist_for_each_safe(iter, tmp, &seat->sessions) {
513                         s = shl_dlist_entry(iter, struct kmscon_session, list);
514                         session_call_display_gone(s, d->disp);
515                 }
516         }
517
518         uterm_display_unref(d->disp);
519         free(d);
520 }
521
522 static void seat_refresh_display(struct kmscon_seat *seat,
523                                  struct kmscon_display *d)
524 {
525         struct shl_dlist *iter;
526         struct kmscon_session *s;
527
528         log_debug("refresh display %p from seat %s", d->disp, seat->name);
529
530         if (d->activated) {
531                 shl_dlist_for_each(iter, &seat->sessions) {
532                         s = shl_dlist_entry(iter, struct kmscon_session, list);
533                         session_call_display_refresh(s, d->disp);
534                 }
535         }
536 }
537
538 static int seat_vt_event(struct uterm_vt *vt, struct uterm_vt_event *ev,
539                          void *data)
540 {
541         struct kmscon_seat *seat = data;
542         int ret;
543
544         switch (ev->action) {
545         case UTERM_VT_ACTIVATE:
546                 ret = seat_go_awake(seat);
547                 if (ret)
548                         return ret;
549                 seat_run(seat);
550                 break;
551         case UTERM_VT_DEACTIVATE:
552                 seat->async_schedule = SCHEDULE_VT;
553                 ret = seat_pause(seat, false);
554                 if (ret)
555                         return ret;
556                 ret = seat_go_background(seat, false);
557                 if (ret)
558                         return ret;
559                 ret = seat_go_asleep(seat, false);
560                 if (ret)
561                         return ret;
562                 break;
563         case UTERM_VT_HUP:
564                 if (seat->cb)
565                         seat->cb(seat, KMSCON_SEAT_HUP, seat->data);
566                 break;
567         }
568
569         return 0;
570 }
571
572 static void seat_input_event(struct uterm_input *input,
573                              struct uterm_input_event *ev,
574                              void *data)
575 {
576         struct kmscon_seat *seat = data;
577         struct kmscon_session *s;
578         int ret;
579
580         if (ev->handled || !seat->awake)
581                 return;
582
583         if (conf_grab_matches(seat->conf->grab_session_next,
584                               ev->mods, ev->num_syms, ev->keysyms)) {
585                 ev->handled = true;
586                 if (!seat->conf->session_control)
587                         return;
588                 seat_next(seat);
589                 return;
590         }
591         if (conf_grab_matches(seat->conf->grab_session_prev,
592                               ev->mods, ev->num_syms, ev->keysyms)) {
593                 ev->handled = true;
594                 if (!seat->conf->session_control)
595                         return;
596                 seat_prev(seat);
597                 return;
598         }
599         if (conf_grab_matches(seat->conf->grab_session_dummy,
600                               ev->mods, ev->num_syms, ev->keysyms)) {
601                 ev->handled = true;
602                 if (!seat->conf->session_control)
603                         return;
604                 seat->scheduled_sess = seat->dummy_sess;
605                 seat_switch(seat);
606                 return;
607         }
608         if (conf_grab_matches(seat->conf->grab_session_close,
609                               ev->mods, ev->num_syms, ev->keysyms)) {
610                 ev->handled = true;
611                 if (!seat->conf->session_control)
612                         return;
613                 s = seat->current_sess;
614                 if (!s)
615                         return;
616                 if (s == seat->dummy_sess)
617                         return;
618
619                 /* First time this is invoked on a session, we simply try
620                  * unloading it. If it fails, we give it some time. If this is
621                  * invoked a second time, we notice that we already tried
622                  * removing it and so we go straight to unregistering the
623                  * session unconditionally. */
624                 if (!s->deactivating) {
625                         seat->async_schedule = SCHEDULE_UNREGISTER;
626                         ret = seat_pause(seat, false);
627                         if (ret)
628                                 return;
629                 }
630
631                 kmscon_session_unregister(s);
632                 return;
633         }
634         if (conf_grab_matches(seat->conf->grab_terminal_new,
635                               ev->mods, ev->num_syms, ev->keysyms)) {
636                 ev->handled = true;
637                 if (!seat->conf->session_control)
638                         return;
639                 ret = kmscon_terminal_register(&s, seat,
640                                                uterm_vt_get_num(seat->vt));
641                 if (ret == -EOPNOTSUPP) {
642                         log_notice("terminal support not compiled in");
643                 } else if (ret) {
644                         log_error("cannot register terminal session: %d", ret);
645                 } else {
646                         s->enabled = true;
647                         seat->scheduled_sess = s;
648                         seat_switch(seat);
649                 }
650                 return;
651         }
652 }
653
654 int kmscon_seat_new(struct kmscon_seat **out,
655                     struct conf_ctx *main_conf,
656                     struct ev_eloop *eloop,
657                     struct uterm_vt_master *vtm,
658                     unsigned int vt_types,
659                     const char *seatname,
660                     kmscon_seat_cb_t cb,
661                     void *data)
662 {
663         struct kmscon_seat *seat;
664         int ret;
665         char *keymap;
666
667         if (!out || !eloop || !vtm || !seatname)
668                 return -EINVAL;
669
670         seat = malloc(sizeof(*seat));
671         if (!seat)
672                 return -ENOMEM;
673         memset(seat, 0, sizeof(*seat));
674         seat->eloop = eloop;
675         seat->vtm = vtm;
676         seat->cb = cb;
677         seat->data = data;
678         shl_dlist_init(&seat->displays);
679         shl_dlist_init(&seat->sessions);
680
681         seat->name = strdup(seatname);
682         if (!seat->name) {
683                 log_error("cannot copy string");
684                 ret = -ENOMEM;
685                 goto err_free;
686         }
687
688         ret = kmscon_conf_new(&seat->conf_ctx);
689         if (ret) {
690                 log_error("cannot create seat configuration object: %d", ret);
691                 goto err_name;
692         }
693         seat->conf = conf_ctx_get_mem(seat->conf_ctx);
694
695         ret = kmscon_conf_load_seat(seat->conf_ctx, main_conf, seat->name);
696         if (ret) {
697                 log_error("cannot parse seat configuration on seat %s: %d",
698                           seat->name, ret);
699                 goto err_conf;
700         }
701
702         /* TODO: The XKB-API currently requires zero-terminated strings as
703          * keymap input. Hence, we have to read it in instead of using mmap().
704          * We should fix this upstream! */
705         keymap = NULL;
706         if (seat->conf->xkb_keymap && *seat->conf->xkb_keymap) {
707                 ret = shl_read_file(seat->conf->xkb_keymap, &keymap, NULL);
708                 if (ret)
709                         log_error("cannot read keymap file %s: %d",
710                                   seat->conf->xkb_keymap, ret);
711         }
712
713         ret = uterm_input_new(&seat->input, seat->eloop,
714                               seat->conf->xkb_model,
715                               seat->conf->xkb_layout,
716                               seat->conf->xkb_variant,
717                               seat->conf->xkb_options,
718                               keymap,
719                               seat->conf->xkb_repeat_delay,
720                               seat->conf->xkb_repeat_rate);
721         free(keymap);
722
723         if (ret)
724                 goto err_conf;
725
726         ret = uterm_input_register_cb(seat->input, seat_input_event, seat);
727         if (ret)
728                 goto err_input;
729
730         ret = uterm_vt_allocate(seat->vtm, &seat->vt,
731                                 vt_types, seat->name,
732                                 seat->input, seat->conf->vt, seat_vt_event,
733                                 seat);
734         if (ret)
735                 goto err_input_cb;
736
737         ev_eloop_ref(seat->eloop);
738         uterm_vt_master_ref(seat->vtm);
739         *out = seat;
740         return 0;
741
742 err_input_cb:
743         uterm_input_unregister_cb(seat->input, seat_input_event, seat);
744 err_input:
745         uterm_input_unref(seat->input);
746 err_conf:
747         kmscon_conf_free(seat->conf_ctx);
748 err_name:
749         free(seat->name);
750 err_free:
751         free(seat);
752         return ret;
753 }
754
755 void kmscon_seat_free(struct kmscon_seat *seat)
756 {
757         struct kmscon_display *d;
758         struct kmscon_session *s;
759         int ret;
760
761         if (!seat)
762                 return;
763
764         ret = seat_pause(seat, true);
765         if (ret)
766                 log_warning("destroying seat %s while session %p is active",
767                             seat->name, seat->current_sess);
768
769         ret = seat_go_asleep(seat, true);
770         if (ret)
771                 log_warning("destroying seat %s while still awake: %d",
772                             seat->name, ret);
773
774         while (!shl_dlist_empty(&seat->sessions)) {
775                 s = shl_dlist_entry(seat->sessions.next,
776                                     struct kmscon_session,
777                                     list);
778                 kmscon_session_unregister(s);
779         }
780
781         while (!shl_dlist_empty(&seat->displays)) {
782                 d = shl_dlist_entry(seat->displays.next,
783                                     struct kmscon_display,
784                                     list);
785                 seat_remove_display(seat, d);
786         }
787
788         uterm_vt_deallocate(seat->vt);
789         uterm_input_unregister_cb(seat->input, seat_input_event, seat);
790         uterm_input_unref(seat->input);
791         kmscon_conf_free(seat->conf_ctx);
792         free(seat->name);
793         uterm_vt_master_unref(seat->vtm);
794         ev_eloop_unref(seat->eloop);
795         free(seat);
796 }
797
798 void kmscon_seat_startup(struct kmscon_seat *seat)
799 {
800         int ret;
801         struct kmscon_session *s;
802
803         if (!seat)
804                 return;
805
806         ret = kmscon_dummy_register(&s, seat);
807         if (ret == -EOPNOTSUPP) {
808                 log_notice("dummy sessions not compiled in");
809         } else if (ret) {
810                 log_error("cannot register dummy session: %d", ret);
811         } else {
812                 seat->dummy_sess = s;
813                 kmscon_session_enable(s);
814         }
815
816         if (seat->conf->terminal_session) {
817                 ret = kmscon_terminal_register(&s, seat,
818                                                uterm_vt_get_num(seat->vt));
819                 if (ret == -EOPNOTSUPP)
820                         log_notice("terminal support not compiled in");
821                 else if (ret)
822                         log_error("cannot register terminal session");
823                 else
824                         kmscon_session_enable(s);
825         }
826
827         if (seat->conf->cdev_session) {
828                 ret = kmscon_cdev_register(&s, seat);
829                 if (ret == -EOPNOTSUPP)
830                         log_notice("cdev sessions not compiled in");
831                 else if (ret)
832                         log_error("cannot register cdev session");
833         }
834
835         if (seat->conf->switchvt ||
836             uterm_vt_get_type(seat->vt) == UTERM_VT_FAKE)
837                 uterm_vt_activate(seat->vt);
838 }
839
840 int kmscon_seat_add_display(struct kmscon_seat *seat,
841                             struct uterm_display *disp)
842 {
843         if (!seat || !disp)
844                 return -EINVAL;
845
846         return seat_add_display(seat, disp);
847 }
848
849 void kmscon_seat_remove_display(struct kmscon_seat *seat,
850                                 struct uterm_display *disp)
851 {
852         struct shl_dlist *iter;
853         struct kmscon_display *d;
854
855         if (!seat || !disp)
856                 return;
857
858         shl_dlist_for_each(iter, &seat->displays) {
859                 d = shl_dlist_entry(iter, struct kmscon_display, list);
860                 if (d->disp != disp)
861                         continue;
862
863                 seat_remove_display(seat, d);
864                 break;
865         }
866 }
867
868 void kmscon_seat_refresh_display(struct kmscon_seat *seat,
869                                  struct uterm_display *disp)
870 {
871         struct shl_dlist *iter;
872         struct kmscon_display *d;
873
874         if (!seat || !disp)
875                 return;
876
877         shl_dlist_for_each(iter, &seat->displays) {
878                 d = shl_dlist_entry(iter, struct kmscon_display, list);
879                 if (d->disp != disp)
880                         continue;
881
882                 seat_refresh_display(seat, d);
883                 break;
884         }
885 }
886
887 int kmscon_seat_add_input(struct kmscon_seat *seat, const char *node)
888 {
889         if (!seat || !node)
890                 return -EINVAL;
891
892         uterm_input_add_dev(seat->input, node);
893         return 0;
894 }
895
896 void kmscon_seat_remove_input(struct kmscon_seat *seat, const char *node)
897 {
898         if (!seat || !node)
899                 return;
900
901         uterm_input_remove_dev(seat->input, node);
902 }
903
904 const char *kmscon_seat_get_name(struct kmscon_seat *seat)
905 {
906         if (!seat)
907                 return NULL;
908
909         return seat->name;
910 }
911
912 struct uterm_input *kmscon_seat_get_input(struct kmscon_seat *seat)
913 {
914         if (!seat)
915                 return NULL;
916
917         return seat->input;
918 }
919
920 struct ev_eloop *kmscon_seat_get_eloop(struct kmscon_seat *seat)
921 {
922         if (!seat)
923                 return NULL;
924
925         return seat->eloop;
926 }
927
928 struct conf_ctx *kmscon_seat_get_conf(struct kmscon_seat *seat)
929 {
930         if (!seat)
931                 return NULL;
932
933         return seat->conf_ctx;
934 }
935
936 void kmscon_seat_schedule(struct kmscon_seat *seat, unsigned int id)
937 {
938         struct shl_dlist *iter;
939         struct kmscon_session *s, *next;
940
941         if (!seat)
942                 return;
943
944         next = seat->dummy_sess;
945         shl_dlist_for_each(iter, &seat->sessions) {
946                 s = shl_dlist_entry(iter, struct kmscon_session, list);
947                 if (!s->enabled || seat->dummy_sess == s ||
948                     seat->current_sess == s)
949                         continue;
950
951                 next = s;
952                 if (!id--)
953                         break;
954         }
955
956         seat->scheduled_sess = next;
957         if (seat_has_schedule(seat))
958                 seat_switch(seat);
959 }
960
961 int kmscon_seat_register_session(struct kmscon_seat *seat,
962                                  struct kmscon_session **out,
963                                  kmscon_session_cb_t cb,
964                                  void *data)
965 {
966         struct kmscon_session *sess;
967         struct shl_dlist *iter;
968         struct kmscon_display *d;
969
970         if (!seat || !out)
971                 return -EINVAL;
972
973         if (seat->conf->session_max &&
974             seat->session_count >= seat->conf->session_max) {
975                 log_warning("maximum number of sessions reached (%d), dropping new session",
976                             seat->conf->session_max);
977                 return -EOVERFLOW;
978         }
979
980         sess = malloc(sizeof(*sess));
981         if (!sess) {
982                 log_error("cannot allocate memory for new session on seat %s",
983                           seat->name);
984                 return -ENOMEM;
985         }
986
987         log_debug("register session %p", sess);
988
989         memset(sess, 0, sizeof(*sess));
990         sess->ref = 1;
991         sess->seat = seat;
992         sess->cb = cb;
993         sess->data = data;
994         sess->foreground = true;
995
996         /* register new sessions next to the current one */
997         if (seat->current_sess)
998                 shl_dlist_link(&seat->current_sess->list, &sess->list);
999         else
1000                 shl_dlist_link_tail(&seat->sessions, &sess->list);
1001
1002         ++seat->session_count;
1003         *out = sess;
1004
1005         shl_dlist_for_each(iter, &seat->displays) {
1006                 d = shl_dlist_entry(iter, struct kmscon_display, list);
1007                 session_call_display_new(sess, d->disp);
1008         }
1009
1010         return 0;
1011 }
1012
1013 void kmscon_session_ref(struct kmscon_session *sess)
1014 {
1015         if (!sess || !sess->ref)
1016                 return;
1017
1018         ++sess->ref;
1019 }
1020
1021 void kmscon_session_unref(struct kmscon_session *sess)
1022 {
1023         if (!sess || !sess->ref || --sess->ref)
1024                 return;
1025
1026         kmscon_session_unregister(sess);
1027         free(sess);
1028 }
1029
1030 void kmscon_session_unregister(struct kmscon_session *sess)
1031 {
1032         struct kmscon_seat *seat;
1033         int ret;
1034         bool forced = false;
1035
1036         if (!sess || !sess->seat)
1037                 return;
1038
1039         log_debug("unregister session %p", sess);
1040
1041         seat = sess->seat;
1042         sess->enabled = false;
1043         if (seat->dummy_sess == sess)
1044                 seat->dummy_sess = NULL;
1045         seat_reschedule(seat);
1046
1047         if (seat->current_sess == sess) {
1048                 ret = seat_pause(seat, true);
1049                 if (ret) {
1050                         forced = true;
1051                         log_warning("unregistering active session %p; skipping automatic session-switch",
1052                                     sess);
1053                 }
1054         }
1055
1056         shl_dlist_unlink(&sess->list);
1057         --seat->session_count;
1058         sess->seat = NULL;
1059
1060         session_call(sess, KMSCON_SESSION_UNREGISTER, NULL);
1061         kmscon_session_unref(sess);
1062
1063         /* If this session was active and we couldn't deactivate it, then it
1064          * might still have resources allocated that couldn't get freed. In this
1065          * case we should not automatically switch to the next session as it is
1066          * very likely that it will not be able to start.
1067          * Instead, we stay inactive and wait for user/external input to switch
1068          * to another session. This delay will then hopefully be long enough so
1069          * all resources got freed. */
1070         if (!forced)
1071                 seat_run(seat);
1072 }
1073
1074 bool kmscon_session_is_registered(struct kmscon_session *sess)
1075 {
1076         return sess && sess->seat;
1077 }
1078
1079 bool kmscon_session_is_active(struct kmscon_session *sess)
1080 {
1081         return sess && sess->seat && sess->seat->current_sess == sess;
1082 }
1083
1084 int kmscon_session_set_foreground(struct kmscon_session *sess)
1085 {
1086         struct kmscon_seat *seat;
1087         int ret;
1088
1089         if (!sess)
1090                 return -EINVAL;
1091         if (sess->foreground)
1092                 return 0;
1093
1094         seat = sess->seat;
1095         if (seat && seat->current_sess == sess && !seat->foreground) {
1096                 ret = seat_go_foreground(seat, true);
1097                 if (ret)
1098                         return ret;
1099         }
1100
1101         sess->foreground = true;
1102         return 0;
1103 }
1104
1105 int kmscon_session_set_background(struct kmscon_session *sess)
1106 {
1107         struct kmscon_seat *seat;
1108         int ret;
1109
1110         if (!sess)
1111                 return -EINVAL;
1112         if (!sess->foreground)
1113                 return 0;
1114
1115         seat = sess->seat;
1116         if (seat && seat->current_sess == sess && seat->foreground) {
1117                 ret = seat_go_background(seat, true);
1118                 if (ret)
1119                         return ret;
1120         }
1121
1122         sess->foreground = false;
1123         return 0;
1124 }
1125
1126 void kmscon_session_schedule(struct kmscon_session *sess)
1127 {
1128         struct kmscon_seat *seat;
1129
1130         if (!sess || !sess->seat)
1131                 return;
1132
1133         seat = sess->seat;
1134         seat->scheduled_sess = sess;
1135         seat_reschedule(seat);
1136         if (seat_has_schedule(seat))
1137                 seat_switch(seat);
1138 }
1139
1140 void kmscon_session_enable(struct kmscon_session *sess)
1141 {
1142         if (!sess || sess->enabled)
1143                 return;
1144
1145         log_debug("enable session %p", sess);
1146         sess->enabled = true;
1147         if (sess->seat &&
1148             (!sess->seat->current_sess ||
1149              sess->seat->current_sess == sess->seat->dummy_sess)) {
1150                 sess->seat->scheduled_sess = sess;
1151                 if (seat_has_schedule(sess->seat))
1152                         seat_switch(sess->seat);
1153         }
1154 }
1155
1156 void kmscon_session_disable(struct kmscon_session *sess)
1157 {
1158         if (!sess || !sess->enabled)
1159                 return;
1160
1161         log_debug("disable session %p", sess);
1162         sess->enabled = false;
1163 }
1164
1165 bool kmscon_session_is_enabled(struct kmscon_session *sess)
1166 {
1167         return sess && sess->enabled;
1168 }
1169
1170 void kmscon_session_notify_deactivated(struct kmscon_session *sess)
1171 {
1172         struct kmscon_seat *seat;
1173         int ret;
1174         unsigned int sched;
1175
1176         if (!sess || !sess->seat)
1177                 return;
1178
1179         seat = sess->seat;
1180         if (seat->current_sess != sess)
1181                 return;
1182
1183         sched = seat->async_schedule;
1184         log_debug("session %p notified core about deactivation (schedule: %u)",
1185                   sess, sched);
1186         session_deactivate(sess);
1187         seat_reschedule(seat);
1188
1189         if (sched == SCHEDULE_VT) {
1190                 ret = seat_go_background(seat, false);
1191                 if (ret)
1192                         return;
1193                 ret = seat_go_asleep(seat, false);
1194                 if (ret)
1195                         return;
1196                 uterm_vt_retry(seat->vt);
1197         } else if (sched == SCHEDULE_UNREGISTER) {
1198                 kmscon_session_unregister(sess);
1199         } else {
1200                 seat_switch(seat);
1201         }
1202 }