Index: uspace/app/taskbar/taskbar.c
===================================================================
--- uspace/app/taskbar/taskbar.c	(revision 7cc30e98a2b4441f4a5ddaa86d0a54c7ac71fab8)
+++ uspace/app/taskbar/taskbar.c	(revision 913add60dd2ff444e319a0f402067877d08cb574)
@@ -53,4 +53,12 @@
 };
 
+static void taskbar_wm_window_added(void *, sysarg_t);
+static void taskbar_wm_window_removed(void *, sysarg_t);
+
+static wndmgt_cb_t taskbar_wndmgt_cb = {
+	.window_added = taskbar_wm_window_added,
+	.window_removed = taskbar_wm_window_removed
+};
+
 /** Window close button was clicked.
  *
@@ -89,5 +97,6 @@
 
 	if (wndmgt_svc != NULL) {
-		rc = wndmgt_open(wndmgt_svc, NULL, NULL, &taskbar->wndmgt);
+		rc = wndmgt_open(wndmgt_svc, &taskbar_wndmgt_cb,
+		    (void *)taskbar, &taskbar->wndmgt);
 		if (rc != EOK)
 			goto error;
@@ -241,4 +250,30 @@
 }
 
+/** Handle WM window added event.
+ *
+ * @param arg Argument (taskbar_t *)
+ * @param wnd_id Window ID
+ */
+static void taskbar_wm_window_added(void *arg, sysarg_t wnd_id)
+{
+	taskbar_t *taskbar = (taskbar_t *)arg;
+
+	printf("wm_window_added: taskbar=%p wnd_id=%zu\n",
+	    (void *)taskbar, wnd_id);
+}
+
+/** Handle WM window removed event.
+ *
+ * @param arg Argument (taskbar_t *)
+ * @param wnd_id Window ID
+ */
+static void taskbar_wm_window_removed(void *arg, sysarg_t wnd_id)
+{
+	taskbar_t *taskbar = (taskbar_t *)arg;
+
+	printf("wm_window_removed: taskbar=%p wnd_id=%zu\n",
+	    (void *)taskbar, wnd_id);
+}
+
 /** @}
  */
Index: uspace/srv/hid/display/client.c
===================================================================
--- uspace/srv/hid/display/client.c	(revision 7cc30e98a2b4441f4a5ddaa86d0a54c7ac71fab8)
+++ uspace/srv/hid/display/client.c	(revision 913add60dd2ff444e319a0f402067877d08cb574)
@@ -181,5 +181,5 @@
  * @param ewindow Place to store pointer to window receiving the event
  * @param event Place to store event
- * @return Graphic context
+ * @return EOK on success, ENOENT if event queue is empty
  */
 errno_t ds_client_get_event(ds_client_t *client, ds_window_t **ewindow,
Index: uspace/srv/hid/display/display.c
===================================================================
--- uspace/srv/hid/display/display.c	(revision 7cc30e98a2b4441f4a5ddaa86d0a54c7ac71fab8)
+++ uspace/srv/hid/display/display.c	(revision 913add60dd2ff444e319a0f402067877d08cb574)
@@ -45,7 +45,8 @@
 #include "cursimg.h"
 #include "cursor.h"
+#include "display.h"
 #include "seat.h"
 #include "window.h"
-#include "display.h"
+#include "wmclient.h"
 
 static gfx_context_t *ds_display_get_unbuf_gc(ds_display_t *);
@@ -96,4 +97,5 @@
 	fibril_mutex_initialize(&disp->lock);
 	list_initialize(&disp->clients);
+	list_initialize(&disp->wmclients);
 	disp->next_wnd_id = 1;
 	list_initialize(&disp->ddevs);
@@ -115,4 +117,5 @@
 {
 	assert(list_empty(&disp->clients));
+	assert(list_empty(&disp->wmclients));
 	assert(list_empty(&disp->seats));
 	/* XXX destroy cursors */
@@ -203,4 +206,59 @@
 
 	return list_get_instance(link, ds_client_t, lclients);
+}
+
+/** Add WM client to display.
+ *
+ * @param disp Display
+ * @param wmclient WM client
+ */
+void ds_display_add_wmclient(ds_display_t *disp, ds_wmclient_t *wmclient)
+{
+	assert(wmclient->display == NULL);
+	assert(!link_used(&wmclient->lwmclients));
+
+	wmclient->display = disp;
+	list_append(&wmclient->lwmclients, &disp->wmclients);
+}
+
+/** Remove WM client from display.
+ *
+ * @param wmclient WM client
+ */
+void ds_display_remove_wmclient(ds_wmclient_t *wmclient)
+{
+	list_remove(&wmclient->lwmclients);
+	wmclient->display = NULL;
+}
+
+/** Get first WM client in display.
+ *
+ * @param disp Display
+ * @return First WM client or @c NULL if there is none
+ */
+ds_wmclient_t *ds_display_first_wmclient(ds_display_t *disp)
+{
+	link_t *link = list_first(&disp->wmclients);
+
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ds_wmclient_t, lwmclients);
+}
+
+/** Get next WM client in display.
+ *
+ * @param wmclient Current WM client
+ * @return Next WM client or @c NULL if there is none
+ */
+ds_wmclient_t *ds_display_next_wmclient(ds_wmclient_t *wmclient)
+{
+	link_t *link = list_next(&wmclient->lwmclients,
+	    &wmclient->display->wmclients);
+
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ds_wmclient_t, lwmclients);
 }
 
@@ -262,4 +320,6 @@
 void ds_display_add_window(ds_display_t *display, ds_window_t *wnd)
 {
+	ds_wmclient_t *wmclient;
+
 	assert(wnd->display == NULL);
 	assert(!link_used(&wnd->ldwindows));
@@ -267,4 +327,11 @@
 	wnd->display = display;
 	list_prepend(&wnd->ldwindows, &display->windows);
+
+	/* Notify window managers about the new window */
+	wmclient = ds_display_first_wmclient(display);
+	while (wmclient != NULL) {
+		ds_wmclient_post_wnd_added_event(wmclient, wnd->id);
+		wmclient = ds_display_next_wmclient(wmclient);
+	}
 }
 
@@ -275,6 +342,32 @@
 void ds_display_remove_window(ds_window_t *wnd)
 {
+	ds_wmclient_t *wmclient;
+	ds_display_t *display;
+
+	display = wnd->display;
+
 	list_remove(&wnd->ldwindows);
 	wnd->display = NULL;
+
+	/* Notify window managers about the removed window */
+	wmclient = ds_display_first_wmclient(display);
+	while (wmclient != NULL) {
+		ds_wmclient_post_wnd_removed_event(wmclient, wnd->id);
+		wmclient = ds_display_next_wmclient(wmclient);
+	}
+}
+
+/** Move window to top.
+ *
+ * @param display Display
+ * @param wnd Window
+ */
+void ds_display_window_to_top(ds_window_t *wnd)
+{
+	assert(wnd->display != NULL);
+	assert(link_used(&wnd->ldwindows));
+
+	list_remove(&wnd->ldwindows);
+	list_prepend(&wnd->ldwindows, &wnd->display->windows);
 }
 
Index: uspace/srv/hid/display/display.h
===================================================================
--- uspace/srv/hid/display/display.h	(revision 7cc30e98a2b4441f4a5ddaa86d0a54c7ac71fab8)
+++ uspace/srv/hid/display/display.h	(revision 913add60dd2ff444e319a0f402067877d08cb574)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -48,4 +48,5 @@
 #include "types/display/ptd_event.h"
 #include "types/display/seat.h"
+#include "types/display/wmclient.h"
 
 extern errno_t ds_display_create(gfx_context_t *, ds_display_flags_t,
@@ -59,8 +60,13 @@
 extern ds_client_t *ds_display_first_client(ds_display_t *);
 extern ds_client_t *ds_display_next_client(ds_client_t *);
+extern void ds_display_add_wmclient(ds_display_t *, ds_wmclient_t *);
+extern void ds_display_remove_wmclient(ds_wmclient_t *);
+extern ds_wmclient_t *ds_display_first_wmclient(ds_display_t *);
+extern ds_wmclient_t *ds_display_next_wmclient(ds_wmclient_t *);
 extern ds_window_t *ds_display_find_window(ds_display_t *, ds_wnd_id_t);
 extern ds_window_t *ds_display_window_by_pos(ds_display_t *, gfx_coord2_t *);
 extern void ds_display_add_window(ds_display_t *, ds_window_t *);
 extern void ds_display_remove_window(ds_window_t *);
+extern void ds_display_window_to_top(ds_window_t *);
 extern ds_window_t *ds_display_first_window(ds_display_t *);
 extern ds_window_t *ds_display_last_window(ds_display_t *);
Index: uspace/srv/hid/display/main.c
===================================================================
--- uspace/srv/hid/display/main.c	(revision 7cc30e98a2b4441f4a5ddaa86d0a54c7ac71fab8)
+++ uspace/srv/hid/display/main.c	(revision 913add60dd2ff444e319a0f402067877d08cb574)
@@ -56,8 +56,10 @@
 #include "seat.h"
 #include "window.h"
+#include "wmclient.h"
 #include "wmops.h"
 
 static void display_client_conn(ipc_call_t *, void *);
 static void display_client_ev_pending(void *);
+static void display_wmclient_ev_pending(void *);
 static void display_gc_conn(ipc_call_t *, void *);
 static void display_wndmgt_conn(ipc_call_t *, void *);
@@ -80,4 +82,8 @@
 };
 
+static ds_wmclient_cb_t display_wmclient_cb = {
+	.ev_pending = display_wmclient_ev_pending
+};
+
 static void display_client_ev_pending(void *arg)
 {
@@ -85,4 +91,11 @@
 
 	display_srv_ev_pending(srv);
+}
+
+static void display_wmclient_ev_pending(void *arg)
+{
+	wndmgt_srv_t *srv = (wndmgt_srv_t *) arg;
+
+	wndmgt_srv_ev_pending(srv);
 }
 
@@ -189,5 +202,7 @@
 	if (svc_id != 0) {
 		/* Create client object */
+		ds_display_lock(disp);
 		rc = ds_client_create(disp, &display_client_cb, &srv, &client);
+		ds_display_unlock(disp);
 		if (rc != EOK) {
 			async_answer_0(icall, ENOMEM);
@@ -243,13 +258,28 @@
 {
 	ds_display_t *disp = (ds_display_t *) arg;
+	errno_t rc;
 	wndmgt_srv_t srv;
+	ds_wmclient_t *wmclient = NULL;
+
+	/* Create WM client object */
+	ds_display_lock(disp);
+	rc = ds_wmclient_create(disp, &display_wmclient_cb, &srv, &wmclient);
+	ds_display_unlock(disp);
+	if (rc != EOK) {
+		async_answer_0(icall, ENOMEM);
+		return;
+	}
 
 	/* Set up protocol structure */
 	wndmgt_srv_initialize(&srv);
 	srv.ops = &wndmgt_srv_ops;
-	srv.arg = disp;
+	srv.arg = wmclient;
 
 	/* Handle connection */
 	wndmgt_conn(icall, &srv);
+
+	ds_display_lock(disp);
+	ds_wmclient_destroy(wmclient);
+	ds_display_unlock(disp);
 }
 
Index: uspace/srv/hid/display/meson.build
===================================================================
--- uspace/srv/hid/display/meson.build	(revision 7cc30e98a2b4441f4a5ddaa86d0a54c7ac71fab8)
+++ uspace/srv/hid/display/meson.build	(revision 913add60dd2ff444e319a0f402067877d08cb574)
@@ -42,4 +42,5 @@
 	'seat.c',
 	'window.c',
+	'wmclient.c',
 	'wmops.c',
 )
@@ -54,4 +55,5 @@
 	'seat.c',
 	'window.c',
+	'wmclient.c',
 	'test/client.c',
 	'test/clonegc.c',
@@ -61,3 +63,4 @@
 	'test/seat.c',
 	'test/window.c',
+	'test/wmclient.c',
 )
Index: uspace/srv/hid/display/test/display.c
===================================================================
--- uspace/srv/hid/display/test/display.c	(revision 7cc30e98a2b4441f4a5ddaa86d0a54c7ac71fab8)
+++ uspace/srv/hid/display/test/display.c	(revision 913add60dd2ff444e319a0f402067877d08cb574)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -36,4 +36,5 @@
 #include "../seat.h"
 #include "../window.h"
+#include "../wmclient.h"
 
 PCUT_INIT;
@@ -47,4 +48,10 @@
 };
 
+static void test_ds_wmev_pending(void *);
+
+static ds_wmclient_cb_t test_ds_wmclient_cb = {
+	.ev_pending = test_ds_wmev_pending
+};
+
 static void test_ds_ev_pending(void *arg)
 {
@@ -53,4 +60,10 @@
 }
 
+static void test_ds_wmev_pending(void *arg)
+{
+	bool *called_cb = (bool *) arg;
+	*called_cb = true;
+}
+
 /** Display creation and destruction. */
 PCUT_TEST(display_create_destroy)
@@ -86,4 +99,28 @@
 
 	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** Basic WM client operation. */
+PCUT_TEST(display_wmclient)
+{
+	ds_display_t *disp;
+	ds_wmclient_t *wmclient;
+	ds_wmclient_t *c0, *c1;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_wmclient_create(disp, &test_ds_wmclient_cb, NULL, &wmclient);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	c0 = ds_display_first_wmclient(disp);
+	PCUT_ASSERT_EQUALS(c0, wmclient);
+
+	c1 = ds_display_next_wmclient(c0);
+	PCUT_ASSERT_NULL(c1);
+
+	ds_wmclient_destroy(wmclient);
 	ds_display_destroy(disp);
 }
@@ -150,4 +187,46 @@
 	wnd = ds_display_find_window(disp, w0->id + 1);
 	PCUT_ASSERT_NULL(wnd);
+
+	ds_window_destroy(w0);
+	ds_window_destroy(w1);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_display_window_to_top() */
+PCUT_TEST(display_window_to_top)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *w0;
+	ds_window_t *w1;
+	display_wnd_params_t params;
+	bool called_cb = false;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, &test_ds_client_cb, &called_cb, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 100;
+
+	rc = ds_window_create(client, &params, &w0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_window_create(client, &params, &w1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_EQUALS(w1, ds_display_first_window(disp));
+	ds_display_window_to_top(w0);
+	PCUT_ASSERT_EQUALS(w0, ds_display_first_window(disp));
 
 	ds_window_destroy(w0);
Index: uspace/srv/hid/display/test/main.c
===================================================================
--- uspace/srv/hid/display/test/main.c	(revision 7cc30e98a2b4441f4a5ddaa86d0a54c7ac71fab8)
+++ uspace/srv/hid/display/test/main.c	(revision 913add60dd2ff444e319a0f402067877d08cb574)
@@ -37,4 +37,5 @@
 PCUT_IMPORT(seat);
 PCUT_IMPORT(window);
+PCUT_IMPORT(wmclient);
 
 PCUT_MAIN();
Index: uspace/srv/hid/display/test/window.c
===================================================================
--- uspace/srv/hid/display/test/window.c	(revision 7cc30e98a2b4441f4a5ddaa86d0a54c7ac71fab8)
+++ uspace/srv/hid/display/test/window.c	(revision 913add60dd2ff444e319a0f402067877d08cb574)
@@ -51,4 +51,82 @@
 };
 
+/** Test creating and destroying window */
+PCUT_TEST(create_destroy)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *wnd;
+	display_wnd_params_t params;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 10;
+
+	rc = ds_window_create(client, &params, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ds_window_destroy(wnd);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_window_bring_to_top() brings window to top */
+PCUT_TEST(bring_to_top)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *w1;
+	ds_window_t *w2;
+	display_wnd_params_t params;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 10;
+
+	rc = ds_window_create(client, &params, &w1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_window_create(client, &params, &w2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* w2 should be on the top */
+	PCUT_ASSERT_EQUALS(w2, ds_display_first_window(disp));
+
+	/* Bring w1 to top */
+	ds_window_bring_to_top(w1);
+
+	/* Now w1 should be on the top */
+	PCUT_ASSERT_EQUALS(w1, ds_display_first_window(disp));
+
+	ds_window_destroy(w1);
+	ds_window_destroy(w2);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
 /** Test ds_window_resize(). */
 PCUT_TEST(window_resize)
Index: uspace/srv/hid/display/test/wmclient.c
===================================================================
--- uspace/srv/hid/display/test/wmclient.c	(revision 913add60dd2ff444e319a0f402067877d08cb574)
+++ uspace/srv/hid/display/test/wmclient.c	(revision 913add60dd2ff444e319a0f402067877d08cb574)
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2022 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.
+ */
+
+#include <disp_srv.h>
+#include <errno.h>
+#include <pcut/pcut.h>
+#include <str.h>
+
+#include "../display.h"
+#include "../wmclient.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(wmclient);
+
+static void test_ds_wmev_pending(void *);
+
+static ds_wmclient_cb_t test_ds_wmclient_cb = {
+	.ev_pending = test_ds_wmev_pending
+};
+
+static void test_ds_wmev_pending(void *arg)
+{
+	bool *called_cb = (bool *) arg;
+	*called_cb = true;
+}
+
+/** WM client creation and destruction. */
+PCUT_TEST(create_destroy)
+{
+	ds_display_t *disp;
+	ds_wmclient_t *wmclient;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_wmclient_create(disp, &test_ds_wmclient_cb, NULL, &wmclient);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ds_wmclient_destroy(wmclient);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_wmclient_get_event(), ds_wmclient_post_wnd_added_event(). */
+PCUT_TEST(client_get_post_wnd_added_event)
+{
+	ds_display_t *disp;
+	ds_wmclient_t *wmclient;
+	wndmgt_ev_t revent;
+	bool called_cb = NULL;
+	sysarg_t wnd_id;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_wmclient_create(disp, &test_ds_wmclient_cb, &called_cb,
+	    &wmclient);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	called_cb = false;
+	wnd_id = 42;
+
+	rc = ds_wmclient_get_event(wmclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
+
+	rc = ds_wmclient_post_wnd_added_event(wmclient, wnd_id);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_TRUE(called_cb);
+
+	rc = ds_wmclient_get_event(wmclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS(wnd_id, revent.wnd_id);
+	PCUT_ASSERT_EQUALS(wmev_window_added, revent.etype);
+
+	rc = ds_wmclient_get_event(wmclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
+
+	ds_wmclient_destroy(wmclient);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_wmclient_purge_events() */
+PCUT_TEST(purge_events)
+{
+	ds_display_t *disp;
+	ds_wmclient_t *wmclient;
+	wndmgt_ev_t revent;
+	bool called_cb = NULL;
+	sysarg_t wnd_id;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_wmclient_create(disp, &test_ds_wmclient_cb, &called_cb,
+	    &wmclient);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Post window added event */
+	wnd_id = 42;
+	rc = ds_wmclient_post_wnd_added_event(wmclient, wnd_id);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Purge it */
+	ds_wmclient_purge_events(wmclient);
+
+	/* The queue should be empty now */
+	rc = ds_wmclient_get_event(wmclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
+
+	ds_wmclient_destroy(wmclient);
+	ds_display_destroy(disp);
+}
+
+PCUT_EXPORT(wmclient);
Index: uspace/srv/hid/display/types/display/display.h
===================================================================
--- uspace/srv/hid/display/types/display/display.h	(revision 7cc30e98a2b4441f4a5ddaa86d0a54c7ac71fab8)
+++ uspace/srv/hid/display/types/display/display.h	(revision 913add60dd2ff444e319a0f402067877d08cb574)
@@ -62,4 +62,6 @@
 	/** Clients (of ds_client_t) */
 	list_t clients;
+	/** WM clients (of ds_wmclient_t) */
+	list_t wmclients;
 
 	/** Next ID to assign to a window.
Index: uspace/srv/hid/display/types/display/wmclient.h
===================================================================
--- uspace/srv/hid/display/types/display/wmclient.h	(revision 913add60dd2ff444e319a0f402067877d08cb574)
+++ uspace/srv/hid/display/types/display/wmclient.h	(revision 913add60dd2ff444e319a0f402067877d08cb574)
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2022 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 WM client type
+ */
+
+#ifndef TYPES_DISPLAY_WMCLIENT_H
+#define TYPES_DISPLAY_WMCLIENT_H
+
+#include <adt/list.h>
+#include <wndmgt.h>
+
+/** Display server WM client callbacks */
+typedef struct {
+	void (*ev_pending)(void *);
+} ds_wmclient_cb_t;
+
+/** Display server WM client */
+typedef struct ds_wmclient {
+	/** Parent display */
+	struct ds_display *display;
+	/** Callbacks */
+	ds_wmclient_cb_t *cb;
+	/** Callback argument */
+	void *cb_arg;
+	/** Link to @c display->wmclients */
+	link_t lwmclients;
+	/** Event queue (of ds_window_ev_t) */
+	list_t events;
+} ds_wmclient_t;
+
+/** WM client event queue entry */
+typedef struct {
+	/** Link to event queue */
+	link_t levents;
+	/** WM client to which the event is delivered */
+	ds_wmclient_t *wmclient;
+	/** Event */
+	wndmgt_ev_t event;
+} ds_wmclient_ev_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/hid/display/window.c
===================================================================
--- uspace/srv/hid/display/window.c	(revision 7cc30e98a2b4441f4a5ddaa86d0a54c7ac71fab8)
+++ uspace/srv/hid/display/window.c	(revision 913add60dd2ff444e319a0f402067877d08cb574)
@@ -197,8 +197,5 @@
 void ds_window_bring_to_top(ds_window_t *wnd)
 {
-	ds_display_t *disp = wnd->display;
-
-	ds_display_remove_window(wnd);
-	ds_display_add_window(disp, wnd);
+	ds_display_window_to_top(wnd);
 	(void) ds_display_paint(wnd->display, NULL);
 }
Index: uspace/srv/hid/display/wmclient.c
===================================================================
--- uspace/srv/hid/display/wmclient.c	(revision 913add60dd2ff444e319a0f402067877d08cb574)
+++ uspace/srv/hid/display/wmclient.c	(revision 913add60dd2ff444e319a0f402067877d08cb574)
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2022 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 WM client
+ */
+
+#include <adt/list.h>
+#include <errno.h>
+#include <stdlib.h>
+#include "display.h"
+#include "window.h"
+#include "wmclient.h"
+
+/** Create WM client.
+ *
+ * @param display Parent display
+ * @param cb WM client callbacks
+ * @param cb_arg Callback argument
+ * @param rwmclient Place to store pointer to new WM client.
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ds_wmclient_create(ds_display_t *display, ds_wmclient_cb_t *cb,
+    void *cb_arg, ds_wmclient_t **rwmclient)
+{
+	ds_wmclient_t *wmclient;
+
+	wmclient = calloc(1, sizeof(ds_wmclient_t));
+	if (wmclient == NULL)
+		return ENOMEM;
+
+	list_initialize(&wmclient->events);
+	wmclient->cb = cb;
+	wmclient->cb_arg = cb_arg;
+
+	ds_display_add_wmclient(display, wmclient);
+
+	*rwmclient = wmclient;
+	return EOK;
+}
+
+/** Destroy WM client.
+ *
+ * @param wmclient WM client
+ */
+void ds_wmclient_destroy(ds_wmclient_t *wmclient)
+{
+	ds_wmclient_purge_events(wmclient);
+	ds_display_remove_wmclient(wmclient);
+	free(wmclient);
+}
+
+/** Get next event from WM client event queue.
+ *
+ * @param wmclient WM client
+ * @param event Place to store event
+ * @return EOK on success, ENOENT if event queue is empty
+ */
+errno_t ds_wmclient_get_event(ds_wmclient_t *wmclient, wndmgt_ev_t *event)
+{
+	link_t *link;
+	ds_wmclient_ev_t *wevent;
+
+	link = list_first(&wmclient->events);
+	if (link == NULL)
+		return ENOENT;
+
+	wevent = list_get_instance(link, ds_wmclient_ev_t, levents);
+	list_remove(link);
+
+	*event = wevent->event;
+	free(wevent);
+	return EOK;
+}
+
+/** Purge events from WM client event queue.
+ *
+ * @param client Client
+ */
+void ds_wmclient_purge_events(ds_wmclient_t *wmclient)
+{
+	link_t *cur;
+	link_t *next;
+	ds_wmclient_ev_t *wevent;
+
+	cur = list_first(&wmclient->events);
+	while (cur != NULL) {
+		next = list_next(cur, &wmclient->events);
+		wevent = list_get_instance(cur, ds_wmclient_ev_t, levents);
+
+		list_remove(cur);
+		free(wevent);
+		cur = next;
+	}
+}
+#include <io/log.h>
+/** Post window added event to the WM client's message queue.
+ *
+ * @param wmclient WM client
+ * @param wnd_id Window ID of the added window
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ds_wmclient_post_wnd_added_event(ds_wmclient_t *wmclient,
+    sysarg_t wnd_id)
+{
+	ds_wmclient_ev_t *wevent;
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "post wnd added event wmclient=%p\n",
+	    (void *)wmclient);
+
+	wevent = calloc(1, sizeof(ds_wmclient_ev_t));
+	if (wevent == NULL)
+		return ENOMEM;
+
+	wevent->wmclient = wmclient;
+	wevent->event.etype = wmev_window_added;
+	wevent->event.wnd_id = wnd_id;
+	list_append(&wevent->levents, &wmclient->events);
+
+	/* Notify the client */
+	// TODO Do not send more than once until client drains the queue
+	if (wmclient->cb != NULL && wmclient->cb->ev_pending != NULL)
+		wmclient->cb->ev_pending(wmclient->cb_arg);
+
+	return EOK;
+}
+
+/** Post window removed event to the WM client's message queue.
+ *
+ * @param wmclient WM client
+ * @param wnd_id Window ID of the added window
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ds_wmclient_post_wnd_removed_event(ds_wmclient_t *wmclient,
+    sysarg_t wnd_id)
+{
+	ds_wmclient_ev_t *wevent;
+
+	wevent = calloc(1, sizeof(ds_wmclient_ev_t));
+	if (wevent == NULL)
+		return ENOMEM;
+
+	wevent->wmclient = wmclient;
+	wevent->event.etype = wmev_window_removed;
+	wevent->event.wnd_id = wnd_id;
+	list_append(&wevent->levents, &wmclient->events);
+
+	/* Notify the client */
+	// TODO Do not send more than once until client drains the queue
+	if (wmclient->cb != NULL && wmclient->cb->ev_pending != NULL)
+		wmclient->cb->ev_pending(wmclient->cb_arg);
+
+	return EOK;
+}
+
+/** @}
+ */
Index: uspace/srv/hid/display/wmclient.h
===================================================================
--- uspace/srv/hid/display/wmclient.h	(revision 913add60dd2ff444e319a0f402067877d08cb574)
+++ uspace/srv/hid/display/wmclient.h	(revision 913add60dd2ff444e319a0f402067877d08cb574)
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2022 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 WM client
+ */
+
+#ifndef WMCLIENT_H
+#define WMCLIENT_H
+
+#include <errno.h>
+#include <wndmgt.h>
+#include "types/display/display.h"
+#include "types/display/wmclient.h"
+
+extern errno_t ds_wmclient_create(ds_display_t *, ds_wmclient_cb_t *, void *,
+    ds_wmclient_t **);
+extern void ds_wmclient_destroy(ds_wmclient_t *);
+extern errno_t ds_wmclient_get_event(ds_wmclient_t *,  wndmgt_ev_t *);
+extern errno_t ds_wmclient_post_wnd_added_event(ds_wmclient_t *, sysarg_t);
+extern errno_t ds_wmclient_post_wnd_removed_event(ds_wmclient_t *, sysarg_t);
+extern void ds_wmclient_purge_events(ds_wmclient_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/hid/display/wmops.c
===================================================================
--- uspace/srv/hid/display/wmops.c	(revision 7cc30e98a2b4441f4a5ddaa86d0a54c7ac71fab8)
+++ uspace/srv/hid/display/wmops.c	(revision 913add60dd2ff444e319a0f402067877d08cb574)
@@ -40,4 +40,5 @@
 #include <wndmgt_srv.h>
 #include "display.h"
+#include "wmclient.h"
 
 static errno_t dispwm_get_window_list(void *, wndmgt_window_list_t **);
@@ -55,8 +56,14 @@
 };
 
+/** Get window list.
+ *
+ * @param arg Argument (WM client)
+ * @param rlist Place to store pointer to new list
+ * @return EOK on success or an error code
+ */
 static errno_t dispwm_get_window_list(void *arg, wndmgt_window_list_t **rlist)
 {
+	ds_wmclient_t *wmclient = (ds_wmclient_t *)arg;
 	wndmgt_window_list_t *list;
-	ds_display_t *disp = (ds_display_t *)arg;
 	ds_window_t *wnd;
 	unsigned i;
@@ -68,7 +75,9 @@
 		return ENOMEM;
 
+	ds_display_lock(wmclient->display);
+
 	/* Count the number of windows */
 	list->nwindows = 0;
-	wnd = ds_display_first_window(disp);
+	wnd = ds_display_first_window(wmclient->display);
 	while (wnd != NULL) {
 		++list->nwindows;
@@ -79,4 +88,5 @@
 	list->windows = calloc(list->nwindows, sizeof(sysarg_t));
 	if (list->windows == NULL) {
+		ds_display_unlock(wmclient->display);
 		free(list);
 		return ENOMEM;
@@ -85,5 +95,5 @@
 	/* Fill in window IDs */
 	i = 0;
-	wnd = ds_display_first_window(disp);
+	wnd = ds_display_first_window(wmclient->display);
 	while (wnd != NULL) {
 		list->windows[i++] = wnd->id;
@@ -91,12 +101,20 @@
 	}
 
+	ds_display_unlock(wmclient->display);
 	*rlist = list;
 	return EOK;
 }
 
+/** Get window information.
+ *
+ * @param arg Argument (WM client)
+ * @param wnd_id Window ID
+ * @param rinfo Place to store pointer to new window information structure
+ * @return EOK on success or an error code
+ */
 static errno_t dispwm_get_window_info(void *arg, sysarg_t wnd_id,
     wndmgt_window_info_t **rinfo)
 {
-	ds_display_t *disp = (ds_display_t *)arg;
+	ds_wmclient_t *wmclient = (ds_wmclient_t *)arg;
 	ds_window_t *wnd;
 	wndmgt_window_info_t *info;
@@ -104,22 +122,35 @@
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "dispwm_get_window_info()");
 
-	wnd = ds_display_find_window(disp, wnd_id);
-	if (wnd == NULL)
+	ds_display_lock(wmclient->display);
+	wnd = ds_display_find_window(wmclient->display, wnd_id);
+	if (wnd == NULL) {
+		ds_display_unlock(wmclient->display);
 		return ENOENT;
+	}
 
 	info = calloc(1, sizeof(wndmgt_window_info_t));
-	if (info == NULL)
+	if (info == NULL) {
+		ds_display_unlock(wmclient->display);
 		return ENOMEM;
+	}
 
 	info->caption = str_dup(wnd->caption);
 	if (info->caption == NULL) {
+		ds_display_unlock(wmclient->display);
 		free(info);
 		return ENOMEM;
 	}
 
+	ds_display_unlock(wmclient->display);
 	*rinfo = info;
 	return EOK;
 }
 
+/** Activate window.
+ *
+ * @param arg Argument (WM client)
+ * @param wnd_id Window ID
+ * @return EOK on success or an error code
+ */
 static errno_t dispwm_activate_window(void *arg, sysarg_t wnd_id)
 {
@@ -130,4 +161,10 @@
 }
 
+/** Close window.
+ *
+ * @param arg Argument (WM client)
+ * @param wnd_id Window ID
+ * @return EOK on success or an error code
+ */
 static errno_t dispwm_close_window(void *arg, sysarg_t wnd_id)
 {
@@ -138,7 +175,21 @@
 }
 
+/** Get window management event.
+ *
+ * @param arg Argument (WM client)
+ * @param ev Place to store event
+ * @return EOK on success, ENOENT if there are no events
+ */
 static errno_t dispwm_get_event(void *arg, wndmgt_ev_t *ev)
 {
-	return ENOTSUP;
+	ds_wmclient_t *wmclient = (ds_wmclient_t *)arg;
+	errno_t rc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dispwm_get_event()");
+
+	ds_display_lock(wmclient->display);
+	rc = ds_wmclient_get_event(wmclient, ev);
+	ds_display_unlock(wmclient->display);
+	return rc;
 }
 
