2 * uvtd - User-space VT daemon
4 * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.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 * Each set of input+output devices form a single seat. Each seat is independent
29 * of each other and there can be exactly one user per seat interacting with the
31 * Per seat, we have multiple sessions. But only one session can be active at a
32 * time per seat. We allow external sessions, so session activation/deactivation
33 * may be asynchronous.
35 * A seat object manages all the sessions for a single seat. As long as a seat
36 * is asleep, no session is active. If you wake it up, the seat manager
37 * automatically schedules a session. You can then request other sessions to be
38 * scheduled and the seat manager will try to deactivate the current session and
39 * reactivate the new session.
41 * Note that session deactivation may be asynchronous (unless forced). So some
42 * calls might return -EINPROGRESS if the session-deactivation is pending. This
43 * shouldn't bother the user as the session will notify back soon that the
44 * deactivation was successfull. However, if it doesn't the user can chose to
45 * perform any other action and we will retry the operation. As a last resort,
46 * you can always kill the session by unregistering it or forcing a
48 * "async_schedule" tracks the task that requested the deactivation of a
49 * session. So when the session notifies us that it got deactivated, we know
50 * what the user wanted and can perform the requested task now.
57 #include "shl_dlist.h"
59 #include "uvtd_seat.h"
61 #define LOG_SUBSYSTEM "seat"
64 struct shl_dlist list;
66 struct uvtd_seat *seat;
76 /* task that requested the pending session-deactivation */
77 enum uvtd_async_schedule {
78 SCHEDULE_NONE, /* default, causes a reschedule */
79 SCHEDULE_SWITCH, /* causes a reschedule */
80 SCHEDULE_SLEEP, /* puts the seat asleep */
81 SCHEDULE_UNREGISTER, /* unregisters the session */
85 struct ev_eloop *eloop;
89 struct shl_dlist sessions;
92 struct uvtd_session *current_sess;
93 struct uvtd_session *scheduled_sess;
94 struct uvtd_session *dummy_sess;
96 unsigned int async_schedule;
102 static int session_call(struct uvtd_session *sess, unsigned int event)
107 return sess->cb(sess, event, sess->data);
110 static int session_call_activate(struct uvtd_session *sess)
112 log_debug("activate session %p", sess);
113 return session_call(sess, UVTD_SESSION_ACTIVATE);
116 static int session_call_deactivate(struct uvtd_session *sess)
118 log_debug("deactivate session %p", sess);
119 return session_call(sess, UVTD_SESSION_DEACTIVATE);
122 /* drop the current session as if it was successfully deactivated */
123 static void seat_yield(struct uvtd_seat *seat)
125 if (!seat->current_sess)
128 seat->current_sess->deactivating = false;
129 seat->current_sess = NULL;
130 seat->async_schedule = SCHEDULE_NONE;
133 static int seat_go_asleep(struct uvtd_seat *seat, bool force)
140 if (seat->current_sess) {
151 seat->cb(seat, UVTD_SEAT_SLEEP, seat->data);
156 static void seat_go_awake(struct uvtd_seat *seat)
164 static int seat_run(struct uvtd_seat *seat)
167 struct uvtd_session *session;
171 if (seat->current_sess)
174 if (!seat->scheduled_sess) {
175 log_debug("no session scheduled to run (num: %zu)",
176 seat->session_count);
179 session = seat->scheduled_sess;
181 /* TODO: unregister session and try next on failure */
182 ret = session_call_activate(session);
184 log_warning("cannot activate session %p: %d", session, ret);
188 seat->current_sess = session;
193 static int seat_pause(struct uvtd_seat *seat, bool force, unsigned int async)
197 if (!seat->current_sess)
200 /* TODO: pass \force to the session */
201 seat->current_sess->deactivating = true;
202 ret = session_call_deactivate(seat->current_sess);
204 if (!force && ret == -EINPROGRESS) {
205 seat->async_schedule = async;
206 log_debug("pending deactivation for session %p",
209 log_warning("cannot deactivate session %p (%d): %d",
210 seat->current_sess, force, ret);
221 static void seat_reschedule(struct uvtd_seat *seat)
223 struct shl_dlist *iter, *start;
224 struct uvtd_session *sess;
226 if (seat->scheduled_sess && seat->scheduled_sess->enabled)
229 if (seat->current_sess && seat->current_sess->enabled) {
230 seat->scheduled_sess = seat->current_sess;
234 if (seat->current_sess)
235 start = &seat->current_sess->list;
237 start = &seat->sessions;
239 shl_dlist_for_each_but_one(iter, start, &seat->sessions) {
240 sess = shl_dlist_entry(iter, struct uvtd_session, list);
242 if (sess != seat->dummy_sess && sess->enabled) {
243 seat->scheduled_sess = sess;
248 if (seat->dummy_sess && seat->dummy_sess->enabled)
249 seat->scheduled_sess = seat->dummy_sess;
251 seat->scheduled_sess = NULL;
254 static bool seat_has_schedule(struct uvtd_seat *seat)
256 return seat->scheduled_sess &&
257 seat->scheduled_sess != seat->current_sess;
260 static int seat_switch(struct uvtd_seat *seat)
264 ret = seat_pause(seat, false, SCHEDULE_SWITCH);
268 return seat_run(seat);
271 static void seat_schedule(struct uvtd_seat *seat, struct uvtd_session *sess)
273 seat->scheduled_sess = sess;
274 seat_reschedule(seat);
275 if (seat_has_schedule(seat))
279 static void seat_next(struct uvtd_seat *seat, bool reverse)
281 struct shl_dlist *cur, *iter;
282 struct uvtd_session *s, *next;
284 if (seat->current_sess)
285 cur = &seat->current_sess->list;
286 else if (seat->session_count)
287 cur = &seat->sessions;
292 if (!seat->current_sess && seat->dummy_sess &&
293 seat->dummy_sess->enabled)
294 next = seat->dummy_sess;
297 shl_dlist_for_each_reverse_but_one(iter, cur,
299 s = shl_dlist_entry(iter, struct uvtd_session, list);
301 if (s->enabled && seat->dummy_sess != s) {
307 shl_dlist_for_each_but_one(iter, cur, &seat->sessions) {
308 s = shl_dlist_entry(iter, struct uvtd_session, list);
310 if (s->enabled && seat->dummy_sess != s) {
320 seat_schedule(seat, next);
323 int uvtd_seat_new(struct uvtd_seat **out, const char *seatname,
324 struct ev_eloop *eloop, uvtd_seat_cb_t cb, void *data)
326 struct uvtd_seat *seat;
329 if (!out || !eloop || !seatname)
332 seat = malloc(sizeof(*seat));
335 memset(seat, 0, sizeof(*seat));
339 shl_dlist_init(&seat->sessions);
341 seat->name = strdup(seatname);
347 ev_eloop_ref(seat->eloop);
356 void uvtd_seat_free(struct uvtd_seat *seat)
358 struct uvtd_session *s;
364 ret = seat_pause(seat, true, SCHEDULE_NONE);
366 log_warning("destroying seat %s while session %p is active",
367 seat->name, seat->current_sess);
369 ret = seat_go_asleep(seat, true);
371 log_warning("destroying seat %s while still awake: %d",
374 while (!shl_dlist_empty(&seat->sessions)) {
375 s = shl_dlist_entry(seat->sessions.next, struct uvtd_session,
377 uvtd_session_unregister(s);
381 ev_eloop_unref(seat->eloop);
385 const char *uvtd_seat_get_name(struct uvtd_seat *seat)
387 return seat ? seat->name : NULL;
390 struct ev_eloop *uvtd_seat_get_eloop(struct uvtd_seat *seat)
392 return seat ? seat->eloop : NULL;
395 int uvtd_seat_sleep(struct uvtd_seat *seat, bool force)
402 ret = seat_pause(seat, force, SCHEDULE_SLEEP);
410 ret = seat_go_asleep(seat, force);
421 void uvtd_seat_wake_up(struct uvtd_seat *seat)
430 void uvtd_seat_schedule(struct uvtd_seat *seat, unsigned int id)
432 struct shl_dlist *iter;
433 struct uvtd_session *session;
441 shl_dlist_for_each(iter, &seat->sessions) {
442 session = shl_dlist_entry(iter, struct uvtd_session, list);
445 if (session->id >= id)
450 seat_schedule(seat, session);
453 int uvtd_seat_register_session(struct uvtd_seat *seat,
454 struct uvtd_session **out,
455 unsigned int id, uvtd_session_cb_t cb,
458 struct uvtd_session *sess, *s;
459 struct shl_dlist *iter;
464 sess = malloc(sizeof(*sess));
468 log_debug("register session %p with id %u on seat %p",
471 memset(sess, 0, sizeof(*sess));
478 ++seat->session_count;
482 shl_dlist_for_each(iter, &seat->sessions) {
483 s = shl_dlist_entry(iter, struct uvtd_session, list);
484 if (!s->id || s->id > sess->id) {
485 shl_dlist_link_tail(iter, &sess->list);
489 if (s->id == sess->id)
490 log_warning("session %p shadowed by %p",
495 shl_dlist_link_tail(&seat->sessions, &sess->list);
499 void uvtd_session_ref(struct uvtd_session *sess)
501 if (!sess || !sess->ref)
507 void uvtd_session_unref(struct uvtd_session *sess)
509 if (!sess || !sess->ref || --sess->ref)
512 uvtd_session_unregister(sess);
516 void uvtd_session_unregister(struct uvtd_session *sess)
518 struct uvtd_seat *seat;
522 if (!sess || !sess->seat)
525 log_debug("unregister session %p", sess);
528 sess->enabled = false;
529 if (seat->dummy_sess == sess)
530 seat->dummy_sess = NULL;
531 seat_reschedule(seat);
533 if (seat->current_sess == sess) {
534 ret = seat_pause(seat, true, SCHEDULE_NONE);
537 log_warning("unregistering active session %p; skipping automatic session-switch",
542 shl_dlist_unlink(&sess->list);
543 --seat->session_count;
546 session_call(sess, UVTD_SESSION_UNREGISTER);
547 uvtd_session_unref(sess);
549 /* If this session was active and we couldn't deactivate it, then it
550 * might still have resources allocated that couldn't get freed. In this
551 * case we should not automatically switch to the next session as it is
552 * very likely that it will not be able to start.
553 * Instead, we stay inactive and wait for user/external input to switch
554 * to another session. This delay will then hopefully be long enough so
555 * all resources got freed. */
560 bool uvtd_session_is_registered(struct uvtd_session *sess)
562 return sess && sess->seat;
565 bool uvtd_session_is_active(struct uvtd_session *sess)
567 return sess && sess->seat && sess->seat->current_sess == sess;
570 void uvtd_session_schedule(struct uvtd_session *sess)
572 if (!sess || !sess->seat)
575 seat_schedule(sess->seat, sess);
578 void uvtd_session_enable(struct uvtd_session *sess)
580 if (!sess || sess->enabled)
583 log_debug("enable session %p", sess);
584 sess->enabled = true;
587 (!sess->seat->current_sess ||
588 sess->seat->current_sess == sess->seat->dummy_sess))
589 seat_schedule(sess->seat, sess);
592 void uvtd_session_disable(struct uvtd_session *sess)
594 if (!sess || !sess->enabled)
597 log_debug("disable session %p", sess);
598 sess->enabled = false;
601 bool uvtd_session_is_enabled(struct uvtd_session *sess)
603 return sess && sess->enabled;
606 void uvtd_session_notify_deactivated(struct uvtd_session *sess)
608 struct uvtd_seat *seat;
611 if (!sess || !sess->seat)
615 if (seat->current_sess != sess)
618 sched = seat->async_schedule;
619 log_debug("session %p notified core about deactivation (schedule: %u)",
622 seat_reschedule(seat);
624 if (sched == SCHEDULE_SLEEP)
625 seat_go_asleep(seat, false);
626 else if (sched == SCHEDULE_UNREGISTER)
627 uvtd_session_unregister(sess);