4 * Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com>
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:
14 * The above copyright notice and this permission notice shall be included
15 * in all copies or substantial portions of the Software.
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.
28 * A seat is a single session that is self-hosting and provides all the
29 * interaction for a single logged-in user.
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"
44 #include "uterm_input.h"
45 #include "uterm_video.h"
48 #define LOG_SUBSYSTEM "seat"
50 struct kmscon_session {
51 struct shl_dlist list;
53 struct kmscon_seat *seat;
59 struct ev_timer *timer;
61 kmscon_session_cb_t cb;
65 struct kmscon_display {
66 struct shl_dlist list;
67 struct kmscon_seat *seat;
68 struct uterm_display *disp;
72 enum kmscon_async_schedule {
79 struct ev_eloop *eloop;
80 struct uterm_vt_master *vtm;
81 struct conf_ctx *conf_ctx;
82 struct kmscon_conf_t *conf;
85 struct uterm_input *input;
87 struct shl_dlist displays;
90 struct shl_dlist sessions;
94 struct kmscon_session *current_sess;
95 struct kmscon_session *scheduled_sess;
96 struct kmscon_session *dummy_sess;
98 unsigned int async_schedule;
104 static int session_call(struct kmscon_session *sess, unsigned int event,
105 struct uterm_display *disp)
107 struct kmscon_session_event ev;
112 memset(&ev, 0, sizeof(ev));
115 return sess->cb(sess, &ev, sess->data);
118 static int session_call_activate(struct kmscon_session *sess)
120 log_debug("activate session %p", sess);
121 return session_call(sess, KMSCON_SESSION_ACTIVATE, NULL);
124 static int session_call_deactivate(struct kmscon_session *sess)
126 log_debug("deactivate session %p", sess);
127 return session_call(sess, KMSCON_SESSION_DEACTIVATE, NULL);
130 static void session_call_display_new(struct kmscon_session *sess,
131 struct uterm_display *disp)
133 session_call(sess, KMSCON_SESSION_DISPLAY_NEW, disp);
136 static void session_call_display_gone(struct kmscon_session *sess,
137 struct uterm_display *disp)
139 session_call(sess, KMSCON_SESSION_DISPLAY_GONE, disp);
142 static void activate_display(struct kmscon_display *d)
145 struct shl_dlist *iter, *tmp;
146 struct kmscon_session *s;
147 struct kmscon_seat *seat = d->seat;
149 if (d->activated || !d->seat->awake || !d->seat->foreground)
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
155 if (uterm_display_get_state(d->disp) == UTERM_DISPLAY_INACTIVE) {
156 ret = uterm_display_activate(d->disp, NULL);
162 ret = uterm_display_set_dpms(d->disp, UTERM_DPMS_ON);
164 log_warning("cannot set DPMS state to on for display: %d",
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);
174 static int seat_go_foreground(struct kmscon_seat *seat, bool force)
177 struct shl_dlist *iter;
178 struct kmscon_display *d;
180 if (seat->foreground)
182 if (!seat->awake || (!force && seat->current_sess))
186 ret = seat->cb(seat, KMSCON_SEAT_FOREGROUND, seat->data);
188 log_warning("cannot put seat %s into foreground: %d",
194 seat->foreground = true;
196 shl_dlist_for_each(iter, &seat->displays) {
197 d = shl_dlist_entry(iter, struct kmscon_display, list);
204 static int seat_go_background(struct kmscon_seat *seat, bool force)
208 if (!seat->foreground)
210 if (!seat->awake || (!force && seat->current_sess))
214 ret = seat->cb(seat, KMSCON_SEAT_BACKGROUND, seat->data);
216 log_warning("cannot put seat %s into background: %d",
222 seat->foreground = false;
226 static int seat_go_asleep(struct kmscon_seat *seat, bool force)
232 if (seat->current_sess || seat->foreground) {
234 seat->foreground = false;
235 seat->current_sess = NULL;
243 ret = seat->cb(seat, KMSCON_SEAT_SLEEP, seat->data);
245 log_warning("cannot put seat %s asleep: %d",
253 uterm_input_sleep(seat->input);
258 static int seat_go_awake(struct kmscon_seat *seat)
266 ret = seat->cb(seat, KMSCON_SEAT_WAKE_UP, seat->data);
268 log_warning("cannot wake up seat %s: %d", seat->name,
275 uterm_input_wake_up(seat->input);
280 static int seat_run(struct kmscon_seat *seat)
283 struct kmscon_session *session;
287 if (seat->current_sess)
290 if (!seat->scheduled_sess) {
291 log_debug("no session scheduled to run (num %zu)",
292 seat->session_count);
295 session = seat->scheduled_sess;
297 if (session->foreground && !seat->foreground) {
298 ret = seat_go_foreground(seat, false);
300 log_warning("cannot put seat %s into foreground for session %p",
301 seat->name, session);
304 } else if (!session->foreground && seat->foreground) {
305 ret = seat_go_background(seat, false);
307 log_warning("cannot put seat %s into background for session %p",
308 seat->name, session);
313 ret = session_call_activate(session);
315 log_warning("cannot activate session %p: %d", session, ret);
319 seat->current_sess = session;
324 static void session_deactivate(struct kmscon_session *sess)
326 if (sess->seat->current_sess != sess)
329 sess->seat->async_schedule = SCHEDULE_SWITCH;
330 sess->deactivating = false;
331 sess->seat->current_sess = NULL;
334 static int seat_pause(struct kmscon_seat *seat, bool force)
338 if (!seat->current_sess)
341 seat->current_sess->deactivating = true;
342 ret = session_call_deactivate(seat->current_sess);
344 if (ret == -EINPROGRESS)
345 log_debug("pending deactivation for session %p",
348 log_warning("cannot deactivate session %p: %d",
349 seat->current_sess, ret);
354 session_deactivate(seat->current_sess);
359 static void seat_reschedule(struct kmscon_seat *seat)
361 struct shl_dlist *iter, *start;
362 struct kmscon_session *sess;
364 if (seat->scheduled_sess && seat->scheduled_sess->enabled)
367 if (seat->current_sess && seat->current_sess->enabled) {
368 seat->scheduled_sess = seat->current_sess;
372 if (seat->current_sess)
373 start = &seat->current_sess->list;
375 start = &seat->sessions;
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)
381 seat->scheduled_sess = sess;
385 if (seat->dummy_sess && seat->dummy_sess->enabled)
386 seat->scheduled_sess = seat->dummy_sess;
388 seat->scheduled_sess = NULL;
391 static bool seat_has_schedule(struct kmscon_seat *seat)
393 return seat->scheduled_sess &&
394 seat->scheduled_sess != seat->current_sess;
397 static int seat_switch(struct kmscon_seat *seat)
401 seat->async_schedule = SCHEDULE_SWITCH;
402 ret = seat_pause(seat, false);
406 return seat_run(seat);
409 static void seat_next(struct kmscon_seat *seat)
411 struct shl_dlist *cur, *iter;
412 struct kmscon_session *s, *next;
414 if (seat->current_sess)
415 cur = &seat->current_sess->list;
416 else if (seat->session_count)
417 cur = &seat->sessions;
422 if (!seat->current_sess && seat->dummy_sess &&
423 seat->dummy_sess->enabled)
424 next = seat->dummy_sess;
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)
438 seat->scheduled_sess = next;
442 static void seat_prev(struct kmscon_seat *seat)
444 struct shl_dlist *cur, *iter;
445 struct kmscon_session *s, *prev;
447 if (seat->current_sess)
448 cur = &seat->current_sess->list;
449 else if (seat->session_count)
450 cur = &seat->sessions;
455 if (!seat->current_sess && seat->dummy_sess &&
456 seat->dummy_sess->enabled)
457 prev = seat->dummy_sess;
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)
471 seat->scheduled_sess = prev;
475 static int seat_add_display(struct kmscon_seat *seat,
476 struct uterm_display *disp)
478 struct kmscon_display *d;
480 log_debug("add display %p to seat %s", disp, seat->name);
482 d = malloc(sizeof(*d));
485 memset(d, 0, sizeof(*d));
489 uterm_display_ref(d->disp);
490 shl_dlist_link(&seat->displays, &d->list);
495 static void seat_remove_display(struct kmscon_seat *seat,
496 struct kmscon_display *d)
498 struct shl_dlist *iter, *tmp;
499 struct kmscon_session *s;
501 log_debug("remove display %p from seat %s", d->disp, seat->name);
503 shl_dlist_unlink(&d->list);
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);
512 uterm_display_unref(d->disp);
516 static int seat_vt_event(struct uterm_vt *vt, struct uterm_vt_event *ev,
519 struct kmscon_seat *seat = data;
522 switch (ev->action) {
523 case UTERM_VT_ACTIVATE:
524 ret = seat_go_awake(seat);
529 case UTERM_VT_DEACTIVATE:
530 seat->async_schedule = SCHEDULE_VT;
531 ret = seat_pause(seat, false);
534 ret = seat_go_background(seat, false);
537 ret = seat_go_asleep(seat, false);
543 seat->cb(seat, KMSCON_SEAT_HUP, seat->data);
550 static void seat_input_event(struct uterm_input *input,
551 struct uterm_input_event *ev,
554 struct kmscon_seat *seat = data;
555 struct kmscon_session *s;
558 if (ev->handled || !seat->awake)
561 if (conf_grab_matches(seat->conf->grab_session_next,
562 ev->mods, ev->num_syms, ev->keysyms)) {
564 if (!seat->conf->session_control)
569 if (conf_grab_matches(seat->conf->grab_session_prev,
570 ev->mods, ev->num_syms, ev->keysyms)) {
572 if (!seat->conf->session_control)
577 if (conf_grab_matches(seat->conf->grab_session_dummy,
578 ev->mods, ev->num_syms, ev->keysyms)) {
580 if (!seat->conf->session_control)
582 seat->scheduled_sess = seat->dummy_sess;
586 if (conf_grab_matches(seat->conf->grab_session_close,
587 ev->mods, ev->num_syms, ev->keysyms)) {
589 if (!seat->conf->session_control)
591 s = seat->current_sess;
594 if (s == seat->dummy_sess)
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);
609 kmscon_session_unregister(s);
612 if (conf_grab_matches(seat->conf->grab_terminal_new,
613 ev->mods, ev->num_syms, ev->keysyms)) {
615 if (!seat->conf->session_control)
617 ret = kmscon_terminal_register(&s, seat);
618 if (ret == -EOPNOTSUPP) {
619 log_notice("terminal support not compiled in");
621 log_error("cannot register terminal session: %d", ret);
624 seat->scheduled_sess = s;
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,
640 struct kmscon_seat *seat;
643 if (!out || !eloop || !vtm || !seatname)
646 seat = malloc(sizeof(*seat));
649 memset(seat, 0, sizeof(*seat));
654 shl_dlist_init(&seat->displays);
655 shl_dlist_init(&seat->sessions);
657 seat->name = strdup(seatname);
659 log_error("cannot copy string");
664 ret = kmscon_conf_new(&seat->conf_ctx);
666 log_error("cannot create seat configuration object: %d", ret);
669 seat->conf = conf_ctx_get_mem(seat->conf_ctx);
671 ret = kmscon_conf_load_seat(seat->conf_ctx, main_conf, seat->name);
673 log_error("cannot parse seat configuration on seat %s: %d",
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);
688 ret = uterm_input_register_cb(seat->input, seat_input_event, seat);
692 ret = uterm_vt_allocate(seat->vtm, &seat->vt,
693 vt_types, seat->name,
694 seat->input, seat->conf->vt, seat_vt_event,
699 ev_eloop_ref(seat->eloop);
700 uterm_vt_master_ref(seat->vtm);
705 uterm_input_unregister_cb(seat->input, seat_input_event, seat);
707 uterm_input_unref(seat->input);
709 kmscon_conf_free(seat->conf_ctx);
717 void kmscon_seat_free(struct kmscon_seat *seat)
719 struct kmscon_display *d;
720 struct kmscon_session *s;
726 ret = seat_pause(seat, true);
728 log_warning("destroying seat %s while session %p is active",
729 seat->name, seat->current_sess);
731 ret = seat_go_asleep(seat, true);
733 log_warning("destroying seat %s while still awake: %d",
736 while (!shl_dlist_empty(&seat->sessions)) {
737 s = shl_dlist_entry(seat->sessions.next,
738 struct kmscon_session,
740 kmscon_session_unregister(s);
743 while (!shl_dlist_empty(&seat->displays)) {
744 d = shl_dlist_entry(seat->displays.next,
745 struct kmscon_display,
747 seat_remove_display(seat, d);
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);
755 uterm_vt_master_unref(seat->vtm);
756 ev_eloop_unref(seat->eloop);
760 void kmscon_seat_startup(struct kmscon_seat *seat)
763 struct kmscon_session *s;
768 ret = kmscon_dummy_register(&s, seat);
769 if (ret == -EOPNOTSUPP) {
770 log_notice("dummy sessions not compiled in");
772 log_error("cannot register dummy session: %d", ret);
774 seat->dummy_sess = s;
775 kmscon_session_enable(s);
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");
783 log_error("cannot register terminal session");
785 kmscon_session_enable(s);
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");
793 log_error("cannot register cdev session");
796 if (seat->conf->switchvt ||
797 uterm_vt_get_type(seat->vt) == UTERM_VT_FAKE)
798 uterm_vt_activate(seat->vt);
801 int kmscon_seat_add_display(struct kmscon_seat *seat,
802 struct uterm_display *disp)
807 return seat_add_display(seat, disp);
810 void kmscon_seat_remove_display(struct kmscon_seat *seat,
811 struct uterm_display *disp)
813 struct shl_dlist *iter;
814 struct kmscon_display *d;
819 shl_dlist_for_each(iter, &seat->displays) {
820 d = shl_dlist_entry(iter, struct kmscon_display, list);
824 seat_remove_display(seat, d);
829 int kmscon_seat_add_input(struct kmscon_seat *seat, const char *node)
834 uterm_input_add_dev(seat->input, node);
838 void kmscon_seat_remove_input(struct kmscon_seat *seat, const char *node)
843 uterm_input_remove_dev(seat->input, node);
846 const char *kmscon_seat_get_name(struct kmscon_seat *seat)
854 struct uterm_input *kmscon_seat_get_input(struct kmscon_seat *seat)
862 struct ev_eloop *kmscon_seat_get_eloop(struct kmscon_seat *seat)
870 struct conf_ctx *kmscon_seat_get_conf(struct kmscon_seat *seat)
875 return seat->conf_ctx;
878 void kmscon_seat_schedule(struct kmscon_seat *seat, unsigned int id)
880 struct shl_dlist *iter;
881 struct kmscon_session *s, *next;
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)
898 seat->scheduled_sess = next;
899 if (seat_has_schedule(seat))
903 int kmscon_seat_register_session(struct kmscon_seat *seat,
904 struct kmscon_session **out,
905 kmscon_session_cb_t cb,
908 struct kmscon_session *sess;
909 struct shl_dlist *iter;
910 struct kmscon_display *d;
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);
922 sess = malloc(sizeof(*sess));
924 log_error("cannot allocate memory for new session on seat %s",
929 log_debug("register session %p", sess);
931 memset(sess, 0, sizeof(*sess));
936 sess->foreground = true;
938 /* register new sessions next to the current one */
939 if (seat->current_sess)
940 shl_dlist_link(&seat->current_sess->list, &sess->list);
942 shl_dlist_link_tail(&seat->sessions, &sess->list);
944 ++seat->session_count;
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);
955 void kmscon_session_ref(struct kmscon_session *sess)
957 if (!sess || !sess->ref)
963 void kmscon_session_unref(struct kmscon_session *sess)
965 if (!sess || !sess->ref || --sess->ref)
968 kmscon_session_unregister(sess);
972 void kmscon_session_unregister(struct kmscon_session *sess)
974 struct kmscon_seat *seat;
978 if (!sess || !sess->seat)
981 log_debug("unregister session %p", sess);
984 sess->enabled = false;
985 if (seat->dummy_sess == sess)
986 seat->dummy_sess = NULL;
987 seat_reschedule(seat);
989 if (seat->current_sess == sess) {
990 ret = seat_pause(seat, true);
993 log_warning("unregistering active session %p; skipping automatic session-switch",
998 shl_dlist_unlink(&sess->list);
999 --seat->session_count;
1002 session_call(sess, KMSCON_SESSION_UNREGISTER, NULL);
1003 kmscon_session_unref(sess);
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. */
1016 bool kmscon_session_is_registered(struct kmscon_session *sess)
1018 return sess && sess->seat;
1021 bool kmscon_session_is_active(struct kmscon_session *sess)
1023 return sess && sess->seat && sess->seat->current_sess == sess;
1026 int kmscon_session_set_foreground(struct kmscon_session *sess)
1028 struct kmscon_seat *seat;
1033 if (sess->foreground)
1037 if (seat && seat->current_sess == sess && !seat->foreground) {
1038 ret = seat_go_foreground(seat, true);
1043 sess->foreground = true;
1047 int kmscon_session_set_background(struct kmscon_session *sess)
1049 struct kmscon_seat *seat;
1054 if (!sess->foreground)
1058 if (seat && seat->current_sess == sess && seat->foreground) {
1059 ret = seat_go_background(seat, true);
1064 sess->foreground = false;
1068 void kmscon_session_schedule(struct kmscon_session *sess)
1070 struct kmscon_seat *seat;
1072 if (!sess || !sess->seat)
1076 seat->scheduled_sess = sess;
1077 seat_reschedule(seat);
1078 if (seat_has_schedule(seat))
1082 void kmscon_session_enable(struct kmscon_session *sess)
1084 if (!sess || sess->enabled)
1087 log_debug("enable session %p", sess);
1088 sess->enabled = true;
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);
1098 void kmscon_session_disable(struct kmscon_session *sess)
1100 if (!sess || !sess->enabled)
1103 log_debug("disable session %p", sess);
1104 sess->enabled = false;
1107 bool kmscon_session_is_enabled(struct kmscon_session *sess)
1109 return sess && sess->enabled;
1112 void kmscon_session_notify_deactivated(struct kmscon_session *sess)
1114 struct kmscon_seat *seat;
1118 if (!sess || !sess->seat)
1122 if (seat->current_sess != sess)
1125 sched = seat->async_schedule;
1126 log_debug("session %p notified core about deactivation (schedule: %u)",
1128 session_deactivate(sess);
1129 seat_reschedule(seat);
1131 if (sched == SCHEDULE_VT) {
1132 ret = seat_go_background(seat, false);
1135 ret = seat_go_asleep(seat, false);
1138 uterm_vt_retry(seat->vt);
1139 } else if (sched == SCHEDULE_UNREGISTER) {
1140 kmscon_session_unregister(sess);