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