diff --git a/Makefile.am b/Makefile.am
index 7f44cbc..3860ec0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -418,8 +418,8 @@ kmscon_SOURCES = \
 	$(SHL_MISC) \
 	src/kmscon_terminal.h \
 	src/kmscon_terminal.c \
-	src/kmscon_ui.h \
-	src/kmscon_ui.c \
+	src/kmscon_seat.h \
+	src/kmscon_seat.c \
 	src/kmscon_conf.h \
 	src/kmscon_conf.c \
 	src/kmscon_main.c
diff --git a/src/kmscon_main.c b/src/kmscon_main.c
index 6ec9cfb..86095a6 100644
--- a/src/kmscon_main.c
+++ b/src/kmscon_main.c
@@ -34,13 +34,31 @@
 #include "conf.h"
 #include "eloop.h"
 #include "kmscon_conf.h"
-#include "kmscon_ui.h"
+#include "kmscon_seat.h"
 #include "log.h"
 #include "shl_dlist.h"
 #include "shl_misc.h"
 #include "text.h"
 #include "uterm.h"
 
+struct app_video {
+	struct shl_dlist list;
+	struct app_seat *seat;
+
+	char *node;
+	struct uterm_video *video;
+};
+
+struct app_seat {
+	struct shl_dlist list;
+	struct kmscon_app *app;
+
+	bool awake;
+	char *name;
+	struct kmscon_seat *seat;
+	struct shl_dlist videos;
+};
+
 struct kmscon_app {
 	struct ev_eloop *eloop;
 	struct ev_eloop *vt_eloop;
@@ -51,77 +69,45 @@ struct kmscon_app {
 	struct shl_dlist seats;
 };
 
-struct kmscon_seat {
-	struct shl_dlist list;
-	struct kmscon_app *app;
-
-	struct uterm_monitor_seat *useat;
-	char *sname;
-
-	bool awake;
-	struct uterm_vt *vt;
-	struct uterm_input *input;
-	struct kmscon_ui *ui;
-	struct shl_dlist videos;
-};
-
-struct kmscon_video {
-	struct shl_dlist list;
-	struct uterm_monitor_dev *vdev;
-	struct uterm_video *video;
-};
-
-static void sig_generic(struct ev_eloop *eloop, struct signalfd_siginfo *info,
-			void *data)
+static void app_seat_event(struct kmscon_seat *s, unsigned int event,
+			   void *data)
 {
-	struct kmscon_app *app = data;
-
-	ev_eloop_exit(app->eloop);
-	log_info("terminating due to caught signal %d", info->ssi_signo);
-}
-
-static int vt_event(struct uterm_vt *vt, unsigned int action, void *data)
-{
-	struct kmscon_seat *seat = data;
-	struct shl_dlist *iter;
-	struct kmscon_video *vid;
+	struct app_seat *seat = data;
 	struct kmscon_app *app = seat->app;
+	struct shl_dlist *iter;
+	struct app_video *vid;
 
-	if (action == UTERM_VT_ACTIVATE) {
+	switch (event) {
+	case KMSCON_SEAT_WAKE_UP:
 		seat->awake = true;
-		uterm_input_wake_up(seat->input);
 
 		shl_dlist_for_each(iter, &seat->videos) {
-			vid = shl_dlist_entry(iter, struct kmscon_video, list);
+			vid = shl_dlist_entry(iter, struct app_video, list);
 			uterm_video_wake_up(vid->video);
 		}
-		kmscon_ui_wake_up(seat->ui);
-	} else if (action == UTERM_VT_DEACTIVATE) {
-		kmscon_ui_sleep(seat->ui);
+		break;
+	case KMSCON_SEAT_SLEEP:
 		shl_dlist_for_each(iter, &seat->videos) {
-			vid = shl_dlist_entry(iter, struct kmscon_video, list);
+			vid = shl_dlist_entry(iter, struct app_video, list);
 			uterm_video_sleep(vid->video);
 		}
 
-		uterm_input_sleep(seat->input);
-		seat->awake = false;
-
 		if (app->vt_exit_count > 0) {
 			log_debug("deactivating VT on exit, %d to go",
 				  app->vt_exit_count - 1);
 			if (!--app->vt_exit_count)
 				ev_eloop_exit(app->vt_eloop);
 		}
-	}
 
-	return 0;
+		seat->awake = false;
+		break;
+	}
 }
 
-static void seat_new(struct kmscon_app *app,
-		     struct uterm_monitor_seat *useat,
-		     const char *sname)
+static int app_seat_new(struct kmscon_app *app, struct app_seat **out,
+			const char *sname)
 {
-	struct kmscon_seat *seat;
+	struct app_seat *seat;
 	int ret;
 	unsigned int i;
 	bool found;
@@ -139,101 +125,116 @@ static void seat_new(struct kmscon_app *app,
 	}
 
 	if (!found) {
-		log_info("ignoring seat %s as not specified in seat-list",
+		log_info("ignoring new seat %s as not specified in seat-list",
 			 sname);
-		return;
+		return -ERANGE;
 	}
 
+	log_debug("new seat %s", sname);
+
 	seat = malloc(sizeof(*seat));
-	if (!seat)
-		return;
+	if (!seat) {
+		log_error("cannot allocate memory for seat %s", sname);
+		return -ENOMEM;
+	}
 	memset(seat, 0, sizeof(*seat));
 	seat->app = app;
-	seat->useat = useat;
 	shl_dlist_init(&seat->videos);
 
-	seat->sname = strdup(sname);
-	if (!seat->sname) {
-		log_err("cannot allocate memory for seat name");
+	seat->name = strdup(sname);
+	if (!seat->name) {
+		log_error("cannot copy seat name on seat %s", sname);
+		ret = -ENOMEM;
 		goto err_free;
 	}
 
-	ret = uterm_input_new(&seat->input, app->eloop,
-			      kmscon_conf.xkb_layout,
-			      kmscon_conf.xkb_variant,
-			      kmscon_conf.xkb_options,
-			      kmscon_conf.xkb_repeat_delay,
-			      kmscon_conf.xkb_repeat_rate);
-	if (ret)
+	ret = kmscon_seat_new(&seat->seat, app->eloop, app->vtm, sname,
+			      app_seat_event, seat);
+	if (ret) {
+		log_error("cannot create seat object on seat %s: %d",
+			  sname, ret);
 		goto err_name;
+	}
 
-	ret = uterm_vt_allocate(app->vtm, &seat->vt, seat->sname,
-				seat->input, kmscon_conf.vt, vt_event, seat);
-	if (ret)
-		goto err_input;
-
-	ret = kmscon_ui_new(&seat->ui, app->eloop, seat->input, seat->sname);
-	if (ret)
-		goto err_vt;
-
-	uterm_monitor_set_seat_data(seat->useat, seat);
 	shl_dlist_link(&app->seats, &seat->list);
+	*out = seat;
+	return 0;
 
-	log_info("new seat %s", seat->sname);
-	return;
-
-err_vt:
-	uterm_vt_deallocate(seat->vt);
-err_input:
-	uterm_input_unref(seat->input);
 err_name:
-	free(seat->sname);
+	free(seat->name);
 err_free:
 	free(seat);
+	return ret;
 }
 
-static void seat_free(struct kmscon_seat *seat)
+static void app_seat_free(struct app_seat *seat)
 {
-	log_info("free seat %s", seat->sname);
+	log_debug("free seat %s", seat->name);
 
 	shl_dlist_unlink(&seat->list);
-	uterm_monitor_set_seat_data(seat->useat, NULL);
-	kmscon_ui_free(seat->ui);
-	uterm_input_unref(seat->input);
-	uterm_vt_deallocate(seat->vt);
-	free(seat->sname);
+	kmscon_seat_free(seat->seat);
+	free(seat->name);
 	free(seat);
 }
 
-static void seat_add_video(struct kmscon_seat *seat,
-			   struct uterm_monitor_dev *dev,
-			   unsigned int type,
-			   const char *node)
+static void app_seat_video_event(struct uterm_video *video,
+				 struct uterm_video_hotplug *ev,
+				 void *data)
+{
+	struct app_video *vid = data;
+
+	switch (ev->action) {
+	case UTERM_NEW:
+		kmscon_seat_add_display(vid->seat->seat, ev->display);
+		break;
+	case UTERM_GONE:
+		kmscon_seat_remove_display(vid->seat->seat, ev->display);
+		break;
+	}
+}
+
+static int app_seat_add_video(struct app_seat *seat,
+			      struct app_video **out,
+			      unsigned int type,
+			      const char *node)
 {
 	int ret;
 	unsigned int mode;
-	struct kmscon_video *vid;
+	struct app_video *vid;
 
 	if (kmscon_conf.use_fbdev) {
 		if (type != UTERM_MONITOR_FBDEV &&
 		    type != UTERM_MONITOR_FBDEV_DRM) {
-			log_debug("ignoring %s as it is not fbdev device",
-				  node);
-			return;
+			log_info("ignoring video device %s on seat %s as it is not an fbdev device",
+				  node, seat->name);
+			return -ERANGE;
 		}
 	} else {
 		if (type == UTERM_MONITOR_FBDEV_DRM) {
-			log_debug("ignoring %s as it is a DRM-fbdev device",
-				  node);
-			return;
+			log_info("ignoring video device %s on seat %s as it is a DRM-fbdev device",
+				  node, seat->name);
+			return -ERANGE;
 		}
 	}
 
+	log_debug("new video device %s on seat %s", node, seat->name);
+
 	vid = malloc(sizeof(*vid));
-	if (!vid)
-		return;
+	if (!vid) {
+		log_error("cannot allocate memory for video device %s on seat %s",
+			  node, seat->name);
+		return -ENOMEM;
+	}
 	memset(vid, 0, sizeof(*vid));
-	vid->vdev = dev;
+	vid->seat = seat;
+
+	vid->node = strdup(node);
+	if (!vid->node) {
+		log_error("cannot copy video device name %s on seat %s",
+			  node, seat->name);
+		ret = -ENOMEM;
+		goto err_free;
+	}
 
 	if (type == UTERM_MONITOR_DRM) {
 		if (kmscon_conf.dumb)
@@ -247,122 +248,153 @@ static void seat_add_video(struct kmscon_seat *seat,
 	ret = uterm_video_new(&vid->video, seat->app->eloop, mode, node);
 	if (ret) {
 		if (mode == UTERM_VIDEO_DRM) {
-			log_info("cannot create drm device; trying dumb drm mode");
+			log_info("cannot create drm device %s on seat %s (%d); trying dumb drm mode",
+				 vid->node, seat->name, ret);
 			ret = uterm_video_new(&vid->video, seat->app->eloop,
 					      UTERM_VIDEO_DUMB, node);
 			if (ret)
-				goto err_free;
+				goto err_node;
 		} else {
-			goto err_free;
+			goto err_node;
 		}
 	}
 
-	kmscon_ui_add_video(seat->ui, vid->video);
+	ret = uterm_video_register_cb(vid->video, app_seat_video_event, vid);
+	if (ret) {
+		log_error("cannot register video callback for device %s on seat %s: %d",
+			  vid->node, seat->name, ret);
+		goto err_video;
+	}
+
 	if (seat->awake)
 		uterm_video_wake_up(vid->video);
+
 	shl_dlist_link(&seat->videos, &vid->list);
+	*out = vid;
+	return 0;
 
-	log_debug("new graphics device on seat %s", seat->sname);
-	return;
-
+err_video:
+	uterm_video_unref(vid->video);
+err_node:
+	free(vid->node);
 err_free:
 	free(vid);
-	log_warning("cannot add video object %s on %s", node, seat->sname);
-	return;
+	return ret;
 }
 
-static void seat_rm_video(struct kmscon_seat *seat,
-			  struct uterm_monitor_dev *dev)
+static void app_seat_remove_video(struct app_seat *seat, struct app_video *vid)
 {
-	struct shl_dlist *iter;
-	struct kmscon_video *vid;
+	log_debug("free video device %s on seat %s", vid->node, seat->name);
 
-	shl_dlist_for_each(iter, &seat->videos) {
-		vid = shl_dlist_entry(iter, struct kmscon_video, list);
-		if (vid->vdev != dev)
-			continue;
-
-		log_debug("free graphics device on seat %s", seat->sname);
-
-		kmscon_ui_remove_video(seat->ui, vid->video);
-		uterm_video_unref(vid->video);
-		shl_dlist_unlink(&vid->list);
-		free(vid);
-
-		break;
-	}
+	shl_dlist_unlink(&vid->list);
+	uterm_video_unregister_cb(vid->video, app_seat_video_event, vid);
+	uterm_video_unref(vid->video);
+	free(vid->node);
+	free(vid);
 }
 
-static void seat_hotplug_video(struct kmscon_seat *seat,
-			       struct uterm_monitor_dev *dev)
-{
-	struct shl_dlist *iter;
-	struct kmscon_video *vid;
-
-	shl_dlist_for_each(iter, &seat->videos) {
-		vid = shl_dlist_entry(iter, struct kmscon_video, list);
-		if (vid->vdev != dev)
-			continue;
-
-		uterm_video_poll(vid->video);
-		break;
-	}
-}
-
-static void monitor_event(struct uterm_monitor *mon,
-			  struct uterm_monitor_event *ev,
-			  void *data)
+static void app_monitor_event(struct uterm_monitor *mon,
+			      struct uterm_monitor_event *ev,
+			      void *data)
 {
 	struct kmscon_app *app = data;
-	struct kmscon_seat *seat;
+	struct app_seat *seat;
+	struct app_video *vid;
+	int ret;
 
 	switch (ev->type) {
 	case UTERM_MONITOR_NEW_SEAT:
-		seat_new(app, ev->seat, ev->seat_name);
+		ret = app_seat_new(app, &seat, ev->seat_name);
+		if (ret)
+			return;
+		uterm_monitor_set_seat_data(ev->seat, seat);
 		break;
 	case UTERM_MONITOR_FREE_SEAT:
 		if (ev->seat_data)
-			seat_free(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_DRM:
+		case UTERM_MONITOR_FBDEV:
+		case UTERM_MONITOR_FBDEV_DRM:
+			ret = app_seat_add_video(seat, &vid, ev->dev_type,
+						 ev->dev_node);
+			if (ret)
+				return;
+			uterm_monitor_set_dev_data(ev->dev, vid);
 			break;
-		if (ev->dev_type == UTERM_MONITOR_DRM ||
-		    ev->dev_type == UTERM_MONITOR_FBDEV ||
-		    ev->dev_type == UTERM_MONITOR_FBDEV_DRM)
-			seat_add_video(seat, ev->dev, ev->dev_type,
-				       ev->dev_node);
-		else if (ev->dev_type == UTERM_MONITOR_INPUT)
-			uterm_input_add_dev(seat->input, ev->dev_node);
+		case UTERM_MONITOR_INPUT:
+			log_debug("new input device %s on seat %s",
+				  ev->dev_node, seat->name);
+			kmscon_seat_add_input(seat->seat, ev->dev_node);
+			break;
+		}
 		break;
 	case UTERM_MONITOR_FREE_DEV:
 		seat = ev->seat_data;
 		if (!seat)
+			return;
+
+		switch (ev->dev_type) {
+		case UTERM_MONITOR_DRM:
+		case UTERM_MONITOR_FBDEV:
+		case UTERM_MONITOR_FBDEV_DRM:
+			if (ev->dev_data)
+				app_seat_remove_video(seat, ev->dev_data);
 			break;
-		if (ev->dev_type == UTERM_MONITOR_DRM ||
-		    ev->dev_type == UTERM_MONITOR_FBDEV ||
-		    ev->dev_type == UTERM_MONITOR_FBDEV_DRM)
-			seat_rm_video(seat, ev->dev);
-		else if (ev->dev_type == UTERM_MONITOR_INPUT)
-			uterm_input_remove_dev(seat->input, ev->dev_node);
+		case UTERM_MONITOR_INPUT:
+			log_debug("free input device %s on seat %s",
+				  ev->dev_node, seat->name);
+			kmscon_seat_remove_input(seat->seat, ev->dev_node);
+			break;
+		}
 		break;
 	case UTERM_MONITOR_HOTPLUG_DEV:
 		seat = ev->seat_data;
 		if (!seat)
+			return;
+
+		switch (ev->dev_type) {
+		case UTERM_MONITOR_DRM:
+		case UTERM_MONITOR_FBDEV:
+		case UTERM_MONITOR_FBDEV_DRM:
+			vid = ev->dev_data;
+			if (!vid)
+				return;
+
+			log_debug("video hotplug event on device %s on seat %s",
+				  vid->node, seat->name);
+			uterm_video_poll(vid->video);
 			break;
-		seat_hotplug_video(seat, ev->dev);
+		}
 		break;
 	}
 }
 
+static void app_sig_generic(struct ev_eloop *eloop,
+			    struct signalfd_siginfo *info,
+			    void *data)
+{
+	struct kmscon_app *app = data;
+
+	log_info("terminating due to caught signal %d", info->ssi_signo);
+	ev_eloop_exit(app->eloop);
+}
+
 static void destroy_app(struct kmscon_app *app)
 {
 	uterm_monitor_unref(app->mon);
 	uterm_vt_master_unref(app->vtm);
-	ev_eloop_unregister_signal_cb(app->eloop, SIGINT, sig_generic, app);
-	ev_eloop_unregister_signal_cb(app->eloop, SIGTERM, sig_generic, app);
 	ev_eloop_rm_eloop(app->vt_eloop);
+	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);
 }
 
@@ -370,34 +402,47 @@ static int setup_app(struct kmscon_app *app)
 {
 	int ret;
 
-	ret = ev_eloop_new(&app->eloop, log_llog);
-	if (ret)
-		goto err_app;
-
-	ret = ev_eloop_register_signal_cb(app->eloop, SIGTERM,
-						sig_generic, app);
-	if (ret)
-		goto err_app;
-
-	ret = ev_eloop_register_signal_cb(app->eloop, SIGINT,
-						sig_generic, app);
-	if (ret)
-		goto err_app;
-
-	ret = ev_eloop_new_eloop(app->eloop, &app->vt_eloop);
-	if (ret)
-		goto err_app;
-
-	ret = uterm_vt_master_new(&app->vtm, app->vt_eloop);
-	if (ret)
-		goto err_app;
-
 	shl_dlist_init(&app->seats);
 
-	ret = uterm_monitor_new(&app->mon, app->eloop, monitor_event, app);
-	if (ret)
+	ret = ev_eloop_new(&app->eloop, log_llog);
+	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_new_eloop(app->eloop, &app->vt_eloop);
+	if (ret) {
+		log_error("cannot create VT eloop object: %d", ret);
+		goto err_app;
+	}
+
+	ret = uterm_vt_master_new(&app->vtm, app->vt_eloop);
+	if (ret) {
+		log_error("cannot create VT master: %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;
diff --git a/src/kmscon_seat.c b/src/kmscon_seat.c
new file mode 100644
index 0000000..91a8a5d
--- /dev/null
+++ b/src/kmscon_seat.c
@@ -0,0 +1,491 @@
+/*
+ * Seats
+ *
+ * Copyright (c) 2012 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.
+ */
+
+/*
+ * Seats
+ * A seat is a single session that is self-hosting and provides all the
+ * interaction for a single logged-in user.
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include "eloop.h"
+#include "kmscon_conf.h"
+#include "kmscon_seat.h"
+#include "kmscon_terminal.h"
+#include "log.h"
+#include "shl_dlist.h"
+#include "uterm.h"
+
+#define LOG_SUBSYSTEM "seat"
+
+struct kmscon_session {
+	struct shl_dlist list;
+	unsigned long ref;
+	struct kmscon_seat *seat;
+
+	kmscon_session_cb_t cb;
+	void *data;
+};
+
+struct kmscon_display {
+	struct shl_dlist list;
+	struct kmscon_seat *seat;
+	struct uterm_display *disp;
+	bool activated;
+};
+
+struct kmscon_seat {
+	struct ev_eloop *eloop;
+	struct uterm_vt_master *vtm;
+
+	char *name;
+	bool awake;
+	struct uterm_input *input;
+	struct uterm_vt *vt;
+	struct shl_dlist displays;
+
+	struct shl_dlist sessions;
+	struct kmscon_session *cur_sess;
+
+	kmscon_seat_cb_t cb;
+	void *data;
+};
+
+static void session_call(struct kmscon_session *sess, unsigned int event,
+			 struct uterm_display *disp)
+{
+	if (!sess->cb)
+		return;
+
+	sess->cb(sess, event, disp, sess->data);
+}
+
+static void session_wake_up(struct kmscon_session *sess)
+{
+	session_call(sess, KMSCON_SESSION_WAKE_UP, NULL);
+}
+
+static void session_sleep(struct kmscon_session *sess)
+{
+	session_call(sess, KMSCON_SESSION_SLEEP, NULL);
+}
+
+static void session_display_new(struct kmscon_session *sess,
+				struct uterm_display *disp)
+{
+	session_call(sess, KMSCON_SESSION_DISPLAY_NEW, disp);
+}
+
+static void session_display_gone(struct kmscon_session *sess,
+				 struct uterm_display *disp)
+{
+	session_call(sess, KMSCON_SESSION_DISPLAY_GONE, disp);
+}
+
+static void session_activate(struct kmscon_session *sess)
+{
+	struct kmscon_seat *seat = sess->seat;
+
+	if (seat->cur_sess == sess)
+		return;
+
+	if (seat->cur_sess) {
+		if (seat->awake)
+			session_sleep(seat->cur_sess);
+		seat->cur_sess = NULL;
+	}
+
+	seat->cur_sess = sess;
+	if (seat->awake)
+		session_wake_up(sess);
+}
+
+static void session_deactivate(struct kmscon_session *sess)
+{
+	struct kmscon_seat *seat = sess->seat;
+
+	if (seat->cur_sess != sess)
+		return;
+
+	if (seat->awake)
+		session_sleep(sess);
+
+	if (shl_dlist_empty(&seat->sessions)) {
+		seat->cur_sess = NULL;
+	} else {
+		seat->cur_sess = shl_dlist_entry(seat->sessions.next,
+						 struct kmscon_session,
+						 list);
+		if (seat->awake)
+			session_wake_up(seat->cur_sess);
+	}
+}
+
+static void activate_display(struct kmscon_display *d)
+{
+	int ret;
+	struct shl_dlist *iter, *tmp;
+	struct kmscon_session *s;
+	struct kmscon_seat *seat = d->seat;
+
+	if (d->activated)
+		return;
+
+	if (uterm_display_get_state(d->disp) == UTERM_DISPLAY_INACTIVE) {
+		ret = uterm_display_activate(d->disp, NULL);
+		if (ret)
+			return;
+
+		d->activated = true;
+
+		shl_dlist_for_each_safe(iter, tmp, &seat->sessions) {
+			s = shl_dlist_entry(iter, struct kmscon_session, list);
+			session_display_new(s, d->disp);
+		}
+
+		ret = uterm_display_set_dpms(d->disp, UTERM_DPMS_ON);
+		if (ret)
+			log_warning("cannot set DPMS state to on for display: %d",
+				    ret);
+	}
+}
+
+static int seat_add_display(struct kmscon_seat *seat,
+			    struct uterm_display *disp)
+{
+	struct kmscon_display *d;
+
+	log_debug("add display %p to seat %s", disp, seat->name);
+
+	d = malloc(sizeof(*d));
+	if (!d)
+		return -ENOMEM;
+	memset(d, 0, sizeof(*d));
+	d->disp = disp;
+	d->seat = seat;
+
+	uterm_display_ref(d->disp);
+	shl_dlist_link(&seat->displays, &d->list);
+	activate_display(d);
+	return 0;
+}
+
+static void seat_remove_display(struct kmscon_seat *seat,
+				struct kmscon_display *d)
+{
+	struct shl_dlist *iter, *tmp;
+	struct kmscon_session *s;
+
+	log_debug("remove display %p from seat %s", d->disp, seat->name);
+
+	shl_dlist_unlink(&d->list);
+
+	if (d->activated) {
+		shl_dlist_for_each_safe(iter, tmp, &seat->sessions) {
+			s = shl_dlist_entry(iter, struct kmscon_session, list);
+			session_display_gone(s, d->disp);
+		}
+	}
+
+	uterm_display_unref(d->disp);
+	free(d);
+}
+
+static int seat_vt_event(struct uterm_vt *vt, unsigned int event, void *data)
+{
+	struct kmscon_seat *seat = data;
+	struct shl_dlist *iter;
+	struct kmscon_display *d;
+
+	switch (event) {
+	case UTERM_VT_ACTIVATE:
+		seat->awake = true;
+		if (seat->cb)
+			seat->cb(seat, KMSCON_SEAT_WAKE_UP, seat->data);
+
+		uterm_input_wake_up(seat->input);
+
+		shl_dlist_for_each(iter, &seat->displays) {
+			d = shl_dlist_entry(iter, struct kmscon_display, list);
+			activate_display(d);
+		}
+
+		if (seat->cur_sess)
+			session_wake_up(seat->cur_sess);
+		break;
+	case UTERM_VT_DEACTIVATE:
+		if (seat->cur_sess)
+			session_sleep(seat->cur_sess);
+
+		uterm_input_sleep(seat->input);
+
+		if (seat->cb)
+			seat->cb(seat, KMSCON_SEAT_SLEEP, seat->data);
+		seat->awake = false;
+		break;
+	}
+
+	return 0;
+}
+
+int kmscon_seat_new(struct kmscon_seat **out,
+		    struct ev_eloop *eloop,
+		    struct uterm_vt_master *vtm,
+		    const char *seatname,
+		    kmscon_seat_cb_t cb,
+		    void *data)
+{
+	struct kmscon_seat *seat;
+	int ret;
+	struct kmscon_session *s;
+
+	if (!out || !eloop || !vtm || !seatname)
+		return -EINVAL;
+
+	seat = malloc(sizeof(*seat));
+	if (!seat)
+		return -ENOMEM;
+	memset(seat, 0, sizeof(*seat));
+	seat->eloop = eloop;
+	seat->vtm = vtm;
+	seat->cb = cb;
+	seat->data = data;
+	shl_dlist_init(&seat->displays);
+	shl_dlist_init(&seat->sessions);
+
+	seat->name = strdup(seatname);
+	if (!seat->name) {
+		log_error("cannot copy string");
+		ret = -ENOMEM;
+		goto err_free;
+	}
+
+	ret = uterm_input_new(&seat->input, seat->eloop,
+			      kmscon_conf.xkb_layout,
+			      kmscon_conf.xkb_variant,
+			      kmscon_conf.xkb_options,
+			      kmscon_conf.xkb_repeat_delay,
+			      kmscon_conf.xkb_repeat_rate);
+	if (ret)
+		goto err_name;
+
+	ret = uterm_vt_allocate(seat->vtm, &seat->vt, seat->name,
+				seat->input, kmscon_conf.vt, seat_vt_event,
+				seat);
+	if (ret)
+		goto err_input;
+
+	ret = kmscon_terminal_register(&s, seat);
+	if (ret)
+		goto err_vt;
+
+	ev_eloop_ref(seat->eloop);
+	uterm_vt_master_ref(seat->vtm);
+	*out = seat;
+	return 0;
+
+err_vt:
+	uterm_vt_deallocate(seat->vt);
+err_input:
+	uterm_input_unref(seat->input);
+err_name:
+	free(seat->name);
+err_free:
+	free(seat);
+	return ret;
+}
+
+void kmscon_seat_free(struct kmscon_seat *seat)
+{
+	struct kmscon_display *d;
+	struct kmscon_session *s;
+
+	if (!seat)
+		return;
+
+	while (!shl_dlist_empty(&seat->sessions)) {
+		s = shl_dlist_entry(seat->sessions.next,
+				    struct kmscon_session,
+				    list);
+		kmscon_session_unregister(s);
+	}
+
+	while (!shl_dlist_empty(&seat->displays)) {
+		d = shl_dlist_entry(seat->displays.next,
+				    struct kmscon_display,
+				    list);
+		seat_remove_display(seat, d);
+	}
+
+	uterm_vt_deallocate(seat->vt);
+	uterm_input_unref(seat->input);
+	free(seat->name);
+	uterm_vt_master_unref(seat->vtm);
+	ev_eloop_unref(seat->eloop);
+	free(seat);
+}
+
+int kmscon_seat_add_display(struct kmscon_seat *seat,
+			    struct uterm_display *disp)
+{
+	if (!seat || !disp)
+		return -EINVAL;
+
+	return seat_add_display(seat, disp);
+}
+
+void kmscon_seat_remove_display(struct kmscon_seat *seat,
+				struct uterm_display *disp)
+{
+	struct shl_dlist *iter;
+	struct kmscon_display *d;
+
+	shl_dlist_for_each(iter, &seat->displays) {
+		d = shl_dlist_entry(iter, struct kmscon_display, list);
+		if (d->disp != disp)
+			continue;
+
+		seat_remove_display(seat, d);
+		break;
+	}
+}
+
+int kmscon_seat_add_input(struct kmscon_seat *seat, const char *node)
+{
+	if (!seat || !node)
+		return -EINVAL;
+
+	uterm_input_add_dev(seat->input, node);
+	return 0;
+}
+
+void kmscon_seat_remove_input(struct kmscon_seat *seat, const char *node)
+{
+	if (!seat || !node)
+		return;
+
+	uterm_input_remove_dev(seat->input, node);
+}
+
+const char *kmscon_seat_get_name(struct kmscon_seat *seat)
+{
+	if (!seat)
+		return NULL;
+
+	return seat->name;
+}
+
+struct uterm_input *kmscon_seat_get_input(struct kmscon_seat *seat)
+{
+	if (!seat)
+		return NULL;
+
+	return seat->input;
+}
+
+struct ev_eloop *kmscon_seat_get_eloop(struct kmscon_seat *seat)
+{
+	if (!seat)
+		return NULL;
+
+	return seat->eloop;
+}
+
+int kmscon_seat_register_session(struct kmscon_seat *seat,
+				 struct kmscon_session **out,
+				 kmscon_session_cb_t cb,
+				 void *data)
+{
+	struct kmscon_session *sess;
+
+	if (!seat || !out)
+		return -EINVAL;
+
+	sess = malloc(sizeof(*sess));
+	if (!sess) {
+		log_error("cannot allocate memory for new session on seat %s",
+			  seat->name);
+		return -ENOMEM;
+	}
+	memset(sess, 0, sizeof(*sess));
+	sess->ref = 1;
+	sess->seat = seat;
+	sess->cb = cb;
+	sess->data = data;
+
+	shl_dlist_link(&seat->sessions, &sess->list);
+	*out = sess;
+
+	if (!seat->cur_sess)
+		session_activate(sess);
+
+	return 0;
+}
+
+void kmscon_session_ref(struct kmscon_session *sess)
+{
+	if (!sess || !sess->ref)
+		return;
+
+	++sess->ref;
+}
+
+void kmscon_session_unref(struct kmscon_session *sess)
+{
+	if (!sess || !sess->ref || --sess->ref)
+		return;
+
+	kmscon_session_unregister(sess);
+	free(sess);
+}
+
+void kmscon_session_unregister(struct kmscon_session *sess)
+{
+	if (!sess || !sess->seat)
+		return;
+
+	shl_dlist_unlink(&sess->list);
+	session_deactivate(sess);
+	sess->seat = NULL;
+	session_call(sess, KMSCON_SESSION_UNREGISTER, NULL);
+}
+
+void kmscon_session_activate(struct kmscon_session *sess)
+{
+	if (!sess || !sess->seat)
+		return;
+
+	session_activate(sess);
+}
+
+void kmscon_session_deactivate(struct kmscon_session *sess)
+{
+	if (!sess || !sess->seat)
+		return;
+
+	session_deactivate(sess);
+}
diff --git a/src/kmscon_seat.h b/src/kmscon_seat.h
new file mode 100644
index 0000000..76f8e76
--- /dev/null
+++ b/src/kmscon_seat.h
@@ -0,0 +1,96 @@
+/*
+ * Seats
+ *
+ * Copyright (c) 2012 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.
+ */
+
+/*
+ * Seats
+ * A seat is a single session that is self-hosting and provides all the
+ * interaction for a single logged-in user.
+ */
+
+#ifndef KMSCON_SEAT_H
+#define KMSCON_SEAT_H
+
+#include <stdlib.h>
+#include <unistd.h>
+#include "eloop.h"
+#include "uterm.h"
+
+struct kmscon_seat;
+struct kmscon_session;
+
+enum kmscon_seat_event {
+	KMSCON_SEAT_WAKE_UP,
+	KMSCON_SEAT_SLEEP,
+};
+
+typedef void (*kmscon_seat_cb_t) (struct kmscon_seat *seat,
+				  unsigned int event,
+				  void *data);
+
+enum kmscon_session_event {
+	KMSCON_SESSION_DISPLAY_NEW,
+	KMSCON_SESSION_DISPLAY_GONE,
+	KMSCON_SESSION_WAKE_UP,
+	KMSCON_SESSION_SLEEP,
+	KMSCON_SESSION_UNREGISTER,
+};
+
+typedef void (*kmscon_session_cb_t) (struct kmscon_session *session,
+				     unsigned int event,
+				     struct uterm_display *disp,
+				     void *data);
+
+int kmscon_seat_new(struct kmscon_seat **out,
+		    struct ev_eloop *eloop,
+		    struct uterm_vt_master *vtm,
+		    const char *seatname,
+		    kmscon_seat_cb_t cb,
+		    void *data);
+void kmscon_seat_free(struct kmscon_seat *seat);
+
+int kmscon_seat_add_display(struct kmscon_seat *seat,
+			    struct uterm_display *disp);
+void kmscon_seat_remove_display(struct kmscon_seat *seat,
+				struct uterm_display *disp);
+int kmscon_seat_add_input(struct kmscon_seat *seat, const char *node);
+void kmscon_seat_remove_input(struct kmscon_seat *seat, const char *node);
+
+const char *kmscon_seat_get_name(struct kmscon_seat *seat);
+struct uterm_input *kmscon_seat_get_input(struct kmscon_seat *seat);
+struct ev_eloop *kmscon_seat_get_eloop(struct kmscon_seat *seat);
+
+int kmscon_seat_register_session(struct kmscon_seat *seat,
+				 struct kmscon_session **out,
+				 kmscon_session_cb_t cb,
+				 void *data);
+
+void kmscon_session_ref(struct kmscon_session *sess);
+void kmscon_session_unref(struct kmscon_session *sess);
+void kmscon_session_unregister(struct kmscon_session *sess);
+
+void kmscon_session_activate(struct kmscon_session *sess);
+void kmscon_session_deactivate(struct kmscon_session *sess);
+
+#endif /* KMSCON_SEAT_H */
diff --git a/src/kmscon_terminal.c b/src/kmscon_terminal.c
index a616899..6e4da08 100644
--- a/src/kmscon_terminal.c
+++ b/src/kmscon_terminal.c
@@ -36,6 +36,7 @@
 #include <string.h>
 #include "eloop.h"
 #include "kmscon_conf.h"
+#include "kmscon_seat.h"
 #include "kmscon_terminal.h"
 #include "log.h"
 #include "pty.h"
@@ -62,6 +63,8 @@ struct kmscon_terminal {
 	bool opened;
 	bool awake;
 
+	struct kmscon_session *session;
+
 	struct shl_dlist screens;
 	unsigned int min_cols;
 	unsigned int min_rows;
@@ -73,9 +76,6 @@ struct kmscon_terminal {
 	struct tsm_vte *vte;
 	struct kmscon_pty *pty;
 	struct ev_fd *ptyfd;
-
-	kmscon_terminal_event_cb cb;
-	void *data;
 };
 
 static void redraw(struct kmscon_terminal *term)
@@ -180,6 +180,7 @@ static void terminal_resize(struct kmscon_terminal *term,
 	/* shrinking always succeeds */
 	tsm_screen_resize(term->console, term->min_cols, term->min_rows);
 	kmscon_pty_resize(term->pty, term->min_cols, term->min_rows);
+	schedule_redraw(term);
 }
 
 static int add_display(struct kmscon_terminal *term, struct uterm_display *disp)
@@ -310,45 +311,6 @@ static void rm_display(struct kmscon_terminal *term, struct uterm_display *disp)
 
 	log_debug("removed display %p from terminal %p", disp, term);
 	free_screen(term, scr, true);
-	if (shl_dlist_empty(&term->screens) && term->cb)
-		term->cb(term, KMSCON_TERMINAL_NO_DISPLAY, term->data);
-}
-
-static void rm_all_screens(struct kmscon_terminal *term)
-{
-	struct shl_dlist *iter;
-	struct screen *scr;
-
-	while ((iter = term->screens.next) != &term->screens) {
-		scr = shl_dlist_entry(iter, struct screen, list);
-		free_screen(term, scr, false);
-	}
-
-	term->min_cols = 0;
-	term->min_rows = 0;
-}
-
-static void pty_input(struct kmscon_pty *pty, const char *u8, size_t len,
-								void *data)
-{
-	struct kmscon_terminal *term = data;
-
-	if (!len) {
-		if (term->cb)
-			term->cb(term, KMSCON_TERMINAL_HUP, term->data);
-		else
-			kmscon_terminal_open(term, term->cb, term->data);
-	} else {
-		tsm_vte_input(term->vte, u8, len);
-		schedule_redraw(term);
-	}
-}
-
-static void pty_event(struct ev_fd *fd, int mask, void *data)
-{
-	struct kmscon_terminal *term = data;
-
-	kmscon_pty_dispatch(term->pty);
 }
 
 static void input_event(struct uterm_input *input,
@@ -403,6 +365,111 @@ static void input_event(struct uterm_input *input,
 	}
 }
 
+static void rm_all_screens(struct kmscon_terminal *term)
+{
+	struct shl_dlist *iter;
+	struct screen *scr;
+
+	while ((iter = term->screens.next) != &term->screens) {
+		scr = shl_dlist_entry(iter, struct screen, list);
+		free_screen(term, scr, false);
+	}
+
+	term->min_cols = 0;
+	term->min_rows = 0;
+}
+
+static int terminal_open(struct kmscon_terminal *term)
+{
+	int ret;
+	unsigned short width, height;
+
+	kmscon_pty_close(term->pty);
+	tsm_vte_hard_reset(term->vte);
+	width = tsm_screen_get_width(term->console);
+	height = tsm_screen_get_height(term->console);
+	ret = kmscon_pty_open(term->pty, width, height);
+	if (ret) {
+		term->opened = false;
+		return ret;
+	}
+
+	term->opened = true;
+	schedule_redraw(term);
+	return 0;
+}
+
+static void terminal_close(struct kmscon_terminal *term)
+{
+	kmscon_pty_close(term->pty);
+	term->opened = false;
+}
+
+static void terminal_destroy(struct kmscon_terminal *term)
+{
+	log_debug("free terminal object %p", term);
+
+	terminal_close(term);
+	rm_all_screens(term);
+	ev_eloop_rm_timer(term->redraw_timer);
+	ev_timer_unref(term->redraw_timer);
+	uterm_input_unregister_cb(term->input, input_event, term);
+	ev_eloop_rm_fd(term->ptyfd);
+	kmscon_pty_unref(term->pty);
+	tsm_vte_unref(term->vte);
+	tsm_screen_unref(term->console);
+	uterm_input_unref(term->input);
+	ev_eloop_unref(term->eloop);
+	free(term);
+}
+
+static void session_event(struct kmscon_session *session, unsigned int event,
+			  struct uterm_display *disp, void *data)
+{
+	struct kmscon_terminal *term = data;
+
+	switch (event) {
+	case KMSCON_SESSION_DISPLAY_NEW:
+		add_display(term, disp);
+		break;
+	case KMSCON_SESSION_DISPLAY_GONE:
+		rm_display(term, disp);
+		break;
+	case KMSCON_SESSION_WAKE_UP:
+		term->awake = true;
+		if (!term->opened)
+			terminal_open(term);
+		schedule_redraw(term);
+		break;
+	case KMSCON_SESSION_SLEEP:
+		term->awake = false;
+		break;
+	case KMSCON_SESSION_UNREGISTER:
+		terminal_destroy(term);
+		break;
+	}
+}
+
+static void pty_input(struct kmscon_pty *pty, const char *u8, size_t len,
+								void *data)
+{
+	struct kmscon_terminal *term = data;
+
+	if (!len) {
+		terminal_open(term);
+	} else {
+		tsm_vte_input(term->vte, u8, len);
+		schedule_redraw(term);
+	}
+}
+
+static void pty_event(struct ev_fd *fd, int mask, void *data)
+{
+	struct kmscon_terminal *term = data;
+
+	kmscon_pty_dispatch(term->pty);
+}
+
 static void write_event(struct tsm_vte *vte, const char *u8, size_t len,
 			void *data)
 {
@@ -411,17 +478,15 @@ static void write_event(struct tsm_vte *vte, const char *u8, size_t len,
 	kmscon_pty_write(term->pty, u8, len);
 }
 
-int kmscon_terminal_new(struct kmscon_terminal **out,
-			struct ev_eloop *loop,
-			struct uterm_input *input,
-			const char *seat)
+int kmscon_terminal_register(struct kmscon_session **out,
+			     struct kmscon_seat *seat)
 {
 	struct kmscon_terminal *term;
 	int ret;
 	struct itimerspec spec;
 	unsigned long fps;
 
-	if (!out || !loop || !input)
+	if (!out || !seat)
 		return -EINVAL;
 
 	term = malloc(sizeof(*term));
@@ -430,8 +495,8 @@ int kmscon_terminal_new(struct kmscon_terminal **out,
 
 	memset(term, 0, sizeof(*term));
 	term->ref = 1;
-	term->eloop = loop;
-	term->input = input;
+	term->eloop = kmscon_seat_get_eloop(seat);
+	term->input = kmscon_seat_get_input(seat);
 	shl_dlist_init(&term->screens);
 
 	if (kmscon_conf.fps) {
@@ -473,7 +538,7 @@ int kmscon_terminal_new(struct kmscon_terminal **out,
 	if (ret)
 		goto err_pty;
 
-	ret = kmscon_pty_set_seat(term->pty, seat);
+	ret = kmscon_pty_set_seat(term->pty, kmscon_seat_get_name(seat));
 	if (ret)
 		goto err_pty;
 
@@ -501,13 +566,21 @@ int kmscon_terminal_new(struct kmscon_terminal **out,
 	if (ret)
 		goto err_timer;
 
+	ret = kmscon_seat_register_session(seat, &term->session, session_event,
+					   term);
+	if (ret) {
+		log_error("cannot register session for terminal: %d", ret);
+		goto err_redraw;
+	}
+
 	ev_eloop_ref(term->eloop);
 	uterm_input_ref(term->input);
-	*out = term;
-
+	*out = term->session;
 	log_debug("new terminal object %p", term);
 	return 0;
 
+err_redraw:
+	ev_eloop_rm_timer(term->redraw_timer);
 err_timer:
 	ev_timer_unref(term->redraw_timer);
 err_input:
@@ -524,116 +597,3 @@ err_free:
 	free(term);
 	return ret;
 }
-
-void kmscon_terminal_ref(struct kmscon_terminal *term)
-{
-	if (!term)
-		return;
-
-	term->ref++;
-}
-
-void kmscon_terminal_unref(struct kmscon_terminal *term)
-{
-	if (!term || !term->ref)
-		return;
-
-	if (--term->ref)
-		return;
-
-	log_debug("free terminal object %p", term);
-	kmscon_terminal_close(term);
-	rm_all_screens(term);
-	ev_eloop_rm_timer(term->redraw_timer);
-	ev_timer_unref(term->redraw_timer);
-	uterm_input_unregister_cb(term->input, input_event, term);
-	ev_eloop_rm_fd(term->ptyfd);
-	kmscon_pty_unref(term->pty);
-	tsm_vte_unref(term->vte);
-	tsm_screen_unref(term->console);
-	uterm_input_unref(term->input);
-	ev_eloop_unref(term->eloop);
-	free(term);
-}
-
-int kmscon_terminal_open(struct kmscon_terminal *term,
-			kmscon_terminal_event_cb cb, void *data)
-{
-	int ret;
-	unsigned short width, height;
-
-	if (!term)
-		return -EINVAL;
-
-	kmscon_pty_close(term->pty);
-	tsm_vte_hard_reset(term->vte);
-	width = tsm_screen_get_width(term->console);
-	height = tsm_screen_get_height(term->console);
-	ret = kmscon_pty_open(term->pty, width, height);
-	if (ret)
-		return ret;
-
-	term->opened = true;
-	term->cb = cb;
-	term->data = data;
-	return 0;
-}
-
-void kmscon_terminal_close(struct kmscon_terminal *term)
-{
-	if (!term)
-		return;
-
-	kmscon_pty_close(term->pty);
-	term->data = NULL;
-	term->cb = NULL;
-	term->opened = false;
-}
-
-void kmscon_terminal_redraw(struct kmscon_terminal *term)
-{
-	if (!term)
-		return;
-
-	schedule_redraw(term);
-}
-
-int kmscon_terminal_add_display(struct kmscon_terminal *term,
-				struct uterm_display *disp)
-{
-	if (!term || !disp)
-		return -EINVAL;
-
-	return add_display(term, disp);
-}
-
-void kmscon_terminal_remove_display(struct kmscon_terminal *term,
-				    struct uterm_display *disp)
-{
-	if (!term || !disp)
-		return;
-
-	rm_display(term, disp);
-}
-
-void kmscon_terminal_wake_up(struct kmscon_terminal *term)
-{
-	if (!term || term->awake)
-		return;
-
-	term->awake = true;
-	schedule_redraw(term);
-}
-
-void kmscon_terminal_sleep(struct kmscon_terminal *term)
-{
-	if (!term || !term->awake)
-		return;
-
-	term->awake = false;
-}
-
-bool kmscon_terminal_is_awake(struct kmscon_terminal *term)
-{
-	return term && term->awake;
-}
diff --git a/src/kmscon_terminal.h b/src/kmscon_terminal.h
index e41381d..6bdb4bf 100644
--- a/src/kmscon_terminal.h
+++ b/src/kmscon_terminal.h
@@ -34,41 +34,9 @@
 #define KMSCON_TERMINAL_H
 
 #include <stdlib.h>
-#include "eloop.h"
-#include "tsm_screen.h"
-#include "uterm.h"
+#include "kmscon_seat.h"
 
-struct kmscon_terminal;
-
-enum kmscon_terminal_etype {
-	KMSCON_TERMINAL_HUP,		/* child closed */
-	KMSCON_TERMINAL_NO_DISPLAY,	/* no more display connected */
-};
-
-typedef void (*kmscon_terminal_event_cb)
-		(struct kmscon_terminal *term,
-		enum kmscon_terminal_etype type,
-		void *data);
-
-int kmscon_terminal_new(struct kmscon_terminal **out,
-			struct ev_eloop *loop,
-			struct uterm_input *input,
-			const char *seat);
-void kmscon_terminal_ref(struct kmscon_terminal *term);
-void kmscon_terminal_unref(struct kmscon_terminal *term);
-
-int kmscon_terminal_open(struct kmscon_terminal *term,
-			kmscon_terminal_event_cb event_cb, void *data);
-void kmscon_terminal_close(struct kmscon_terminal *term);
-void kmscon_terminal_redraw(struct kmscon_terminal *term);
-
-int kmscon_terminal_add_display(struct kmscon_terminal *term,
-				struct uterm_display *disp);
-void kmscon_terminal_remove_display(struct kmscon_terminal *term,
-				    struct uterm_display *disp);
-
-void kmscon_terminal_wake_up(struct kmscon_terminal *term);
-void kmscon_terminal_sleep(struct kmscon_terminal *term);
-bool kmscon_terminal_is_awake(struct kmscon_terminal *term);
+int kmscon_terminal_register(struct kmscon_session **out,
+			     struct kmscon_seat *seat);
 
 #endif /* KMSCON_TERMINAL_H */