Index: abi/include/abi/ipc/interfaces.h
===================================================================
--- abi/include/abi/ipc/interfaces.h	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ abi/include/abi/ipc/interfaces.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -190,4 +190,6 @@
 	INTERFACE_DISPLAY =
 	    FOURCC_COMPACT('d', 's', 'p', 'l') | IFACE_EXCHANGE_SERIALIZE,
+	INTERFACE_DISPLAY_CB =
+	    FOURCC_COMPACT('d', 's', 'p', 'l') | IFACE_EXCHANGE_SERIALIZE | IFACE_MOD_CALLBACK,
 	INTERFACE_GC =
 	    FOURCC_COMPACT('g', 'f', 'x', 'c') | IFACE_EXCHANGE_SERIALIZE,
Index: uspace/app/gfxdemo/gfxdemo.c
===================================================================
--- uspace/app/gfxdemo/gfxdemo.c	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/app/gfxdemo/gfxdemo.c	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -44,7 +44,16 @@
 #include <io/console.h>
 #include <io/pixelmap.h>
+#include <stdbool.h>
 #include <stdlib.h>
 #include <str.h>
 #include <window.h>
+
+static void wnd_kbd_event(void *, kbd_event_t *);
+
+static display_wnd_cb_t wnd_cb = {
+	.kbd_event = wnd_kbd_event
+};
+
+static bool quit = false;
 
 /** Clear screen.
@@ -126,4 +135,7 @@
 
 		fibril_usleep(500 * 1000);
+
+		if (quit)
+			break;
 	}
 
@@ -245,4 +257,7 @@
 				goto error;
 			fibril_usleep(250 * 1000);
+
+			if (quit)
+				break;
 		}
 	}
@@ -298,4 +313,7 @@
 
 		fibril_usleep(500 * 1000);
+
+		if (quit)
+			break;
 	}
 
@@ -318,5 +336,5 @@
 	errno_t rc;
 
-	while (true) {
+	while (!quit) {
 		rc = demo_rects(gc, w, h);
 		if (rc != EOK)
@@ -331,4 +349,6 @@
 			return rc;
 	}
+
+	return EOK;
 }
 
@@ -444,5 +464,5 @@
 	}
 
-	rc = display_window_create(display, &window);
+	rc = display_window_create(display, &wnd_cb, NULL, &window);
 	if (rc != EOK) {
 		printf("Error creating window.\n");
@@ -464,4 +484,10 @@
 
 	return EOK;
+}
+
+static void wnd_kbd_event(void *arg, kbd_event_t *event)
+{
+	printf("Keyboard event type=%d key=%d\n", event->type, event->key);
+	quit = true;
 }
 
Index: uspace/lib/display/include/disp_srv.h
===================================================================
--- uspace/lib/display/include/disp_srv.h	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/lib/display/include/disp_srv.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -38,4 +38,5 @@
 #include <async.h>
 #include <errno.h>
+#include <types/display/event.h>
 
 typedef struct display_ops display_ops_t;
@@ -51,7 +52,9 @@
 	errno_t (*window_create)(void *, sysarg_t *);
 	errno_t (*window_destroy)(void *, sysarg_t);
+	errno_t (*get_event)(void *, sysarg_t *, display_wnd_ev_t *);
 };
 
 extern void display_conn(ipc_call_t *, display_srv_t *);
+extern void display_srv_ev_pending(display_srv_t *);
 
 #endif
Index: uspace/lib/display/include/display.h
===================================================================
--- uspace/lib/display/include/display.h	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/lib/display/include/display.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -43,5 +43,6 @@
 extern errno_t display_open(const char *, display_t **);
 extern void display_close(display_t *);
-extern errno_t display_window_create(display_t *, display_window_t **);
+extern errno_t display_window_create(display_t *, display_wnd_cb_t *,
+    void *, display_window_t **);
 extern errno_t display_window_destroy(display_window_t *);
 extern errno_t display_window_get_gc(display_window_t *, gfx_context_t **);
Index: uspace/lib/display/include/display/event.h
===================================================================
--- uspace/lib/display/include/display/event.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
+++ uspace/lib/display/include/display/event.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -0,0 +1,43 @@
+/*
+ * 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 libdisplay
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBDISPLAY_DISPLAY_EVENT_H_
+#define _LIBDISPLAY_DISPLAY_EVENT_H_
+
+#include <types/display/event.h>
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/display/include/ipc/display.h
===================================================================
--- uspace/lib/display/include/ipc/display.h	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/lib/display/include/ipc/display.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -39,8 +39,13 @@
 
 typedef enum {
-	DISPLAY_WINDOW_CREATE = IPC_FIRST_USER_METHOD,
+	DISPLAY_CALLBACK_CREATE = IPC_FIRST_USER_METHOD,
+	DISPLAY_WINDOW_CREATE,
 	DISPLAY_WINDOW_DESTROY,
-	DISPLAY_WINDOW_GC
+	DISPLAY_GET_EVENT
 } display_request_t;
+
+typedef enum {
+	DISPLAY_EV_PENDING = IPC_FIRST_USER_METHOD
+} display_event_t;
 
 #endif
Index: uspace/lib/display/include/types/display.h
===================================================================
--- uspace/lib/display/include/types/display.h	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/lib/display/include/types/display.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -37,4 +37,6 @@
 
 #include <async.h>
+#include <fibril_synch.h>
+#include <io/kbd_event.h>
 #include <ipc/devman.h>
 #include <stdint.h>
@@ -44,5 +46,22 @@
 	/** Session with display server */
 	async_sess_t *sess;
+	/** Synchronize access to display object */
+	fibril_mutex_t lock;
+	/** @c true if callback handler terminated */
+	bool cb_done;
+	/** Signalled when cb_done or ev_pending is changed */
+	fibril_condvar_t cv;
+	/** Windows (of display_window_t) */
+	list_t windows;
 } display_t;
+
+/** Display window callbacks */
+typedef struct {
+	void (*kbd_event)(void *, kbd_event_t *);
+} display_wnd_cb_t;
+
+typedef struct {
+	kbd_event_t kbd_event;
+} display_wnd_ev_t;
 
 /** Display window */
@@ -50,6 +69,12 @@
 	/** Display associated with the window */
 	display_t *display;
+	/** Link to @c display->windows */
+	link_t lwindows;
 	/** Window ID */
 	sysarg_t id;
+	/** Callback functions */
+	display_wnd_cb_t *cb;
+	/** Argument to callback functions */
+	void *cb_arg;
 } display_window_t;
 
Index: uspace/lib/display/include/types/display/event.h
===================================================================
--- uspace/lib/display/include/types/display/event.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
+++ uspace/lib/display/include/types/display/event.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -0,0 +1,47 @@
+/*
+ * 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 libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBDISPLAY_TYPES_DISPLAY_EVENT_H_
+#define _LIBDISPLAY_TYPES_DISPLAY_EVENT_H_
+
+#include <io/kbd_event.h>
+
+typedef struct {
+	kbd_event_t kbd_event;
+} display_wnd_ev_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/display/src/disp_srv.c
===================================================================
--- uspace/lib/display/src/disp_srv.c	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/lib/display/src/disp_srv.c	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -36,8 +36,25 @@
 
 #include <disp_srv.h>
+#include <display/event.h>
 #include <errno.h>
+#include <io/log.h>
 #include <ipc/display.h>
 #include <stdlib.h>
 #include <stddef.h>
+
+#include <stdio.h>
+static void display_callback_create_srv(display_srv_t *srv, ipc_call_t *call)
+{
+	printf("display_callback_create_srv\n");
+
+	async_sess_t *sess = async_callback_receive(EXCHANGE_SERIALIZE);
+	if (sess == NULL) {
+		async_answer_0(call, ENOMEM);
+		return;
+	}
+
+	srv->client_sess = sess;
+	async_answer_0(call, EOK);
+}
 
 static void display_window_create_srv(display_srv_t *srv, ipc_call_t *icall)
@@ -45,4 +62,6 @@
 	sysarg_t wnd_id;
 	errno_t rc;
+
+	printf("display_window_create_srv\n");
 
 	if (srv->ops->window_create == NULL) {
@@ -60,4 +79,6 @@
 	errno_t rc;
 
+	printf("display_window_destroy_srv\n");
+
 	wnd_id = ipc_get_arg1(icall);
 
@@ -71,8 +92,50 @@
 }
 
+static void display_get_event_srv(display_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t wnd_id;
+	display_wnd_ev_t event;
+	ipc_call_t call;
+	size_t size;
+	errno_t rc;
+
+	printf("display_get_event_srv\n");
+
+	if (srv->ops->get_event == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->get_event(srv->arg, &wnd_id, &event);
+	if (rc != EOK)
+		async_answer_0(icall, rc);
+
+	/* Transfer event data */
+	if (!async_data_read_receive(&call, &size)) {
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != sizeof(event)) {
+		async_answer_0(icall, EREFUSED);
+		async_answer_0(&call, EREFUSED);
+		return;
+	}
+
+	rc = async_data_read_finalize(&call, &event, sizeof(event));
+	if (rc != EOK) {
+		async_answer_0(icall, rc);
+		async_answer_0(&call, rc);
+		return;
+	}
+
+	async_answer_1(icall, EOK, wnd_id);
+}
+
 void display_conn(ipc_call_t *icall, display_srv_t *srv)
 {
 	/* Accept the connection */
 	async_accept_0(icall);
+	printf("display_conn\n");
 
 	while (true) {
@@ -88,5 +151,9 @@
 		}
 
+		printf("display_conn method=%lu\n", method);
 		switch (method) {
+		case DISPLAY_CALLBACK_CREATE:
+			display_callback_create_srv(srv, &call);
+			break;
 		case DISPLAY_WINDOW_CREATE:
 			display_window_create_srv(srv, &call);
@@ -94,4 +161,7 @@
 		case DISPLAY_WINDOW_DESTROY:
 			display_window_destroy_srv(srv, &call);
+			break;
+		case DISPLAY_GET_EVENT:
+			display_get_event_srv(srv, &call);
 			break;
 		default:
@@ -101,4 +171,19 @@
 }
 
+/** Send 'pending' event to client.
+ *
+ * @param srv Display server structure
+ */
+void display_srv_ev_pending(display_srv_t *srv)
+{
+	async_exch_t *exch;
+
+	printf("display_srv_ev_pending()\n");
+
+	exch = async_exchange_begin(srv->client_sess);
+	async_msg_0(exch, DISPLAY_EV_PENDING);
+	async_exchange_end(exch);
+}
+
 /** @}
  */
Index: uspace/lib/display/src/display.c
===================================================================
--- uspace/lib/display/src/display.c	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/lib/display/src/display.c	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -30,4 +30,5 @@
 #include <display.h>
 #include <errno.h>
+#include <fibril_synch.h>
 #include <ipc/display.h>
 #include <ipc/services.h>
@@ -36,4 +37,8 @@
 #include <stdlib.h>
 
+static errno_t display_callback_create(display_t *);
+static void display_cb_conn(ipc_call_t *, void *);
+static errno_t display_get_window(display_t *, sysarg_t, display_window_t **);
+
 /** Open display service.
  *
@@ -51,4 +56,6 @@
 	if (display == NULL)
 		return ENOMEM;
+
+	list_initialize(&display->windows);
 
 	if (dsname == NULL)
@@ -68,8 +75,41 @@
 	}
 
+	rc = display_callback_create(display);
+	if (rc != EOK) {
+		async_hangup(display->sess);
+		free(display);
+		return EIO;
+	}
+
 	*rdisplay = display;
 	return EOK;
 }
 
+/** Create callback connection from display service.
+ *
+ * @param display Display session
+ * @return EOK on success or an error code
+ */
+static errno_t display_callback_create(display_t *display)
+{
+	async_exch_t *exch = async_exchange_begin(display->sess);
+
+	aid_t req = async_send_0(exch, DISPLAY_CALLBACK_CREATE, NULL);
+
+	port_id_t port;
+	errno_t rc = async_create_callback_port(exch, INTERFACE_DISPLAY_CB, 0, 0,
+	    display_cb_conn, display, &port);
+
+	async_exchange_end(exch);
+
+	if (rc != EOK)
+		return rc;
+
+	errno_t retval;
+	async_wait_for(req, &retval);
+
+	return retval;
+}
+
 /** Close display service.
  *
@@ -79,4 +119,12 @@
 {
 	async_hangup(display->sess);
+
+	/* Wait for callback handler to terminate */
+
+	fibril_mutex_lock(&display->lock);
+	while (!display->cb_done)
+		fibril_condvar_wait(&display->cv, &display->lock);
+	fibril_mutex_unlock(&display->lock);
+
 	free(display);
 }
@@ -85,8 +133,11 @@
  *
  * @param display Display
+ * @param cb Callback functions
+ * @param cb_arg Argument to callback functions
  * @param rwindow Place to store pointer to new window
  * @return EOK on success or an error code
  */
-errno_t display_window_create(display_t *display, display_window_t **rwindow)
+errno_t display_window_create(display_t *display, display_wnd_cb_t *cb,
+    void *cb_arg, display_window_t **rwindow)
 {
 	display_window_t *window;
@@ -111,4 +162,8 @@
 	window->display = display;
 	window->id = wnd_id;
+	window->cb = cb;
+	window->cb_arg = cb_arg;
+
+	list_append(&window->lwindows, &display->windows);
 	*rwindow = window;
 	return EOK;
@@ -166,4 +221,130 @@
 }
 
+/** Get display event.
+ *
+ * @param display Display
+ * @param rwindow Place to store pointe to window that received event
+ * @param event Place to store event
+ * @return EOK on success or an error code
+ */
+static errno_t display_get_event(display_t *display, display_window_t **rwindow,
+    display_wnd_ev_t *event)
+{
+	async_exch_t *exch;
+	ipc_call_t answer;
+	aid_t req;
+	errno_t rc;
+	sysarg_t wnd_id;
+	display_window_t *window;
+
+	exch = async_exchange_begin(display->sess);
+	req = async_send_0(exch, DISPLAY_GET_EVENT, &answer);
+	rc = async_data_read_start(exch, event, sizeof(*event));
+	if (rc != EOK) {
+		async_forget(req);
+		return rc;
+	}
+
+	async_exchange_end(exch);
+
+	async_wait_for(req, &rc);
+	if (rc != EOK)
+		return rc;
+
+	wnd_id = ipc_get_arg1(&answer);
+	rc = display_get_window(display, wnd_id, &window);
+	if (rc != EOK)
+		return EIO;
+
+	*rwindow = window;
+	return EOK;
+}
+
+/** Display events are pending.
+ *
+ * @param display Display
+ * @param icall Call data
+ */
+static void display_ev_pending(display_t *display, ipc_call_t *icall)
+{
+	errno_t rc;
+	display_window_t *window = NULL;
+	display_wnd_ev_t event;
+
+	while (true) {
+		rc = display_get_event(display, &window, &event);
+		if (rc != EOK)
+			break;
+
+		if (window->cb->kbd_event != NULL)
+			window->cb->kbd_event(window->cb_arg, &event.kbd_event);
+	}
+
+	async_answer_0(icall, EOK);
+}
+
+/** Callback connection handler.
+ *
+ * @param icall Connect call data
+ * @param arg   Argument, display_t *
+ */
+static void display_cb_conn(ipc_call_t *icall, void *arg)
+{
+	display_t *display = (display_t *) arg;
+
+	while (true) {
+		ipc_call_t call;
+		async_get_call(&call);
+
+		if (!ipc_get_imethod(&call)) {
+			/* Hangup */
+			async_answer_0(&call, EOK);
+			goto out;
+		}
+
+		switch (ipc_get_imethod(&call)) {
+		case DISPLAY_EV_PENDING:
+			display_ev_pending(display, &call);
+			break;
+		default:
+			async_answer_0(&call, ENOTSUP);
+			break;
+		}
+	}
+
+out:
+	fibril_mutex_lock(&display->lock);
+	display->cb_done = true;
+	fibril_mutex_unlock(&display->lock);
+	fibril_condvar_broadcast(&display->cv);
+}
+
+/** Find window by ID.
+ *
+ * @param display Display
+ * @param wnd_id Window ID
+ * @param rwindow Place to store pointer to window
+ * @return EOK on success, ENOENT if not found
+ */
+static errno_t display_get_window(display_t *display, sysarg_t wnd_id,
+    display_window_t **rwindow)
+{
+	link_t *link;
+	display_window_t *window;
+
+	link = list_first(&display->windows);
+	while (link != NULL) {
+		window = list_get_instance(link, display_window_t, lwindows);
+		if (window->id == wnd_id) {
+			*rwindow = window;
+			return EOK;
+		}
+
+		link = list_next(link, &display->windows);
+	}
+
+	return ENOENT;
+}
+
 /** @}
  */
Index: uspace/srv/hid/display/client.c
===================================================================
--- uspace/srv/hid/display/client.c	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
+++ uspace/srv/hid/display/client.c	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -0,0 +1,203 @@
+/*
+ * 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 client
+ */
+
+#include <disp_srv.h>
+#include <errno.h>
+#include <stdlib.h>
+#include "client.h"
+#include "display.h"
+#include "window.h"
+
+/** Create client.
+ *
+ * @param display Parent display
+ * @param rclient Place to store pointer to new client.
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ds_client_create(ds_display_t *display, display_srv_t *srv,
+    ds_client_t **rclient)
+{
+	ds_client_t *client;
+
+	client = calloc(1, sizeof(ds_client_t));
+	if (client == NULL)
+		return ENOMEM;
+
+	list_initialize(&client->windows);
+	prodcons_initialize(&client->events);
+	client->srv = srv;
+
+	ds_display_add_client(display, client);
+
+	*rclient = client;
+	return EOK;
+}
+
+/** Destroy client.
+ *
+ * @param client Client
+ */
+void ds_client_destroy(ds_client_t *client)
+{
+	assert(list_empty(&client->windows));
+	ds_display_remove_client(client);
+	free(client);
+}
+
+/** Add window to client.
+ *
+ * @param client client
+ * @param wnd Window
+ * @return EOK on success, ENOMEM if there are no free window identifiers
+ */
+errno_t ds_client_add_window(ds_client_t *client, ds_window_t *wnd)
+{
+	assert(wnd->client == NULL);
+	assert(!link_used(&wnd->lwindows));
+
+	wnd->client = client;
+	wnd->id = client->display->next_wnd_id++;
+	list_append(&wnd->lwindows, &client->windows);
+
+	return EOK;
+}
+
+/** Remove window from client.
+ *
+ * @param wnd Window
+ */
+void ds_client_remove_window(ds_window_t *wnd)
+{
+	list_remove(&wnd->lwindows);
+	wnd->client = NULL;
+}
+
+/** Find window by ID.
+ *
+ * @param client Client
+ * @param id Window ID
+ */
+#include <stdio.h>
+ds_window_t *ds_client_find_window(ds_client_t *client, ds_wnd_id_t id)
+{
+	ds_window_t *wnd;
+
+	// TODO Make this faster
+	printf("ds_client_find_window: id=0x%lx\n", id);
+	wnd = ds_client_first_window(client);
+	while (wnd != NULL) {
+		printf("ds_client_find_window: wnd=%p wnd->id=0x%lx\n", wnd, wnd->id);
+		if (wnd->id == id)
+			return wnd;
+		wnd = ds_client_next_window(wnd);
+	}
+
+	return NULL;
+}
+
+/** Get first window in client.
+ *
+ * @param client Client
+ * @return First window or @c NULL if there is none
+ */
+ds_window_t *ds_client_first_window(ds_client_t *client)
+{
+	link_t *link = list_first(&client->windows);
+
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ds_window_t, lwindows);
+}
+
+/** Get next window in client.
+ *
+ * @param wnd Current window
+ * @return Next window or @c NULL if there is none
+ */
+ds_window_t *ds_client_next_window(ds_window_t *wnd)
+{
+	link_t *link = list_next(&wnd->lwindows, &wnd->client->windows);
+
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ds_window_t, lwindows);
+}
+
+/** Get next event from client event queue.
+ *
+ * This function blocks until an event is available.
+ *
+ * @param wnd Window
+ * @return Graphic context
+ */
+errno_t ds_client_get_event(ds_client_t *client, ds_window_t **ewindow,
+    display_wnd_ev_t *event)
+{
+	link_t *link;
+	ds_window_ev_t *wevent;
+
+	link = prodcons_consume(&client->events);
+	wevent = list_get_instance(link, ds_window_ev_t, levents);
+
+	*ewindow = wevent->window;
+	*event = wevent->event;
+	free(wevent);
+	return EOK;
+}
+
+errno_t ds_client_post_kbd_event(ds_client_t *client, ds_window_t *ewindow,
+    kbd_event_t *event)
+{
+	ds_window_ev_t *wevent;
+
+	wevent = calloc(1, sizeof(ds_window_ev_t));
+	if (wevent == NULL)
+		return ENOMEM;
+
+	wevent->window = ewindow;
+	wevent->event.kbd_event = *event;
+	prodcons_produce(&client->events, &wevent->levents);
+
+	/* Notify the client */
+	// TODO Do not send more than once until client drains the queue
+	display_srv_ev_pending(client->srv);
+
+	return EOK;
+}
+
+/** @}
+ */
Index: uspace/srv/hid/display/client.h
===================================================================
--- uspace/srv/hid/display/client.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
+++ uspace/srv/hid/display/client.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -0,0 +1,58 @@
+/*
+ * 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 client
+ */
+
+#ifndef CLIENT_H
+#define CLIENT_H
+
+#include <disp_srv.h>
+#include "types/display/client.h"
+#include "types/display/display.h"
+
+extern errno_t ds_client_create(ds_display_t *, display_srv_t *, ds_client_t **);
+extern void ds_client_destroy(ds_client_t *);
+extern errno_t ds_client_add_window(ds_client_t *, ds_window_t *);
+extern void ds_client_remove_window(ds_window_t *);
+extern ds_window_t *ds_client_find_window(ds_client_t *, ds_wnd_id_t);
+extern ds_window_t *ds_client_first_window(ds_client_t *);
+extern ds_window_t *ds_client_next_window(ds_window_t *);
+extern errno_t ds_client_get_event(ds_client_t *, ds_window_t **,
+    display_wnd_ev_t *);
+extern errno_t ds_client_post_kbd_event(ds_client_t *, ds_window_t *,
+    kbd_event_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/hid/display/display.c
===================================================================
--- uspace/srv/hid/display/display.c	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/srv/hid/display/display.c	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -39,13 +39,16 @@
 #include <io/log.h>
 #include <stdlib.h>
+#include "client.h"
+#include "window.h"
 #include "display.h"
-#include "window.h"
 
 static errno_t disp_window_create(void *, sysarg_t *);
 static errno_t disp_window_destroy(void *, sysarg_t);
+static errno_t disp_get_event(void *, sysarg_t *, display_wnd_ev_t *);
 
 display_ops_t display_srv_ops = {
 	.window_create = disp_window_create,
-	.window_destroy = disp_window_destroy
+	.window_destroy = disp_window_destroy,
+	.get_event = disp_get_event
 };
 
@@ -53,10 +56,10 @@
 {
 	errno_t rc;
-	ds_display_t *disp = (ds_display_t *) arg;
+	ds_client_t *client = (ds_client_t *) arg;
 	ds_window_t *wnd;
 
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "disp_window_create()");
 
-	rc = ds_window_create(disp, &wnd);
+	rc = ds_window_create(client, &wnd);
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "disp_window_create() - ds_window_create -> %d", rc);
 	if (rc != EOK)
@@ -75,14 +78,31 @@
 static errno_t disp_window_destroy(void *arg, sysarg_t wnd_id)
 {
-	ds_display_t *disp = (ds_display_t *) arg;
-	ds_window_t *wnd;
-
-	wnd = ds_display_find_window(disp, wnd_id);
+	ds_client_t *client = (ds_client_t *) arg;
+	ds_window_t *wnd;
+
+	wnd = ds_client_find_window(client, wnd_id);
 	if (wnd == NULL)
 		return ENOENT;
 
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "disp_window_destroy()");
-	ds_display_remove_window(wnd);
+	ds_client_remove_window(wnd);
 	ds_window_delete(wnd);
+	return EOK;
+}
+
+static errno_t disp_get_event(void *arg, sysarg_t *wnd_id,
+    display_wnd_ev_t *event)
+{
+	ds_client_t *client = (ds_client_t *) arg;
+	ds_window_t *wnd;
+	errno_t rc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "disp_window_get_event()");
+
+	rc = ds_client_get_event(client, &wnd, event);
+	if (rc != EOK)
+		return rc;
+
+	*wnd_id = wnd->id;
 	return EOK;
 }
@@ -102,7 +122,7 @@
 		return ENOMEM;
 
-	list_initialize(&disp->windows);
+	list_initialize(&disp->clients);
+	disp->gc = gc;
 	disp->next_wnd_id = 1;
-	disp->gc = gc;
 	*rdisp = disp;
 	return EOK;
@@ -115,84 +135,111 @@
 void ds_display_destroy(ds_display_t *disp)
 {
-	assert(list_empty(&disp->windows));
+	assert(list_empty(&disp->clients));
 	free(disp);
 }
 
-/** Add window to display.
+/** Add client to display.
  *
  * @param disp Display
- * @param wnd Window
- * @return EOK on success, ENOMEM if there are no free window identifiers
- */
-errno_t ds_display_add_window(ds_display_t *disp, ds_window_t *wnd)
-{
-	assert(wnd->display == NULL);
-	assert(!link_used(&wnd->lwindows));
-
-	wnd->display = disp;
-	wnd->id = disp->next_wnd_id++;
-	list_append(&wnd->lwindows, &disp->windows);
-
-	return EOK;
-}
-
-/** Remove window from display.
- *
- * @param wnd Window
- */
-void ds_display_remove_window(ds_window_t *wnd)
-{
-	list_remove(&wnd->lwindows);
-	wnd->display = NULL;
-}
-
-/** Find window by ID.
+ * @param client client
+ */
+void ds_display_add_client(ds_display_t *disp, ds_client_t *client)
+{
+	assert(client->display == NULL);
+	assert(!link_used(&client->lclients));
+
+	client->display = disp;
+	list_append(&client->lclients, &disp->clients);
+}
+
+/** Remove client from display.
+ *
+ * @param client client
+ */
+void ds_display_remove_client(ds_client_t *client)
+{
+	list_remove(&client->lclients);
+	client->display = NULL;
+}
+
+/** Get first client in display.
  *
  * @param disp Display
- * @param id Window ID
- */
-ds_window_t *ds_display_find_window(ds_display_t *disp, ds_wnd_id_t id)
-{
-	ds_window_t *wnd;
-
-	// TODO Make this faster
-	wnd = ds_display_first_window(disp);
-	while (wnd != NULL) {
-		if (wnd->id == id)
-			return wnd;
-		wnd = ds_display_next_window(wnd);
-	}
-
-	return NULL;
-}
-
-/** Get first window in display.
- *
- * @param disp Display
- * @return First window or @c NULL if there is none
- */
-ds_window_t *ds_display_first_window(ds_display_t *disp)
-{
-	link_t *link = list_first(&disp->windows);
+ * @return First client or @c NULL if there is none
+ */
+ds_client_t *ds_display_first_client(ds_display_t *disp)
+{
+	link_t *link = list_first(&disp->clients);
 
 	if (link == NULL)
 		return NULL;
 
-	return list_get_instance(link, ds_window_t, lwindows);
-}
-
-/** Get next window in display.
- *
- * @param wnd Current window
- * @return Next window or @c NULL if there is none
- */
-ds_window_t *ds_display_next_window(ds_window_t *wnd)
-{
-	link_t *link = list_next(&wnd->lwindows, &wnd->display->windows);
+	return list_get_instance(link, ds_client_t, lclients);
+}
+
+/** Get next client in display.
+ *
+ * @param client Current client
+ * @return Next client or @c NULL if there is none
+ */
+ds_client_t *ds_display_next_client(ds_client_t *client)
+{
+	link_t *link = list_next(&client->lclients, &client->display->clients);
 
 	if (link == NULL)
 		return NULL;
 
-	return list_get_instance(link, ds_window_t, lwindows);
+	return list_get_instance(link, ds_client_t, lclients);
+}
+
+/** Find window in all clients by ID.
+ *
+ * XXX This is just a hack needed to match GC connection to a window,
+ * as we don't have a good safe way to pass the GC endpoint to our client
+ * on demand.
+ *
+ * @param display Display
+ * @param id Window ID
+ */
+#include <stdio.h>
+ds_window_t *ds_display_find_window(ds_display_t *display, ds_wnd_id_t id)
+{
+	ds_client_t *client;
+	ds_window_t *wnd;
+
+	printf("ds_display_find_window: id=0x%lx\n", id);
+
+	client = ds_display_first_client(display);
+	while (client != NULL) {
+		printf("ds_display_find_window: client=%p\n", client);
+		wnd = ds_client_find_window(client, id);
+		if (wnd != NULL) {
+			printf("ds_display_find_window: found wnd=%p id=0x%lx\n",
+			    wnd, wnd->id);
+			return wnd;
+		}
+		client = ds_display_next_client(client);
+	}
+
+	printf("ds_display_find_window: not found\n");
+	return NULL;
+}
+
+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)
+		return EOK;
+
+	wnd = ds_client_first_window(client);
+	if (wnd == NULL)
+		return EOK;
+
+	return ds_client_post_kbd_event(client, wnd, event);
 }
 
Index: uspace/srv/hid/display/display.h
===================================================================
--- uspace/srv/hid/display/display.h	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/srv/hid/display/display.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -41,6 +41,7 @@
 #include <errno.h>
 #include <gfx/context.h>
+#include <io/kbd_event.h>
+#include "types/display/client.h"
 #include "types/display/display.h"
-#include "types/display/window.h"
 
 extern display_ops_t display_srv_ops;
@@ -48,9 +49,10 @@
 extern errno_t ds_display_create(gfx_context_t *, ds_display_t **);
 extern void ds_display_destroy(ds_display_t *);
-extern errno_t ds_display_add_window(ds_display_t *, ds_window_t *);
-extern void ds_display_remove_window(ds_window_t *);
+extern void ds_display_add_client(ds_display_t *, ds_client_t *);
+extern void ds_display_remove_client(ds_client_t *);
+extern ds_client_t *ds_display_first_client(ds_display_t *);
+extern ds_client_t *ds_display_next_client(ds_client_t *);
 extern ds_window_t *ds_display_find_window(ds_display_t *, ds_wnd_id_t);
-extern ds_window_t *ds_display_first_window(ds_display_t *);
-extern ds_window_t *ds_display_next_window(ds_window_t *);
+extern errno_t ds_display_post_kbd_event(ds_display_t *, kbd_event_t *);
 
 #endif
Index: uspace/srv/hid/display/main.c
===================================================================
--- uspace/srv/hid/display/main.c	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/srv/hid/display/main.c	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -45,11 +45,19 @@
 #include <stdio.h>
 #include <task.h>
+#include "client.h"
 #include "display.h"
+#include "main.h"
 #include "output.h"
 #include "window.h"
 
-#define NAME  "display"
+static void display_client_conn(ipc_call_t *, void *);
 
-static void display_client_conn(ipc_call_t *, void *);
+static void display_kbd_event(void *arg, kbd_event_t *event)
+{
+	ds_display_t *disp = (ds_display_t *) arg;
+
+	printf("display_kbd_event\n");
+	ds_display_post_kbd_event(disp, event);
+}
 
 /** Initialize display server */
@@ -60,14 +68,20 @@
 	errno_t rc;
 
-	rc = output_init(&gc);
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "display_srv_init()");
+
+	rc = ds_display_create(NULL, &disp);
 	if (rc != EOK)
 		goto error;
 
-	rc = ds_display_create(gc, &disp);
+	rc = output_init(display_kbd_event, (void *) disp, &gc);
 	if (rc != EOK)
 		goto error;
 
-	log_msg(LOG_DEFAULT, LVL_DEBUG, "display_srv_init()");
-
+	disp->gc = gc;
+#if 0
+	rc = ds_input_open(disp);
+	if (rc != EOK)
+		goto error;
+#endif
 	async_set_fallback_port_handler(display_client_conn, disp);
 
@@ -88,4 +102,8 @@
 	return EOK;
 error:
+#if 0
+	if (disp->input != NULL)
+		ds_input_close(disp);
+#endif
 	if (gc != NULL)
 		gfx_context_delete(gc);
@@ -101,7 +119,9 @@
 	sysarg_t wnd_id;
 	sysarg_t svc_id;
+	ds_client_t *client = NULL;
 	ds_window_t *wnd;
 	ds_display_t *disp = (ds_display_t *) arg;
 	gfx_context_t *gc;
+	errno_t rc;
 
 	log_msg(LOG_DEFAULT, LVL_NOTE, "display_client_conn arg1=%zu arg2=%zu arg3=%zu arg4=%zu.",
@@ -116,11 +136,22 @@
 
 	if (svc_id != 0) {
-		/* Display management */
+		/* Create client object */
+		rc = ds_client_create(disp, &srv, &client);
+		if (rc != EOK) {
+			async_answer_0(icall, ENOMEM);
+			return;
+		}
+
+		/* Set up protocol structure */
 		srv.ops = &display_srv_ops;
-		srv.arg = disp;
+		srv.arg = client;
 
+		/* Handle connection */
 		display_conn(icall, &srv);
+
+		ds_client_destroy(client);
 	} else {
-		/* Window GC */
+		/* Window GC connection */
+
 		wnd = ds_display_find_window(disp, wnd_id);
 		if (wnd == NULL) {
@@ -132,4 +163,5 @@
 		gc_conn(icall, gc);
 	}
+
 }
 
Index: uspace/srv/hid/display/main.h
===================================================================
--- uspace/srv/hid/display/main.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
+++ uspace/srv/hid/display/main.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -0,0 +1,43 @@
+/*
+ * 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 main
+ */
+
+#ifndef MAIN_H
+#define MAIN_H
+
+#define NAME "display"
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/hid/display/meson.build
===================================================================
--- uspace/srv/hid/display/meson.build	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/srv/hid/display/meson.build	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -30,4 +30,5 @@
 
 src = files(
+	'client.c',
 	'display.c',
 	'main.c',
@@ -37,4 +38,5 @@
 
 test_src = files(
+	'client.c',
 	'display.c',
 	'window.c',
Index: uspace/srv/hid/display/output.c
===================================================================
--- uspace/srv/hid/display/output.c	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/srv/hid/display/output.c	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -42,5 +42,15 @@
 #include "output.h"
 
-errno_t output_init(gfx_context_t **rgc)
+static void (*kbd_ev_handler)(void *, kbd_event_t *);
+static void *kbd_ev_arg;
+
+static void on_keyboard_event(widget_t *widget, void *data)
+{
+	printf("Keyboard event\n");
+	kbd_ev_handler(kbd_ev_arg, (kbd_event_t *) data);
+}
+
+errno_t output_init(void (*kbd_event_handler)(void *, kbd_event_t *),
+    void *arg, gfx_context_t **rgc)
 {
 	canvas_gc_t *cgc = NULL;
@@ -53,4 +63,6 @@
 
 	printf("Init canvas..\n");
+	kbd_ev_handler = kbd_event_handler;
+	kbd_ev_arg = arg;
 
 	window = window_open("comp:0/winreg", NULL,
@@ -83,4 +95,6 @@
 	}
 
+	sig_connect(&canvas->keyboard_event, NULL, on_keyboard_event);
+
 	window_resize(window, 0, 0, vw + 10, vh + 30, WINDOW_PLACEMENT_ANY);
 	window_exec(window);
Index: uspace/srv/hid/display/output.h
===================================================================
--- uspace/srv/hid/display/output.h	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/srv/hid/display/output.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -39,5 +39,6 @@
 #include <gfx/context.h>
 
-extern errno_t output_init(gfx_context_t **);
+extern errno_t output_init(void (*)(void *, kbd_event_t *), void *,
+    gfx_context_t **);
 
 #endif
Index: uspace/srv/hid/display/test/display.c
===================================================================
--- uspace/srv/hid/display/test/display.c	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/srv/hid/display/test/display.c	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -32,6 +32,6 @@
 #include <str.h>
 
+#include "../client.h"
 #include "../display.h"
-#include "../window.h"
 
 PCUT_INIT;
@@ -51,10 +51,10 @@
 }
 
-/** Basic window operation. */
-PCUT_TEST(display_window)
+/** Basic client operation. */
+PCUT_TEST(display_client)
 {
 	ds_display_t *disp;
-	ds_window_t *wnd;
-	ds_window_t *w0, *w1, *w2;
+	ds_client_t *client;
+	ds_client_t *c0, *c1;
 	errno_t rc;
 
@@ -62,17 +62,14 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_window_create(disp, &wnd);
+	rc = ds_client_create(disp, NULL, &client);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	w0 = ds_display_first_window(disp);
-	PCUT_ASSERT_EQUALS(w0, wnd);
+	c0 = ds_display_first_client(disp);
+	PCUT_ASSERT_EQUALS(c0, client);
 
-	w1 = ds_display_next_window(w0);
-	PCUT_ASSERT_NULL(w1);
+	c1 = ds_display_next_client(c0);
+	PCUT_ASSERT_NULL(c1);
 
-	w2 = ds_display_find_window(disp, wnd->id);
-	PCUT_ASSERT_EQUALS(w2, wnd);
-
-	ds_window_delete(wnd);
+	ds_client_destroy(client);
 	ds_display_destroy(disp);
 }
Index: uspace/srv/hid/display/test/window.c
===================================================================
--- uspace/srv/hid/display/test/window.c	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/srv/hid/display/test/window.c	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -32,5 +32,5 @@
 #include <str.h>
 
-#include "../display.h"
+#include "../client.h"
 #include "../window.h"
 
@@ -42,13 +42,13 @@
 PCUT_TEST(window_get_ctx)
 {
-	ds_display_t *disp;
+	ds_client_t *client;
 	ds_window_t *wnd;
 	gfx_context_t *gc;
 	errno_t rc;
 
-	rc = ds_display_create(NULL, &disp);
+	rc = ds_client_create(NULL, NULL, &client);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_window_create(disp, &wnd);
+	rc = ds_window_create(client, &wnd);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -57,5 +57,5 @@
 
 	ds_window_delete(wnd);
-	ds_display_destroy(disp);
+	ds_client_destroy(client);
 }
 
Index: uspace/srv/hid/display/types/display/client.h
===================================================================
--- uspace/srv/hid/display/types/display/client.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
+++ uspace/srv/hid/display/types/display/client.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -0,0 +1,62 @@
+/*
+ * 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 client type
+ */
+
+#ifndef TYPES_DISPLAY_CLIENT_H
+#define TYPES_DISPLAY_CLIENT_H
+
+#include <adt/list.h>
+#include <adt/prodcons.h>
+#include <disp_srv.h>
+
+typedef sysarg_t ds_wnd_id_t;
+
+/** Display server client */
+typedef struct ds_client {
+	/** Parent display */
+	struct ds_display *display;
+	/** Display protocol per-connection structure */
+	display_srv_t *srv;
+	/** Link to @c display->clients */
+	link_t lclients;
+	/** Windows (of ds_window_t) */
+	list_t windows;
+	/** Event queue (of ds_window_ev_t) */
+	prodcons_t events;
+} ds_client_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/hid/display/types/display/display.h
===================================================================
--- uspace/srv/hid/display/types/display/display.h	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/srv/hid/display/types/display/display.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -27,5 +27,5 @@
  */
 
-/** @addtogroup libipcgfx
+/** @addtogroup display
  * @{
  */
@@ -39,13 +39,24 @@
 #include <adt/list.h>
 #include <gfx/context.h>
+#include <io/input.h>
 #include "window.h"
 
+/** Display server display */
 typedef struct ds_display {
-	/** Windows (of ds_window_t) */
-	list_t windows;
-	/** Next ID to assign to a window */
-	ds_wnd_id_t next_wnd_id;
+	/** Clients (of ds_client_t) */
+	list_t clients;
 	/** Output GC */
 	gfx_context_t *gc;
+
+	/** Next ID to assign to a window.
+	 *
+	 * XXX Window IDs need to be unique per display just because
+	 * we don't have a way to match GC connection to the proper
+	 * client. Really this should be in ds_client_t and the ID
+	 * space should be per client.
+	 */
+	ds_wnd_id_t next_wnd_id;
+	/** Input service */
+	input_t *input;
 } ds_display_t;
 
Index: uspace/srv/hid/display/types/display/window.h
===================================================================
--- uspace/srv/hid/display/types/display/window.h	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/srv/hid/display/types/display/window.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -27,5 +27,5 @@
  */
 
-/** @addtogroup libipcgfx
+/** @addtogroup display
  * @{
  */
@@ -38,4 +38,5 @@
 
 #include <adt/list.h>
+#include <display/event.h>
 #include <gfx/context.h>
 #include <gfx/coord.h>
@@ -45,7 +46,7 @@
 /** Display server window */
 typedef struct ds_window {
-	/** Parent display */
-	struct ds_display *display;
-	/** Link to @c display->windows */
+	/** Parent client */
+	struct ds_client *client;
+	/** Link to @c client->windows */
 	link_t lwindows;
 	/** Display position */
@@ -56,4 +57,14 @@
 	gfx_context_t *gc;
 } ds_window_t;
+
+/** Window event queue entry */
+typedef struct {
+	/** Link to event queue */
+	link_t levents;
+	/** Window to which the event is delivered */
+	ds_window_t *window;
+	/** Event */
+	display_wnd_ev_t event;
+} ds_window_ev_t;
 
 /** Bitmap in display server window GC */
Index: uspace/srv/hid/display/window.c
===================================================================
--- uspace/srv/hid/display/window.c	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/srv/hid/display/window.c	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -43,4 +43,5 @@
 #include <io/log.h>
 #include <stdlib.h>
+#include "client.h"
 #include "display.h"
 #include "window.h"
@@ -76,6 +77,6 @@
 	ds_window_t *wnd = (ds_window_t *) arg;
 
-	log_msg(LOG_DEFAULT, LVL_NOTE, "gc_set_color");
-	return gfx_set_color(wnd->display->gc, color);
+	log_msg(LOG_DEFAULT, LVL_NOTE, "gc_set_color gc=%p", wnd->client->display->gc);
+	return gfx_set_color(wnd->client->display->gc, color);
 }
 
@@ -94,5 +95,5 @@
 	log_msg(LOG_DEFAULT, LVL_NOTE, "gc_fill_rect");
 	gfx_rect_translate(&wnd->dpos, rect, &drect);
-	return gfx_fill_rect(wnd->display->gc, &drect);
+	return gfx_fill_rect(wnd->client->display->gc, &drect);
 }
 
@@ -116,5 +117,6 @@
 		return ENOMEM;
 
-	rc = gfx_bitmap_create(wnd->display->gc, params, alloc, &cbm->bitmap);
+	rc = gfx_bitmap_create(wnd->client->display->gc, params, alloc,
+	    &cbm->bitmap);
 	if (rc != EOK)
 		goto error;
@@ -181,10 +183,10 @@
  * Create graphics context for rendering into a window.
  *
- * @param disp Display to create window on
+ * @param client Client owning the window
  * @param rgc Place to store pointer to new GC.
  *
  * @return EOK on success or an error code
  */
-errno_t ds_window_create(ds_display_t *disp, ds_window_t **rgc)
+errno_t ds_window_create(ds_client_t *client, ds_window_t **rgc)
 {
 	ds_window_t *wnd = NULL;
@@ -202,5 +204,5 @@
 		goto error;
 
-	ds_display_add_window(disp, wnd);
+	ds_client_add_window(client, wnd);
 
 	wnd->gc = gc;
@@ -222,5 +224,5 @@
 	errno_t rc;
 
-	ds_display_remove_window(wnd);
+	ds_client_remove_window(wnd);
 
 	rc = gfx_context_delete(wnd->gc);
Index: uspace/srv/hid/display/window.h
===================================================================
--- uspace/srv/hid/display/window.h	(revision 22faaf294cff1ff8a344bf9bb31edc17a6e83f3d)
+++ uspace/srv/hid/display/window.h	(revision b3c185b62f0fa507e9d72bce104161ccdb4b3d57)
@@ -37,4 +37,5 @@
 #define WINDOW_H
 
+#include <display/event.h>
 #include <errno.h>
 #include <types/gfx/context.h>
@@ -45,5 +46,5 @@
 extern gfx_context_ops_t window_gc_ops;
 
-extern errno_t ds_window_create(ds_display_t *, ds_window_t **);
+extern errno_t ds_window_create(ds_client_t *, ds_window_t **);
 extern errno_t ds_window_delete(ds_window_t *);
 extern gfx_context_t *ds_window_get_ctx(ds_window_t *);
