Index: uspace/srv/hid/display/client.c
===================================================================
--- uspace/srv/hid/display/client.c	(revision 38e5f36caa4aaa59cfd863a6deeaa4427a3f7789)
+++ uspace/srv/hid/display/client.c	(revision cf32dbd4a95cb4b405fc474d3971f4fa4e52a5ad)
@@ -39,4 +39,5 @@
 #include "client.h"
 #include "display.h"
+#include "seat.h"
 #include "window.h"
 
@@ -112,4 +113,13 @@
 void ds_client_remove_window(ds_window_t *wnd)
 {
+	ds_seat_t *seat;
+
+	/* Make sure window is no longer focused in any seat */
+	seat = ds_display_first_seat(wnd->client->display);
+	while (seat != NULL) {
+		ds_seat_evac_focus(seat, wnd);
+		seat = ds_display_next_seat(seat);
+	}
+
 	list_remove(&wnd->lwindows);
 	wnd->client = NULL;
Index: uspace/srv/hid/display/client.h
===================================================================
--- uspace/srv/hid/display/client.h	(revision 38e5f36caa4aaa59cfd863a6deeaa4427a3f7789)
+++ uspace/srv/hid/display/client.h	(revision cf32dbd4a95cb4b405fc474d3971f4fa4e52a5ad)
@@ -37,4 +37,6 @@
 #define CLIENT_H
 
+#include <errno.h>
+#include <io/kbd_event.h>
 #include "types/display/client.h"
 #include "types/display/display.h"
Index: uspace/srv/hid/display/display.c
===================================================================
--- uspace/srv/hid/display/display.c	(revision 38e5f36caa4aaa59cfd863a6deeaa4427a3f7789)
+++ uspace/srv/hid/display/display.c	(revision cf32dbd4a95cb4b405fc474d3971f4fa4e52a5ad)
@@ -39,4 +39,5 @@
 #include <stdlib.h>
 #include "client.h"
+#include "seat.h"
 #include "window.h"
 #include "display.h"
@@ -59,4 +60,5 @@
 	disp->gc = gc;
 	disp->next_wnd_id = 1;
+	list_initialize(&disp->seats);
 	*rdisp = disp;
 	return EOK;
@@ -70,4 +72,5 @@
 {
 	assert(list_empty(&disp->clients));
+	assert(list_empty(&disp->seats));
 	free(disp);
 }
@@ -76,5 +79,5 @@
  *
  * @param disp Display
- * @param client client
+ * @param client Client
  */
 void ds_display_add_client(ds_display_t *disp, ds_client_t *client)
@@ -89,5 +92,5 @@
 /** Remove client from display.
  *
- * @param client client
+ * @param client Client
  */
 void ds_display_remove_client(ds_client_t *client)
@@ -160,20 +163,77 @@
 }
 
+/** Post keyboard event to a display.
+ *
+ * The event is routed to the correct window by first determining the
+ * seat the keyboard device belongs to and then the event is sent to the
+ * window focused by that seat.
+ *
+ * @param display Display
+ * @param event Event
+ */
 errno_t ds_display_post_kbd_event(ds_display_t *display, kbd_event_t *event)
 {
-	ds_client_t *client;
-	ds_window_t *wnd;
-
-	// XXX Correctly determine destination window
-
-	client = ds_display_first_client(display);
-	if (client == NULL)
+	ds_seat_t *seat;
+
+	// TODO Determine which seat the event belongs to
+	seat = ds_display_first_seat(display);
+	if (seat == NULL)
 		return EOK;
 
-	wnd = ds_client_first_window(client);
-	if (wnd == NULL)
-		return EOK;
-
-	return ds_client_post_kbd_event(client, wnd, event);
+	return ds_seat_post_kbd_event(seat, event);
+}
+
+/** Add seat to display.
+ *
+ * @param disp Display
+ * @param seat Seat
+ */
+void ds_display_add_seat(ds_display_t *disp, ds_seat_t *seat)
+{
+	assert(seat->display == NULL);
+	assert(!link_used(&seat->lseats));
+
+	seat->display = disp;
+	list_append(&seat->lseats, &disp->seats);
+}
+
+/** Remove seat from display.
+ *
+ * @param seat Seat
+ */
+void ds_display_remove_seat(ds_seat_t *seat)
+{
+	list_remove(&seat->lseats);
+	seat->display = NULL;
+}
+
+/** Get first seat in display.
+ *
+ * @param disp Display
+ * @return First seat or @c NULL if there is none
+ */
+ds_seat_t *ds_display_first_seat(ds_display_t *disp)
+{
+	link_t *link = list_first(&disp->seats);
+
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ds_seat_t, lseats);
+}
+
+/** Get next seat in display.
+ *
+ * @param seat Current seat
+ * @return Next seat or @c NULL if there is none
+ */
+ds_seat_t *ds_display_next_seat(ds_seat_t *seat)
+{
+	link_t *link = list_next(&seat->lseats, &seat->display->seats);
+
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ds_seat_t, lseats);
 }
 
Index: uspace/srv/hid/display/display.h
===================================================================
--- uspace/srv/hid/display/display.h	(revision 38e5f36caa4aaa59cfd863a6deeaa4427a3f7789)
+++ uspace/srv/hid/display/display.h	(revision cf32dbd4a95cb4b405fc474d3971f4fa4e52a5ad)
@@ -42,4 +42,5 @@
 #include "types/display/client.h"
 #include "types/display/display.h"
+#include "types/display/seat.h"
 
 extern errno_t ds_display_create(gfx_context_t *, ds_display_t **);
@@ -51,4 +52,8 @@
 extern ds_window_t *ds_display_find_window(ds_display_t *, ds_wnd_id_t);
 extern errno_t ds_display_post_kbd_event(ds_display_t *, kbd_event_t *);
+extern void ds_display_add_seat(ds_display_t *, ds_seat_t *);
+extern void ds_display_remove_seat(ds_seat_t *);
+extern ds_seat_t *ds_display_first_seat(ds_display_t *);
+extern ds_seat_t *ds_display_next_seat(ds_seat_t *);
 
 #endif
Index: uspace/srv/hid/display/dsops.c
===================================================================
--- uspace/srv/hid/display/dsops.c	(revision 38e5f36caa4aaa59cfd863a6deeaa4427a3f7789)
+++ uspace/srv/hid/display/dsops.c	(revision cf32dbd4a95cb4b405fc474d3971f4fa4e52a5ad)
@@ -38,6 +38,8 @@
 #include <io/log.h>
 #include "client.h"
+#include "display.h"
+#include "dsops.h"
+#include "seat.h"
 #include "window.h"
-#include "dsops.h"
 
 static errno_t disp_window_create(void *, sysarg_t *);
@@ -55,4 +57,5 @@
 	errno_t rc;
 	ds_client_t *client = (ds_client_t *) arg;
+	ds_seat_t *seat;
 	ds_window_t *wnd;
 
@@ -69,4 +72,12 @@
 	wnd->dpos.x = ((wnd->id - 1) & 1) * 400;
 	wnd->dpos.y = ((wnd->id - 1) & 2) / 2 * 300;
+
+	/*
+	 * XXX This should be performed by window manager. It needs to determine
+	 * whether the new window should get focus and which seat should
+	 * focus on it.
+	 */
+	seat = ds_display_first_seat(client->display);
+	ds_seat_set_focus(seat, wnd);
 
 	*rwnd_id = wnd->id;
Index: uspace/srv/hid/display/main.c
===================================================================
--- uspace/srv/hid/display/main.c	(revision 38e5f36caa4aaa59cfd863a6deeaa4427a3f7789)
+++ uspace/srv/hid/display/main.c	(revision cf32dbd4a95cb4b405fc474d3971f4fa4e52a5ad)
@@ -50,4 +50,5 @@
 #include "main.h"
 #include "output.h"
+#include "seat.h"
 #include "window.h"
 
@@ -78,4 +79,5 @@
 {
 	ds_display_t *disp = NULL;
+	ds_seat_t *seat = NULL;
 	gfx_context_t *gc = NULL;
 	errno_t rc;
@@ -84,4 +86,8 @@
 
 	rc = ds_display_create(NULL, &disp);
+	if (rc != EOK)
+		goto error;
+
+	rc = ds_seat_create(disp, &seat);
 	if (rc != EOK)
 		goto error;
@@ -121,4 +127,6 @@
 	if (gc != NULL)
 		gfx_context_delete(gc);
+	if (seat != NULL)
+		ds_seat_destroy(seat);
 	if (disp != NULL)
 		ds_display_destroy(disp);
Index: uspace/srv/hid/display/meson.build
===================================================================
--- uspace/srv/hid/display/meson.build	(revision 38e5f36caa4aaa59cfd863a6deeaa4427a3f7789)
+++ uspace/srv/hid/display/meson.build	(revision cf32dbd4a95cb4b405fc474d3971f4fa4e52a5ad)
@@ -35,4 +35,5 @@
 	'main.c',
 	'output.c',
+	'seat.c',
 	'window.c',
 )
@@ -41,4 +42,5 @@
 	'client.c',
 	'display.c',
+	'seat.c',
 	'window.c',
 	'test/client.c',
Index: uspace/srv/hid/display/seat.c
===================================================================
--- uspace/srv/hid/display/seat.c	(revision cf32dbd4a95cb4b405fc474d3971f4fa4e52a5ad)
+++ uspace/srv/hid/display/seat.c	(revision cf32dbd4a95cb4b405fc474d3971f4fa4e52a5ad)
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2019 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup display
+ * @{
+ */
+/**
+ * @file Display server seat
+ */
+
+#include <adt/list.h>
+#include <errno.h>
+#include <stdlib.h>
+#include "client.h"
+#include "display.h"
+#include "seat.h"
+#include "window.h"
+
+/** Create seat.
+ *
+ * @param display Parent display
+ * @param rseat Place to store pointer to new seat.
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ds_seat_create(ds_display_t *display, ds_seat_t **rseat)
+{
+	ds_seat_t *seat;
+
+	seat = calloc(1, sizeof(ds_seat_t));
+	if (seat == NULL)
+		return ENOMEM;
+
+	ds_display_add_seat(display, seat);
+
+	*rseat = seat;
+	return EOK;
+}
+
+/** Destroy seat.
+ *
+ * @param seat Seat
+ */
+void ds_seat_destroy(ds_seat_t *seat)
+{
+	ds_display_remove_seat(seat);
+	free(seat);
+}
+
+void ds_seat_set_focus(ds_seat_t *seat, ds_window_t *wnd)
+{
+	seat->focus = wnd;
+}
+
+void ds_seat_evac_focus(ds_seat_t *seat, ds_window_t *wnd)
+{
+	ds_window_t *nwnd;
+
+	if (seat->focus == wnd) {
+		/* Focus a different window. XXX Need list of all windows */
+		nwnd = ds_client_next_window(wnd);
+		if (nwnd == NULL)
+			nwnd = ds_client_first_window(wnd->client);
+		if (nwnd == wnd)
+			nwnd = NULL;
+
+		ds_seat_set_focus(seat, nwnd);
+	}
+}
+
+/** Post keyboard event to the seat's focused window.
+ *
+ * @param seat Seat
+ * @param event Event
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ds_seat_post_kbd_event(ds_seat_t *seat, kbd_event_t *event)
+{
+	ds_window_t *dwindow = seat->focus;
+
+	if (dwindow == NULL)
+		return EOK;
+
+	return ds_client_post_kbd_event(dwindow->client, dwindow, event);
+}
+
+/** @}
+ */
Index: uspace/srv/hid/display/seat.h
===================================================================
--- uspace/srv/hid/display/seat.h	(revision cf32dbd4a95cb4b405fc474d3971f4fa4e52a5ad)
+++ uspace/srv/hid/display/seat.h	(revision cf32dbd4a95cb4b405fc474d3971f4fa4e52a5ad)
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2019 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup display
+ * @{
+ */
+/**
+ * @file Display server seat
+ */
+
+#ifndef SEAT_H
+#define SEAT_H
+
+#include <errno.h>
+#include <io/kbd_event.h>
+#include "types/display/display.h"
+#include "types/display/seat.h"
+#include "types/display/window.h"
+
+extern errno_t ds_seat_create(ds_display_t *, ds_seat_t **);
+extern void ds_seat_destroy(ds_seat_t *);
+extern void ds_seat_set_focus(ds_seat_t *, ds_window_t *);
+extern void ds_seat_evac_focus(ds_seat_t *, ds_window_t *);
+extern errno_t ds_seat_post_kbd_event(ds_seat_t *, kbd_event_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/hid/display/test/display.c
===================================================================
--- uspace/srv/hid/display/test/display.c	(revision 38e5f36caa4aaa59cfd863a6deeaa4427a3f7789)
+++ uspace/srv/hid/display/test/display.c	(revision cf32dbd4a95cb4b405fc474d3971f4fa4e52a5ad)
@@ -34,4 +34,5 @@
 #include "../client.h"
 #include "../display.h"
+#include "../seat.h"
 #include "../window.h"
 
@@ -130,8 +131,33 @@
 }
 
+/** Basic seat operation. */
+PCUT_TEST(display_seat)
+{
+	ds_display_t *disp;
+	ds_seat_t *seat;
+	ds_seat_t *s0, *s1;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	s0 = ds_display_first_seat(disp);
+	PCUT_ASSERT_EQUALS(s0, seat);
+
+	s1 = ds_display_next_seat(s0);
+	PCUT_ASSERT_NULL(s1);
+
+	ds_seat_destroy(seat);
+	ds_display_destroy(disp);
+}
+
 /** Test ds_display_post_kbd_event(). */
 PCUT_TEST(display_post_kbd_event)
 {
 	ds_display_t *disp;
+	ds_seat_t *seat;
 	ds_client_t *client;
 	ds_window_t *wnd;
@@ -141,4 +167,7 @@
 
 	rc = ds_display_create(NULL, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -162,4 +191,5 @@
 	ds_window_destroy(wnd);
 	ds_client_destroy(client);
+	ds_seat_destroy(seat);
 	ds_display_destroy(disp);
 }
Index: uspace/srv/hid/display/types/display/display.h
===================================================================
--- uspace/srv/hid/display/types/display/display.h	(revision 38e5f36caa4aaa59cfd863a6deeaa4427a3f7789)
+++ uspace/srv/hid/display/types/display/display.h	(revision cf32dbd4a95cb4b405fc474d3971f4fa4e52a5ad)
@@ -59,4 +59,7 @@
 	/** Input service */
 	input_t *input;
+
+	/** Seats (of ds_seat_t) */
+	list_t seats;
 } ds_display_t;
 
Index: uspace/srv/hid/display/types/display/seat.h
===================================================================
--- uspace/srv/hid/display/types/display/seat.h	(revision cf32dbd4a95cb4b405fc474d3971f4fa4e52a5ad)
+++ uspace/srv/hid/display/types/display/seat.h	(revision cf32dbd4a95cb4b405fc474d3971f4fa4e52a5ad)
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2019 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup display
+ * @{
+ */
+/**
+ * @file Display server seat type
+ */
+
+#ifndef TYPES_DISPLAY_SEAT_H
+#define TYPES_DISPLAY_SEAT_H
+
+#include <adt/list.h>
+
+/** Display server seat */
+typedef struct ds_seat {
+	/** Containing display */
+	struct ds_display *display;
+	/** Link to display->seats */
+	link_t lseats;
+	/** Window this seat is focused on */
+	struct ds_window *focus;
+} ds_seat_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/hid/display/window.h
===================================================================
--- uspace/srv/hid/display/window.h	(revision 38e5f36caa4aaa59cfd863a6deeaa4427a3f7789)
+++ uspace/srv/hid/display/window.h	(revision cf32dbd4a95cb4b405fc474d3971f4fa4e52a5ad)
@@ -37,5 +37,4 @@
 #define WINDOW_H
 
-#include <display/event.h>
 #include <errno.h>
 #include <types/gfx/context.h>
