uvtd: add new Virtual Terminal daemon
authorDavid Herrmann <dh.herrmann@gmail.com>
Mon, 4 Mar 2013 14:11:30 +0000 (15:11 +0100)
committerDavid Herrmann <dh.herrmann@gmail.com>
Mon, 4 Mar 2013 14:11:30 +0000 (15:11 +0100)
This introduces uvtd which replaces kmscon sessions as an external helper
program. It's still a dummy program but it will get extended soon. After
that, kmscon sessions will get removed and limited to a single seat. This
will simplify kmscon itself heavily and move rarely used features out of
kmscon into helpers.

Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
.gitignore
Makefile.am
configure.ac
src/uvtd_main.c [new file with mode: 0644]
src/uvtd_seat.c [new file with mode: 0644]
src/uvtd_seat.h [new file with mode: 0644]

index e407031..25d3e5b 100644 (file)
@@ -40,6 +40,7 @@ docs/reference/kmscon.????*
 docs/reference/*.stamp
 docs/reference/version.xml
 docs/reference/*/
+uvtd
 wlterm
 docs/man/*.1
 docs/man/*.3
index 382da61..0d6a9a9 100644 (file)
@@ -1,6 +1,6 @@
 #
 # Kmscon - Global Makefile
-# Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com>
+# Copyright (c) 2012-2013 David Herrmann <dh.herrmann@googlemail.com>
 #
 
 #
@@ -30,7 +30,7 @@ LIBUTERM_AGE = 0
 ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
 AM_MAKEFLAGS = --no-print-directory
 AUTOMAKE_OPTIONS = color-tests
-AM_DISTCHECK_CONFIGURE_FLAGS = --enable-wlterm
+AM_DISTCHECK_CONFIGURE_FLAGS = --enable-wlterm --enable-uvtd
 
 SUBDIRS = .
 
@@ -688,6 +688,27 @@ wlterm_LDADD = \
        -lpthread
 
 #
+# uvtd
+#
+
+if BUILD_ENABLE_UVTD
+bin_PROGRAMS += uvtd
+endif
+
+uvtd_SOURCES = \
+       src/uvtd_main.c \
+       src/uvtd_seat.h \
+       src/uvtd_seat.c
+uvtd_CPPFLAGS = \
+       $(AM_CPPFLAGS) \
+       $(XKBCOMMON_CFLAGS)
+uvtd_LDADD = \
+       $(XKBCOMMON_LIBS) \
+       libeloop.la \
+       libshl.la \
+       libuterm.la
+
+#
 # Tests
 #
 
index 96f7c5f..a9ff70b 100644 (file)
@@ -188,6 +188,16 @@ if test "x$enable_wlterm" = "x" ; then
 fi
 AC_MSG_RESULT([$enable_wlterm])
 
+# uvtd
+AC_MSG_CHECKING([whether user wants uvtd])
+AC_ARG_ENABLE([uvtd],
+              [AS_HELP_STRING([--enable-uvtd],
+                              [build uvtd])])
+if test "x$enable_uvtd" = "x" ; then
+        enable_uvtd="no (default)"
+fi
+AC_MSG_RESULT([$enable_uvtd])
+
 # debug
 AC_MSG_CHECKING([whether to build with debugging on])
 AC_ARG_ENABLE([debug],
@@ -803,6 +813,30 @@ else
         wlterm_missing="enable-wlterm"
 fi
 
+# uvtd
+uvtd_avail=no
+uvtd_missing=""
+if test ! "x$enable_uvtd" = "xno" ; then
+        uvtd_avail=yes
+        if test "x$uvt_avail" = "xno" ; then
+                uvtd_avail=no
+                uvtd_missing="$uvt_missing,$uvtd_missing"
+        fi
+
+        if test "x$eloop_avail" = "xno" ; then
+                uvtd_avail=no
+                uvtd_missing="$eloop_missing,$uvtd_missing"
+        fi
+
+        if test "x$uvtd_avail" = "xno" ; then
+                if test "x$enable_uvtd" = "xyes" ; then
+                        AC_ERROR([missing for uvtd: $uvtd_missing])
+                fi
+        fi
+else
+        uvtd_missing="enable-uvtd"
+fi
+
 #
 # Enable all required modules
 # We now know which modules can be built by checking the *_avail variables set
@@ -811,6 +845,16 @@ fi
 # needs them. This is done top-down of course.
 #
 
+# uvtd
+uvtd_enabled=no
+if test "x$uvtd_avail" = "xyes" ; then
+        if test "x${enable_uvtd% *}" = "xyes" ; then
+                uvtd_enabled=yes
+                enable_eloop=yes
+                enable_uvt=yes
+        fi
+fi
+
 # wlterm
 wlterm_enabled=no
 if test "x$wlterm_avail" = "xyes" ; then
@@ -1194,6 +1238,10 @@ AM_CONDITIONAL([BUILD_ENABLE_KMSCON],
 AM_CONDITIONAL([BUILD_ENABLE_WLTERM],
                [test "x$wlterm_enabled" = "xyes"])
 
+# uvtd
+AM_CONDITIONAL([BUILD_ENABLE_UVTD],
+               [test "x$uvtd_enabled" = "xyes"])
+
 #
 # Miscellaneous Checks
 # All checks below are independent of module checking or depend on the results
@@ -1278,6 +1326,7 @@ AC_MSG_NOTICE([Build configuration:
   Applications and Libraries:
                kmscon: $kmscon_enabled ($kmscon_avail: $kmscon_missing)
                wlterm: $wlterm_enabled ($wlterm_avail: $wlterm_missing)
+                 uvtd: $uvtd_enabled ($uvtd_avail: $uvtd_missing)
                 uterm: $uterm_enabled ($uterm_avail: $uterm_missing)
                   tsm: $tsm_enabled ($tsm_avail: $tsm_missing)
                   uvt: $uvt_enabled ($uvt_avail: $uvt_missing)
diff --git a/src/uvtd_main.c b/src/uvtd_main.c
new file mode 100644 (file)
index 0000000..0dc7a83
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * uvtd - User-space VT daemon
+ *
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@googlemail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/signalfd.h>
+#include "eloop.h"
+#include "shl_dlist.h"
+#include "shl_log.h"
+#include "uterm_input.h"
+#include "uterm_monitor.h"
+#include "uvtd_seat.h"
+
+struct app_seat {
+       struct shl_dlist list;
+       struct uvtd_app *app;
+       struct uterm_monitor_seat *useat;
+       struct uvtd_seat *seat;
+};
+
+struct uvtd_app {
+       struct ev_eloop *eloop;
+       struct uterm_monitor *mon;
+       struct shl_dlist seats;
+};
+
+static void app_seat_event(struct uvtd_seat *seat, unsigned int ev, void *data)
+{
+}
+
+static int app_seat_new(struct uvtd_app *app, const char *sname,
+                       struct uterm_monitor_seat *useat)
+{
+       struct app_seat *seat;
+       int ret;
+
+       seat = malloc(sizeof(*seat));
+       if (!seat)
+               return -ENOMEM;
+
+       log_debug("new seat %p on %s", seat, sname);
+
+       memset(seat, 0, sizeof(*seat));
+       seat->app = app;
+       seat->useat = useat;
+
+       ret = uvtd_seat_new(&seat->seat, sname, app->eloop, app_seat_event,
+                           seat);
+       if (ret)
+               goto err_free;
+
+       uterm_monitor_set_seat_data(seat->useat, seat);
+       shl_dlist_link(&app->seats, &seat->list);
+       return 0;
+
+err_free:
+       free(seat);
+       return ret;
+}
+
+static void app_seat_free(struct app_seat *seat)
+{
+       log_debug("free seat %p", seat);
+
+       shl_dlist_unlink(&seat->list);
+       uterm_monitor_set_seat_data(seat->useat, NULL);
+       uvtd_seat_free(seat->seat);
+       free(seat);
+}
+
+static void app_monitor_event(struct uterm_monitor *mon,
+                             struct uterm_monitor_event *ev,
+                             void *data)
+{
+       struct uvtd_app *app = data;
+       struct app_seat *seat;
+       int ret;
+
+       switch (ev->type) {
+       case UTERM_MONITOR_NEW_SEAT:
+               ret = app_seat_new(app, ev->seat_name, ev->seat);
+               if (ret)
+                       return;
+               break;
+       case UTERM_MONITOR_FREE_SEAT:
+               if (ev->seat_data)
+                       app_seat_free(ev->seat_data);
+               break;
+       case UTERM_MONITOR_NEW_DEV:
+               seat = ev->seat_data;
+               if (!seat)
+                       return;
+
+               switch (ev->dev_type) {
+               case UTERM_MONITOR_INPUT:
+                       log_debug("new input device %s on seat %p",
+                                 ev->dev_node, seat);
+                       break;
+               }
+               break;
+       case UTERM_MONITOR_FREE_DEV:
+               seat = ev->seat_data;
+               if (!seat)
+                       return;
+
+               switch (ev->dev_type) {
+               case UTERM_MONITOR_INPUT:
+                       log_debug("free input device %s on seat %p",
+                                 ev->dev_node, seat);
+                       break;
+               }
+               break;
+       }
+}
+
+static void app_sig_generic(struct ev_eloop *eloop,
+                           struct signalfd_siginfo *info,
+                           void *data)
+{
+       struct uvtd_app *app = data;
+
+       log_info("terminating due to caught signal %d", info->ssi_signo);
+       ev_eloop_exit(app->eloop);
+}
+
+static void app_sig_ignore(struct ev_eloop *eloop,
+                          struct signalfd_siginfo *info,
+                          void *data)
+{
+}
+
+static void destroy_app(struct uvtd_app *app)
+{
+       uterm_monitor_unref(app->mon);
+       ev_eloop_unregister_signal_cb(app->eloop, SIGPIPE, app_sig_ignore,
+                                     app);
+       ev_eloop_unregister_signal_cb(app->eloop, SIGINT, app_sig_generic,
+                                     app);
+       ev_eloop_unregister_signal_cb(app->eloop, SIGTERM, app_sig_generic,
+                                     app);
+       ev_eloop_unref(app->eloop);
+}
+
+static int setup_app(struct uvtd_app *app)
+{
+       int ret;
+
+       shl_dlist_init(&app->seats);
+
+       ret = ev_eloop_new(&app->eloop, log_llog, NULL);
+       if (ret) {
+               log_error("cannot create eloop object: %d", ret);
+               goto err_app;
+       }
+
+       ret = ev_eloop_register_signal_cb(app->eloop, SIGTERM,
+                                         app_sig_generic, app);
+       if (ret) {
+               log_error("cannot register SIGTERM signal handler: %d", ret);
+               goto err_app;
+       }
+
+       ret = ev_eloop_register_signal_cb(app->eloop, SIGINT,
+                                         app_sig_generic, app);
+       if (ret) {
+               log_error("cannot register SIGINT signal handler: %d", ret);
+               goto err_app;
+       }
+
+       ret = ev_eloop_register_signal_cb(app->eloop, SIGPIPE,
+                                         app_sig_ignore, app);
+       if (ret) {
+               log_error("cannot register SIGPIPE signal handler: %d", ret);
+               goto err_app;
+       }
+
+       ret = uterm_monitor_new(&app->mon, app->eloop, app_monitor_event, app);
+       if (ret) {
+               log_error("cannot create device monitor: %d", ret);
+               goto err_app;
+       }
+
+       log_debug("scanning for devices...");
+       uterm_monitor_scan(app->mon);
+
+       return 0;
+
+err_app:
+       destroy_app(app);
+       return ret;
+}
+
+int main(int argc, char **argv)
+{
+       int ret;
+       struct uvtd_app app;
+
+       log_set_config(&LOG_CONFIG_INFO(1, 1));
+       log_print_init("uvtd");
+
+       memset(&app, 0, sizeof(app));
+
+       ret = setup_app(&app);
+       if (ret)
+               goto err_out;
+
+       ev_eloop_run(app.eloop, -1);
+
+       ret = 0;
+       destroy_app(&app);
+err_out:
+       if (ret)
+               log_err("cannot initialize uvtd, errno %d: %s",
+                       ret, strerror(-ret));
+       log_info("exiting");
+       return -ret;
+}
diff --git a/src/uvtd_seat.c b/src/uvtd_seat.c
new file mode 100644 (file)
index 0000000..2ef3180
--- /dev/null
@@ -0,0 +1,594 @@
+/*
+ * uvtd - User-space VT daemon
+ *
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * Seats
+ * Each set of input+output devices form a single seat. Each seat is independent
+ * of each other and there can be exactly one user per seat interacting with the
+ * system.
+ * Per seat, we have multiple sessions. But only one session can be active at a
+ * time per seat. We allow external sessions, so session activation/deactivation
+ * may be asynchronous.
+ *
+ * A seat object manages all the sessions for a single seat. As long as a seat
+ * is asleep, no session is active. If you wake it up, the seat manager
+ * automatically schedules a session. You can then request other sessions to be
+ * scheduled and the seat manager will try to deactivate the current session and
+ * reactivate the new session.
+ *
+ * Note that session deactivation may be asynchronous (unless forced). So some
+ * calls might return -EINPROGRESS if the session-deactivation is pending. This
+ * shouldn't bother the user as the session will notify back soon that the
+ * deactivation was successfull. However, if it doesn't the user can chose to
+ * perform any other action and we will retry the operation. As a last resort,
+ * you can always kill the session by unregistering it or forcing a
+ * deactivation.
+ * "async_schedule" tracks the task that requested the deactivation of a
+ * session. So when the session notifies us that it got deactivated, we know
+ * what the user wanted and can perform the requested task now.
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include "eloop.h"
+#include "shl_dlist.h"
+#include "shl_log.h"
+#include "uvtd_seat.h"
+
+#define LOG_SUBSYSTEM "seat"
+
+struct uvtd_session {
+       struct shl_dlist list;
+       unsigned long ref;
+       struct uvtd_seat *seat;
+
+       bool enabled;
+       bool deactivating;
+
+       uvtd_session_cb_t cb;
+       void *data;
+};
+
+/* task that requested the pending session-deactivation */
+enum uvtd_async_schedule {
+       SCHEDULE_NONE,                  /* default, causes a reschedule */
+       SCHEDULE_SWITCH,                /* causes a reschedule */
+       SCHEDULE_SLEEP,                 /* puts the seat asleep */
+       SCHEDULE_UNREGISTER,            /* unregisters the session */
+};
+
+struct uvtd_seat {
+       struct ev_eloop *eloop;
+       char *name;
+
+       size_t session_count;
+       struct shl_dlist sessions;
+
+       bool awake;
+       struct uvtd_session *current_sess;
+       struct uvtd_session *scheduled_sess;
+       struct uvtd_session *dummy_sess;
+
+       unsigned int async_schedule;
+
+       uvtd_seat_cb_t cb;
+       void *data;
+};
+
+static int session_call(struct uvtd_session *sess, unsigned int event)
+{
+       if (!sess->cb)
+               return 0;
+
+       return sess->cb(sess, event, sess->data);
+}
+
+static int session_call_activate(struct uvtd_session *sess)
+{
+       log_debug("activate session %p", sess);
+       return session_call(sess, UVTD_SESSION_ACTIVATE);
+}
+
+static int session_call_deactivate(struct uvtd_session *sess)
+{
+       log_debug("deactivate session %p", sess);
+       return session_call(sess, UVTD_SESSION_DEACTIVATE);
+}
+
+/* drop the current session as if it was successfully deactivated */
+static void seat_yield(struct uvtd_seat *seat)
+{
+       if (!seat->current_sess)
+               return;
+
+       seat->current_sess->deactivating = false;
+       seat->current_sess = NULL;
+       seat->async_schedule = SCHEDULE_NONE;
+}
+
+static int seat_go_asleep(struct uvtd_seat *seat, bool force)
+{
+       int ret = 0;
+
+       if (!seat->awake)
+               return 0;
+
+       if (seat->current_sess) {
+               ret = -EBUSY;
+               if (!force)
+                       return ret;
+
+               seat_yield(seat);
+       }
+
+       seat->awake = false;
+
+       if (seat->cb)
+               seat->cb(seat, UVTD_SEAT_SLEEP, seat->data);
+
+       return ret;
+}
+
+static void seat_go_awake(struct uvtd_seat *seat)
+{
+       if (seat->awake)
+               return;
+
+       seat->awake = true;
+}
+
+static int seat_run(struct uvtd_seat *seat)
+{
+       int ret;
+       struct uvtd_session *session;
+
+       if (!seat->awake)
+               return -EBUSY;
+       if (seat->current_sess)
+               return 0;
+
+       if (!seat->scheduled_sess) {
+               log_debug("no session scheduled to run (num: %zu)",
+                         seat->session_count);
+               return -ENOENT;
+       }
+       session = seat->scheduled_sess;
+
+       /* TODO: unregister session and try next on failure */
+       ret = session_call_activate(session);
+       if (ret) {
+               log_warning("cannot activate session %p: %d", session, ret);
+               return ret;
+       }
+
+       seat->current_sess = session;
+
+       return 0;
+}
+
+static int seat_pause(struct uvtd_seat *seat, bool force, unsigned int async)
+{
+       int ret;
+
+       if (!seat->current_sess)
+               return 0;
+
+       /* TODO: pass \force to the session */
+       seat->current_sess->deactivating = true;
+       ret = session_call_deactivate(seat->current_sess);
+       if (ret) {
+               if (!force && ret == -EINPROGRESS) {
+                       seat->async_schedule = async;
+                       log_debug("pending deactivation for session %p",
+                                 seat->current_sess);
+               } else {
+                       log_warning("cannot deactivate session %p (%d): %d",
+                                   seat->current_sess, force, ret);
+               }
+
+               if (!force)
+                       return ret;
+       }
+
+       seat_yield(seat);
+       return ret;
+}
+
+static void seat_reschedule(struct uvtd_seat *seat)
+{
+       struct shl_dlist *iter, *start;
+       struct uvtd_session *sess;
+
+       if (seat->scheduled_sess && seat->scheduled_sess->enabled)
+               return;
+
+       if (seat->current_sess && seat->current_sess->enabled) {
+               seat->scheduled_sess = seat->current_sess;
+               return;
+       }
+
+       if (seat->current_sess)
+               start = &seat->current_sess->list;
+       else
+               start = &seat->sessions;
+
+       shl_dlist_for_each_but_one(iter, start, &seat->sessions) {
+               sess = shl_dlist_entry(iter, struct uvtd_session, list);
+
+               if (sess != seat->dummy_sess && sess->enabled) {
+                       seat->scheduled_sess = sess;
+                       return;
+               }
+       }
+
+       if (seat->dummy_sess && seat->dummy_sess->enabled)
+               seat->scheduled_sess = seat->dummy_sess;
+       else
+               seat->scheduled_sess = NULL;
+}
+
+static bool seat_has_schedule(struct uvtd_seat *seat)
+{
+       return seat->scheduled_sess &&
+              seat->scheduled_sess != seat->current_sess;
+}
+
+static int seat_switch(struct uvtd_seat *seat)
+{
+       int ret;
+
+       ret = seat_pause(seat, false, SCHEDULE_SWITCH);
+       if (ret)
+               return ret;
+
+       return seat_run(seat);
+}
+
+static void seat_schedule(struct uvtd_seat *seat, struct uvtd_session *sess)
+{
+       seat->scheduled_sess = sess;
+       seat_reschedule(seat);
+       if (seat_has_schedule(seat))
+               seat_switch(seat);
+}
+
+static void seat_next(struct uvtd_seat *seat, bool reverse)
+{
+       struct shl_dlist *cur, *iter;
+       struct uvtd_session *s, *next;
+
+       if (seat->current_sess)
+               cur = &seat->current_sess->list;
+       else if (seat->session_count)
+               cur = &seat->sessions;
+       else
+               return;
+
+       next = NULL;
+       if (!seat->current_sess && seat->dummy_sess &&
+           seat->dummy_sess->enabled)
+               next = seat->dummy_sess;
+
+       if (reverse) {
+               shl_dlist_for_each_reverse_but_one(iter, cur,
+                                                  &seat->sessions) {
+                       s = shl_dlist_entry(iter, struct uvtd_session, list);
+
+                       if (s->enabled && seat->dummy_sess != s) {
+                               next = s;
+                               break;
+                       }
+               }
+       } else {
+               shl_dlist_for_each_but_one(iter, cur, &seat->sessions) {
+                       s = shl_dlist_entry(iter, struct uvtd_session, list);
+
+                       if (s->enabled && seat->dummy_sess != s) {
+                               next = s;
+                               break;
+                       }
+               }
+       }
+
+       if (!next)
+               return;
+
+       seat_schedule(seat, next);
+}
+
+int uvtd_seat_new(struct uvtd_seat **out, const char *seatname,
+                 struct ev_eloop *eloop, uvtd_seat_cb_t cb, void *data)
+{
+       struct uvtd_seat *seat;
+       int ret;
+
+       if (!out || !eloop || !seatname)
+               return -EINVAL;
+
+       seat = malloc(sizeof(*seat));
+       if (!seat)
+               return -ENOMEM;
+       memset(seat, 0, sizeof(*seat));
+       seat->eloop = eloop;
+       seat->cb = cb;
+       seat->data = data;
+       shl_dlist_init(&seat->sessions);
+
+       seat->name = strdup(seatname);
+       if (!seat->name) {
+               ret = -ENOMEM;
+               goto err_free;
+       }
+
+       ev_eloop_ref(seat->eloop);
+       *out = seat;
+       return 0;
+
+err_free:
+       free(seat);
+       return ret;
+}
+
+void uvtd_seat_free(struct uvtd_seat *seat)
+{
+       struct uvtd_session *s;
+       int ret;
+
+       if (!seat)
+               return;
+
+       ret = seat_pause(seat, true, SCHEDULE_NONE);
+       if (ret)
+               log_warning("destroying seat %s while session %p is active",
+                           seat->name, seat->current_sess);
+
+       ret = seat_go_asleep(seat, true);
+       if (ret)
+               log_warning("destroying seat %s while still awake: %d",
+                           seat->name, ret);
+
+       while (!shl_dlist_empty(&seat->sessions)) {
+               s = shl_dlist_entry(seat->sessions.next, struct uvtd_session,
+                                   list);
+               uvtd_session_unregister(s);
+       }
+
+       free(seat->name);
+       ev_eloop_unref(seat->eloop);
+       free(seat);
+}
+
+const char *uvtd_seat_get_name(struct uvtd_seat *seat)
+{
+       return seat ? seat->name : NULL;
+}
+
+struct ev_eloop *uvtd_seat_get_eloop(struct uvtd_seat *seat)
+{
+       return seat ? seat->eloop : NULL;
+}
+
+int uvtd_seat_sleep(struct uvtd_seat *seat, bool force)
+{
+       int ret, err = 0;
+
+       if (!seat)
+               return -EINVAL;
+
+       ret = seat_pause(seat, force, SCHEDULE_SLEEP);
+       if (ret) {
+               if (force)
+                       err = ret;
+               else
+                       return ret;
+       }
+
+       ret = seat_go_asleep(seat, force);
+       if (ret) {
+               if (force)
+                       err = ret;
+               else
+                       return ret;
+       }
+
+       return err;
+}
+
+void uvtd_seat_wake_up(struct uvtd_seat *seat)
+{
+       if (!seat)
+               return;
+
+       seat_go_awake(seat);
+       seat_run(seat);
+}
+
+int uvtd_seat_register_session(struct uvtd_seat *seat,
+                              struct uvtd_session **out,
+                              unsigned int id, uvtd_session_cb_t cb,
+                              void *data)
+{
+       struct uvtd_session *sess;
+
+       if (!seat || !out)
+               return -EINVAL;
+
+       sess = malloc(sizeof(*sess));
+       if (!sess)
+               return -ENOMEM;
+
+       log_debug("register session %p", sess);
+
+       memset(sess, 0, sizeof(*sess));
+       sess->ref = 1;
+       sess->seat = seat;
+       sess->cb = cb;
+       sess->data = data;
+
+       /* TODO: add support for \ids */
+       /* register new sessions next to the current one */
+       if (seat->current_sess)
+               shl_dlist_link(&seat->current_sess->list, &sess->list);
+       else
+               shl_dlist_link_tail(&seat->sessions, &sess->list);
+
+       ++seat->session_count;
+       *out = sess;
+       return 0;
+}
+
+void uvtd_session_ref(struct uvtd_session *sess)
+{
+       if (!sess || !sess->ref)
+               return;
+
+       ++sess->ref;
+}
+
+void uvtd_session_unref(struct uvtd_session *sess)
+{
+       if (!sess || !sess->ref || --sess->ref)
+               return;
+
+       uvtd_session_unregister(sess);
+       free(sess);
+}
+
+void uvtd_session_unregister(struct uvtd_session *sess)
+{
+       struct uvtd_seat *seat;
+       int ret;
+       bool forced = false;
+
+       if (!sess || !sess->seat)
+               return;
+
+       log_debug("unregister session %p", sess);
+
+       seat = sess->seat;
+       sess->enabled = false;
+       if (seat->dummy_sess == sess)
+               seat->dummy_sess = NULL;
+       seat_reschedule(seat);
+
+       if (seat->current_sess == sess) {
+               ret = seat_pause(seat, true, SCHEDULE_NONE);
+               if (ret) {
+                       forced = true;
+                       log_warning("unregistering active session %p; skipping automatic session-switch",
+                                   sess);
+               }
+       }
+
+       shl_dlist_unlink(&sess->list);
+       --seat->session_count;
+       sess->seat = NULL;
+
+       session_call(sess, UVTD_SESSION_UNREGISTER);
+       uvtd_session_unref(sess);
+
+       /* If this session was active and we couldn't deactivate it, then it
+        * might still have resources allocated that couldn't get freed. In this
+        * case we should not automatically switch to the next session as it is
+        * very likely that it will not be able to start.
+        * Instead, we stay inactive and wait for user/external input to switch
+        * to another session. This delay will then hopefully be long enough so
+        * all resources got freed. */
+       if (!forced)
+               seat_run(seat);
+}
+
+bool uvtd_session_is_registered(struct uvtd_session *sess)
+{
+       return sess && sess->seat;
+}
+
+bool uvtd_session_is_active(struct uvtd_session *sess)
+{
+       return sess && sess->seat && sess->seat->current_sess == sess;
+}
+
+void uvtd_session_schedule(struct uvtd_session *sess)
+{
+       if (!sess || !sess->seat)
+               return;
+
+       seat_schedule(sess->seat, sess);
+}
+
+void uvtd_session_enable(struct uvtd_session *sess)
+{
+       if (!sess || sess->enabled)
+               return;
+
+       log_debug("enable session %p", sess);
+       sess->enabled = true;
+
+       if (sess->seat &&
+           (!sess->seat->current_sess ||
+            sess->seat->current_sess == sess->seat->dummy_sess))
+               seat_schedule(sess->seat, sess);
+}
+
+void uvtd_session_disable(struct uvtd_session *sess)
+{
+       if (!sess || !sess->enabled)
+               return;
+
+       log_debug("disable session %p", sess);
+       sess->enabled = false;
+}
+
+bool uvtd_session_is_enabled(struct uvtd_session *sess)
+{
+       return sess && sess->enabled;
+}
+
+void uvtd_session_notify_deactivated(struct uvtd_session *sess)
+{
+       struct uvtd_seat *seat;
+       unsigned int sched;
+
+       if (!sess || !sess->seat)
+               return;
+
+       seat = sess->seat;
+       if (seat->current_sess != sess)
+               return;
+
+       sched = seat->async_schedule;
+       log_debug("session %p notified core about deactivation (schedule: %u)",
+                 sess, sched);
+       seat_yield(seat);
+       seat_reschedule(seat);
+
+       if (sched == SCHEDULE_SLEEP)
+               seat_go_asleep(seat, false);
+       else if (sched == SCHEDULE_UNREGISTER)
+               uvtd_session_unregister(sess);
+       else
+               seat_run(seat);
+}
diff --git a/src/uvtd_seat.h b/src/uvtd_seat.h
new file mode 100644 (file)
index 0000000..534d773
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * uvtd - User-space VT daemon
+ *
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * Seats
+ * Each set of input+output devices form a single seat. Each seat is independent
+ * of each other and there can be exactly one user per seat interacting with the
+ * system.
+ * Per seat, we have multiple sessions. But only one session can be active at a
+ * time per seat. We allow external sessions, so session activation/deactivation
+ * may be asynchronous.
+ */
+
+#ifndef UVTD_SEAT_H
+#define UVTD_SEAT_H
+
+#include <stdlib.h>
+#include "eloop.h"
+
+/* sessions */
+
+struct uvtd_session;
+
+enum uvtd_session_event_type {
+       UVTD_SESSION_ACTIVATE,
+       UVTD_SESSION_DEACTIVATE,
+       UVTD_SESSION_UNREGISTER,
+};
+
+typedef int (*uvtd_session_cb_t) (struct uvtd_session *session,
+                                 unsigned int event,
+                                 void *data);
+
+void uvtd_session_ref(struct uvtd_session *sess);
+void uvtd_session_unref(struct uvtd_session *sess);
+void uvtd_session_unregister(struct uvtd_session *sess);
+bool uvtd_session_is_registered(struct uvtd_session *sess);
+
+bool uvtd_session_is_active(struct uvtd_session *sess);
+void uvtd_session_schedule(struct uvtd_session *sess);
+
+void uvtd_session_enable(struct uvtd_session *sess);
+void uvtd_session_disable(struct uvtd_session *sess);
+bool uvtd_session_is_enabled(struct uvtd_session *sess);
+
+void uvtd_session_notify_deactivated(struct uvtd_session *sess);
+
+/* seats */
+
+struct uvtd_seat;
+
+enum uvtd_seat_event {
+       UVTD_SEAT_SLEEP,
+};
+
+typedef void (*uvtd_seat_cb_t) (struct uvtd_seat *seat, unsigned int event,
+                               void *data);
+
+int uvtd_seat_new(struct uvtd_seat **out, const char *seatname,
+                 struct ev_eloop *eloop, uvtd_seat_cb_t cb, void *data);
+void uvtd_seat_free(struct uvtd_seat *seat);
+
+const char *uvtd_seat_get_name(struct uvtd_seat *seat);
+struct ev_eloop *uvtd_seat_get_eloop(struct uvtd_seat *seat);
+int uvtd_seat_sleep(struct uvtd_seat *seat, bool force);
+void uvtd_seat_wake_up(struct uvtd_seat *seat);
+void uvtd_seat_schedule(struct uvtd_seat *seat, unsigned int id);
+
+int uvtd_seat_register_session(struct uvtd_seat *seat,
+                              struct uvtd_session **out,
+                              unsigned int id, uvtd_session_cb_t cb,
+                              void *data);
+
+#endif /* UVTD_SEAT_H */