Index: abi/include/abi/ipc/interfaces.h
===================================================================
--- abi/include/abi/ipc/interfaces.h	(revision e0e612b4a7ecb09b6016f7d622789dcc33cb26e4)
+++ abi/include/abi/ipc/interfaces.h	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
@@ -192,4 +192,8 @@
 	INTERFACE_GC =
 	    FOURCC_COMPACT('g', 'f', 'x', 'c') | IFACE_EXCHANGE_SERIALIZE,
+	INTERFACE_WNDMGT =
+	    FOURCC_COMPACT('w', 'm', 'g', 't') | IFACE_EXCHANGE_SERIALIZE,
+	INTERFACE_WNDMGT_CB =
+	    FOURCC_COMPACT('w', 'm', 'g', 't') | IFACE_EXCHANGE_SERIALIZE | IFACE_MOD_CALLBACK,
 } iface_t;
 
Index: uspace/lib/c/include/ipc/services.h
===================================================================
--- uspace/lib/c/include/ipc/services.h	(revision e0e612b4a7ecb09b6016f7d622789dcc33cb26e4)
+++ uspace/lib/c/include/ipc/services.h	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
@@ -55,4 +55,5 @@
 #define SERVICE_NAME_CORECFG  "corecfg"
 #define SERVICE_NAME_DISPLAY  "hid/display"
+#define SERVICE_NAME_WNDMGT   "hid/display"
 #define SERVICE_NAME_DHCP     "net/dhcp"
 #define SERVICE_NAME_DNSR     "net/dnsr"
Index: uspace/lib/meson.build
===================================================================
--- uspace/lib/meson.build	(revision e0e612b4a7ecb09b6016f7d622789dcc33cb26e4)
+++ uspace/lib/meson.build	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
@@ -103,4 +103,5 @@
 	'ddev',
 	'display',
+	'wndmgt',
 
 	'ui',
Index: uspace/lib/wndmgt/doc/doxygroups.h
===================================================================
--- uspace/lib/wndmgt/doc/doxygroups.h	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
+++ uspace/lib/wndmgt/doc/doxygroups.h	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
@@ -0,0 +1,3 @@
+/** @addtogroup libwndmgt libwndmgt
+ * @ingroup libs
+ */
Index: uspace/lib/wndmgt/include/ipc/wndmgt.h
===================================================================
--- uspace/lib/wndmgt/include/ipc/wndmgt.h	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
+++ uspace/lib/wndmgt/include/ipc/wndmgt.h	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
@@ -0,0 +1,56 @@
+/*
+ * 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 libwndmgt
+ * @{
+ */
+/** @file
+ */
+
+#ifndef LIBWNDMGT_IPC_WNDMGT_H
+#define LIBWNDMGT_IPC_WNDMGT_H
+
+#include <ipc/common.h>
+
+typedef enum {
+	WNDMGT_CALLBACK_CREATE = IPC_FIRST_USER_METHOD,
+	WNDMGT_GET_WINDOW_LIST,
+	WNDMGT_GET_WINDOW_INFO,
+	WNDMGT_ACTIVATE_WINDOW,
+	WNDMGT_CLOSE_WINDOW,
+	WNDMGT_GET_EVENT,
+} wndmgt_request_t;
+
+typedef enum {
+	WNDMGT_EV_PENDING = IPC_FIRST_USER_METHOD
+} wndmgt_event_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/wndmgt/include/types/wndmgt.h
===================================================================
--- uspace/lib/wndmgt/include/types/wndmgt.h	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
+++ uspace/lib/wndmgt/include/types/wndmgt.h	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
@@ -0,0 +1,89 @@
+/*
+ * 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 libwndmgt
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBWNDMGT_TYPES_WNDMGT_H_
+#define _LIBWNDMGT_TYPES_WNDMGT_H_
+
+#include <types/common.h>
+
+/** Use the default window management service (argument to wndmgt_open() */
+#define WNDMGT_DEFAULT NULL
+
+struct wndmgt;
+
+/** Window management session */
+typedef struct wndmgt wndmgt_t;
+
+/** Window management callbacks */
+typedef struct {
+	/** Window added */
+	void (*window_added)(void *, sysarg_t);
+	/** Window removed */
+	void (*window_removed)(void *, sysarg_t);
+} wndmgt_cb_t;
+
+/** Window management event type */
+typedef enum {
+	/** Window added */
+	wmev_window_added,
+	/** Window removed */
+	wmev_window_removed
+} wndmgt_ev_type_t;
+
+/** Window management event */
+typedef struct {
+	/** Event type */
+	wndmgt_ev_type_t etype;
+	/** Window ID */
+	sysarg_t wnd_id;
+} wndmgt_ev_t;
+
+/** Window list */
+typedef struct {
+	/** Number of windows */
+	size_t nwindows;
+	/** ID for each window */
+	sysarg_t *windows;
+} wndmgt_window_list_t;
+
+/** Window information */
+typedef struct {
+	/** Window caption */
+	char *caption;
+} wndmgt_window_info_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/wndmgt/include/wndmgt.h
===================================================================
--- uspace/lib/wndmgt/include/wndmgt.h	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
+++ uspace/lib/wndmgt/include/wndmgt.h	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
@@ -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 libwndmgt
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBWNDMGT_WNDMGT_H_
+#define _LIBWNDMGT_WNDMGT_H_
+
+#include <errno.h>
+#include <types/common.h>
+#include "types/wndmgt.h"
+
+extern errno_t wndmgt_open(const char *, wndmgt_cb_t *, void *, wndmgt_t **);
+extern void wndmgt_close(wndmgt_t *);
+extern errno_t wndmgt_get_window_list(wndmgt_t *, wndmgt_window_list_t **);
+extern void wndmgt_free_window_list(wndmgt_window_list_t *);
+extern errno_t wndmgt_get_window_info(wndmgt_t *, sysarg_t,
+    wndmgt_window_info_t **);
+extern void wndmgt_free_window_info(wndmgt_window_info_t *);
+extern errno_t wndmgt_activate_window(wndmgt_t *, sysarg_t);
+extern errno_t wndmgt_close_window(wndmgt_t *, sysarg_t);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/wndmgt/include/wndmgt_srv.h
===================================================================
--- uspace/lib/wndmgt/include/wndmgt_srv.h	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
+++ uspace/lib/wndmgt/include/wndmgt_srv.h	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
@@ -0,0 +1,66 @@
+/*
+ * 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 libwndmgt
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBWNDMGT_WNDMGT_SRV_H_
+#define _LIBWNDMGT_WNDMGT_SRV_H_
+
+#include <async.h>
+#include <errno.h>
+#include "types/wndmgt.h"
+
+typedef struct wndmgt_ops wndmgt_ops_t;
+
+/** Window management server structure (per client session) */
+typedef struct {
+	async_sess_t *client_sess;
+	wndmgt_ops_t *ops;
+	void *arg;
+} wndmgt_srv_t;
+
+struct wndmgt_ops {
+	errno_t (*get_window_list)(void *, wndmgt_window_list_t **);
+	errno_t (*get_window_info)(void *, sysarg_t, wndmgt_window_info_t **);
+	errno_t (*activate_window)(void *, sysarg_t);
+	errno_t (*close_window)(void *, sysarg_t);
+	errno_t (*get_event)(void *, wndmgt_ev_t *);
+};
+
+extern void wndmgt_conn(ipc_call_t *, wndmgt_srv_t *);
+extern void wndmgt_srv_initialize(wndmgt_srv_t *);
+extern void wndmgt_srv_ev_pending(wndmgt_srv_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/wndmgt/meson.build
===================================================================
--- uspace/lib/wndmgt/meson.build	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
+++ uspace/lib/wndmgt/meson.build	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
@@ -0,0 +1,37 @@
+#
+# 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.
+#
+
+src = files(
+	'src/wndmgt.c',
+	'src/wndmgt_srv.c',
+)
+
+test_src = files(
+	'test/main.c',
+	'test/wndmgt.c',
+)
Index: uspace/lib/wndmgt/private/wndmgt.h
===================================================================
--- uspace/lib/wndmgt/private/wndmgt.h	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
+++ uspace/lib/wndmgt/private/wndmgt.h	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
@@ -0,0 +1,61 @@
+/*
+ * 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 libwndmgt
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBWNDMGT_PRIVATE_WNDMGT_H_
+#define _LIBWNDMGT_PRIVATE_WNDMGT_H_
+
+#include <async.h>
+#include <fibril_synch.h>
+#include <stdbool.h>
+
+/** Window management session structure */
+struct wndmgt {
+	/** Session with window management service */
+	async_sess_t *sess;
+	/** Callbacks */
+	wndmgt_cb_t *cb;
+	/** Calback argument */
+	void *cb_arg;
+	/** Synchronize access to window management 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;
+};
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/wndmgt/src/wndmgt.c
===================================================================
--- uspace/lib/wndmgt/src/wndmgt.c	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
+++ uspace/lib/wndmgt/src/wndmgt.c	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
@@ -0,0 +1,432 @@
+/*
+ * 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 libwndmgt
+ * @{
+ */
+/**
+ * @file
+ * @brief Window management protocol client
+ */
+
+#include <async.h>
+#include <wndmgt.h>
+#include <errno.h>
+#include <fibril_synch.h>
+#include <ipc/wndmgt.h>
+#include <ipc/services.h>
+#include <loc.h>
+#include <mem.h>
+#include <stdlib.h>
+#include "../private/wndmgt.h"
+
+static errno_t wndmgt_callback_create(wndmgt_t *);
+static void wndmgt_cb_conn(ipc_call_t *, void *);
+
+/** Open window management service.
+ *
+ * @param wmname Window management service name or @c NULL to use default
+ * @param cb Window management callbacks
+ * @param cb_arg Callback argument
+ * @param rwndmgt Place to store pointer to window management session
+ * @return EOK on success or an error code
+ */
+errno_t wndmgt_open(const char *wmname, wndmgt_cb_t *cb, void *cb_arg,
+    wndmgt_t **rwndmgt)
+{
+	service_id_t wndmgt_svc;
+	wndmgt_t *wndmgt;
+	errno_t rc;
+
+	wndmgt = calloc(1, sizeof(wndmgt_t));
+	if (wndmgt == NULL)
+		return ENOMEM;
+
+	wndmgt->cb = cb;
+	wndmgt->cb_arg = cb_arg;
+
+	fibril_mutex_initialize(&wndmgt->lock);
+	fibril_condvar_initialize(&wndmgt->cv);
+
+	if (wmname == NULL)
+		wmname = SERVICE_NAME_WNDMGT;
+
+	rc = loc_service_get_id(wmname, &wndmgt_svc, 0);
+	if (rc != EOK) {
+		free(wndmgt);
+		return ENOENT;
+	}
+
+	wndmgt->sess = loc_service_connect(wndmgt_svc, INTERFACE_WNDMGT,
+	    0);
+	if (wndmgt->sess == NULL) {
+		free(wndmgt);
+		return ENOENT;
+	}
+
+	rc = wndmgt_callback_create(wndmgt);
+	if (rc != EOK) {
+		async_hangup(wndmgt->sess);
+		free(wndmgt);
+		return EIO;
+	}
+
+	*rwndmgt = wndmgt;
+	return EOK;
+}
+
+/** Create callback connection from window management service.
+ *
+ * @param wndmgt Window management
+ * @return EOK on success or an error code
+ */
+static errno_t wndmgt_callback_create(wndmgt_t *wndmgt)
+{
+	async_exch_t *exch = async_exchange_begin(wndmgt->sess);
+
+	aid_t req = async_send_0(exch, WNDMGT_CALLBACK_CREATE, NULL);
+
+	port_id_t port;
+	errno_t rc = async_create_callback_port(exch, INTERFACE_WNDMGT_CB, 0, 0,
+	    wndmgt_cb_conn, wndmgt, &port);
+
+	async_exchange_end(exch);
+
+	if (rc != EOK)
+		return rc;
+
+	errno_t retval;
+	async_wait_for(req, &retval);
+
+	return retval;
+}
+
+/** Close window management service.
+ *
+ * @param wndmgt Window management
+ */
+void wndmgt_close(wndmgt_t *wndmgt)
+{
+	fibril_mutex_lock(&wndmgt->lock);
+	async_hangup(wndmgt->sess);
+	wndmgt->sess = NULL;
+
+	/* Wait for callback handler to terminate */
+
+	while (!wndmgt->cb_done)
+		fibril_condvar_wait(&wndmgt->cv, &wndmgt->lock);
+	fibril_mutex_unlock(&wndmgt->lock);
+
+	free(wndmgt);
+}
+
+/** Get window list.
+ *
+ * @param wndmgt Window management
+ * @param rlist Place to store pointer to new window list structure
+ * @return EOK on success or an error code
+ */
+errno_t wndmgt_get_window_list(wndmgt_t *wndmgt, wndmgt_window_list_t **rlist)
+{
+	async_exch_t *exch;
+	aid_t req;
+	ipc_call_t answer;
+	wndmgt_window_list_t *list;
+	sysarg_t nwindows;
+	sysarg_t *windows;
+	errno_t rc;
+
+	exch = async_exchange_begin(wndmgt->sess);
+	req = async_send_0(exch, WNDMGT_GET_WINDOW_LIST, &answer);
+
+	/* Receive window list length */
+	rc = async_data_read_start(exch, &nwindows, sizeof (nwindows));
+	if (rc != EOK) {
+		async_exchange_end(exch);
+		async_forget(req);
+		return rc;
+	}
+
+	windows = calloc(nwindows, sizeof(sysarg_t));
+	if (windows == NULL) {
+		async_exchange_end(exch);
+		async_forget(req);
+		return ENOMEM;
+	}
+
+	/* Receive window list */
+	rc = async_data_read_start(exch, windows, nwindows * sizeof (sysarg_t));
+	async_exchange_end(exch);
+
+	if (rc != EOK) {
+		async_forget(req);
+		return rc;
+	}
+
+	async_wait_for(req, &rc);
+	if (rc != EOK)
+		return rc;
+
+	list = calloc(1, sizeof(wndmgt_window_list_t));
+	if (list == NULL)
+		return ENOMEM;
+
+	list->nwindows = nwindows;
+	list->windows = windows;
+	*rlist = list;
+	return EOK;
+}
+
+/** Free window list.
+ *
+ * @param list Window management list
+ */
+void wndmgt_free_window_list(wndmgt_window_list_t *list)
+{
+	free(list->windows);
+	free(list);
+}
+
+/** Get window information.
+ *
+ * @param wndmgt Window management
+ * @param wnd_id Window ID
+ * @param rinfo Place to store pointer to new window information structure
+ * @return EOK on success or an error code
+ */
+errno_t wndmgt_get_window_info(wndmgt_t *wndmgt, sysarg_t wnd_id,
+    wndmgt_window_info_t **rinfo)
+{
+	async_exch_t *exch;
+	aid_t req;
+	ipc_call_t answer;
+	wndmgt_window_info_t *info;
+	sysarg_t capsize;
+	char *caption;
+	errno_t rc;
+
+	exch = async_exchange_begin(wndmgt->sess);
+	req = async_send_1(exch, WNDMGT_GET_WINDOW_INFO, wnd_id, &answer);
+
+	/* Receive caption size */
+	rc = async_data_read_start(exch, &capsize, sizeof (capsize));
+	if (rc != EOK) {
+		async_exchange_end(exch);
+		async_forget(req);
+		return rc;
+	}
+
+	caption = calloc(capsize + 1, sizeof(char));
+	if (caption == NULL) {
+		async_exchange_end(exch);
+		async_forget(req);
+		return ENOMEM;
+	}
+
+	/* Receive caption */
+	rc = async_data_read_start(exch, caption, capsize);
+	async_exchange_end(exch);
+
+	if (rc != EOK) {
+		async_forget(req);
+		return rc;
+	}
+
+	async_wait_for(req, &rc);
+	if (rc != EOK)
+		return rc;
+
+	/* Null-terminate the string */
+	caption[capsize] = '\0';
+
+	info = calloc(1, sizeof(wndmgt_window_info_t));
+	if (info == NULL)
+		return ENOMEM;
+
+	info->caption = caption;
+	*rinfo = info;
+	return EOK;
+}
+
+/** Free window information.
+ *
+ * @param info Window management information
+ */
+void wndmgt_free_window_info(wndmgt_window_info_t *info)
+{
+	free(info->caption);
+	free(info);
+}
+
+/** Activate window.
+ *
+ * @param wndmgt Window management session
+ * @param wnd_id Window ID
+ * @return EOK on success or an error code
+ */
+errno_t wndmgt_activate_window(wndmgt_t *wndmgt, sysarg_t wnd_id)
+{
+	async_exch_t *exch;
+	errno_t rc;
+
+	exch = async_exchange_begin(wndmgt->sess);
+	rc = async_req_1_0(exch, WNDMGT_ACTIVATE_WINDOW, wnd_id);
+
+	async_exchange_end(exch);
+	return rc;
+}
+
+/** Close window.
+ *
+ * @param wndmgt Window management
+ * @param wnd_id Window ID
+ * @return EOK on success or an error code
+ */
+errno_t wndmgt_close_window(wndmgt_t *wndmgt, sysarg_t wnd_id)
+{
+	async_exch_t *exch;
+	errno_t rc;
+
+	exch = async_exchange_begin(wndmgt->sess);
+	rc = async_req_1_0(exch, WNDMGT_CLOSE_WINDOW, wnd_id);
+
+	async_exchange_end(exch);
+	return rc;
+}
+
+/** Get window management event.
+ *
+ * @param wndmgt Window management
+ * @param event Place to store event
+ * @return EOK on success or an error code
+ */
+static errno_t wndmgt_get_event(wndmgt_t *wndmgt, wndmgt_ev_t *event)
+{
+	async_exch_t *exch;
+	ipc_call_t answer;
+	aid_t req;
+	errno_t rc;
+
+	exch = async_exchange_begin(wndmgt->sess);
+	req = async_send_0(exch, WNDMGT_GET_EVENT, &answer);
+	rc = async_data_read_start(exch, event, sizeof(*event));
+	async_exchange_end(exch);
+	if (rc != EOK) {
+		async_forget(req);
+		return rc;
+	}
+
+	async_wait_for(req, &rc);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Window management events are pending.
+ *
+ * @param wndmgt Window management
+ * @param icall Call data
+ */
+static void wndmgt_ev_pending(wndmgt_t *wndmgt, ipc_call_t *icall)
+{
+	errno_t rc;
+	wndmgt_ev_t event;
+
+	while (true) {
+		fibril_mutex_lock(&wndmgt->lock);
+
+		if (wndmgt->sess != NULL)
+			rc = wndmgt_get_event(wndmgt, &event);
+		else
+			rc = ENOENT;
+
+		fibril_mutex_unlock(&wndmgt->lock);
+
+		if (rc != EOK)
+			break;
+
+		switch (event.etype) {
+		case wmev_window_added:
+			if (wndmgt->cb != NULL &&
+			    wndmgt->cb->window_added != NULL) {
+				wndmgt->cb->window_added(wndmgt->cb_arg,
+				    event.wnd_id);
+			}
+			break;
+		case wmev_window_removed:
+			if (wndmgt->cb != NULL &&
+			    wndmgt->cb->window_removed != NULL) {
+				wndmgt->cb->window_removed(wndmgt->cb_arg,
+				    event.wnd_id);
+			}
+			break;
+		}
+	}
+
+	async_answer_0(icall, EOK);
+}
+
+/** Callback connection handler.
+ *
+ * @param icall Connect call data
+ * @param arg   Argument, wndmgt_t *
+ */
+static void wndmgt_cb_conn(ipc_call_t *icall, void *arg)
+{
+	wndmgt_t *wndmgt = (wndmgt_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 WNDMGT_EV_PENDING:
+			wndmgt_ev_pending(wndmgt, &call);
+			break;
+		default:
+			async_answer_0(&call, ENOTSUP);
+			break;
+		}
+	}
+
+out:
+	fibril_mutex_lock(&wndmgt->lock);
+	wndmgt->cb_done = true;
+	fibril_mutex_unlock(&wndmgt->lock);
+	fibril_condvar_broadcast(&wndmgt->cv);
+}
+
+/** @}
+ */
Index: uspace/lib/wndmgt/src/wndmgt_srv.c
===================================================================
--- uspace/lib/wndmgt/src/wndmgt_srv.c	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
+++ uspace/lib/wndmgt/src/wndmgt_srv.c	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
@@ -0,0 +1,347 @@
+/*
+ * 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 libwndmgt
+ * @{
+ */
+/**
+ * @file
+ * @brief Window management protocol server stub
+ */
+
+#include <wndmgt_srv.h>
+#include <errno.h>
+#include <io/log.h>
+#include <ipc/wndmgt.h>
+#include <mem.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <str.h>
+#include <wndmgt.h>
+#include "../private/wndmgt.h"
+
+static void wndmgt_callback_create_srv(wndmgt_srv_t *srv, ipc_call_t *call)
+{
+	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 wndmgt_get_window_list_srv(wndmgt_srv_t *srv, ipc_call_t *icall)
+{
+	ipc_call_t call;
+	wndmgt_window_list_t *list = NULL;
+	size_t size;
+	errno_t rc;
+
+	if (srv->ops->get_window_list == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->get_window_list(srv->arg, &list);
+	if (rc != EOK) {
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	/* Send list size */
+
+	if (!async_data_read_receive(&call, &size)) {
+		wndmgt_free_window_list(list);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != sizeof(list->nwindows)) {
+		wndmgt_free_window_list(list);
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	rc = async_data_read_finalize(&call, &list->nwindows, size);
+	if (rc != EOK) {
+		wndmgt_free_window_list(list);
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	/* Send window list */
+
+	if (!async_data_read_receive(&call, &size)) {
+		wndmgt_free_window_list(list);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != list->nwindows * sizeof(sysarg_t)) {
+		wndmgt_free_window_list(list);
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	rc = async_data_read_finalize(&call, list->windows, size);
+	if (rc != EOK) {
+		wndmgt_free_window_list(list);
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	async_answer_0(icall, EOK);
+	wndmgt_free_window_list(list);
+}
+
+static void wndmgt_get_window_info_srv(wndmgt_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t wnd_id;
+	ipc_call_t call;
+	wndmgt_window_info_t *info = NULL;
+	size_t capsize;
+	size_t size;
+	errno_t rc;
+
+	wnd_id = ipc_get_arg1(icall);
+
+	if (srv->ops->get_window_info == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->get_window_info(srv->arg, wnd_id, &info);
+	if (rc != EOK) {
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	/* Send caption size */
+
+	if (!async_data_read_receive(&call, &size)) {
+		wndmgt_free_window_info(info);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != sizeof(size_t)) {
+		wndmgt_free_window_info(info);
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	capsize = str_size(info->caption);
+
+	rc = async_data_read_finalize(&call, &capsize, size);
+	if (rc != EOK) {
+		wndmgt_free_window_info(info);
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	/* Send caption */
+
+	if (!async_data_read_receive(&call, &size)) {
+		wndmgt_free_window_info(info);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != capsize) {
+		wndmgt_free_window_info(info);
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	rc = async_data_read_finalize(&call, info->caption, size);
+	if (rc != EOK) {
+		wndmgt_free_window_info(info);
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	async_answer_0(icall, EOK);
+	wndmgt_free_window_info(info);
+}
+
+static void wndmgt_activate_window_srv(wndmgt_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t wnd_id;
+	errno_t rc;
+
+	wnd_id = ipc_get_arg1(icall);
+
+	if (srv->ops->activate_window == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->activate_window(srv->arg, wnd_id);
+	async_answer_0(icall, rc);
+}
+
+static void wndmgt_close_window_srv(wndmgt_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t wnd_id;
+	errno_t rc;
+
+	wnd_id = ipc_get_arg1(icall);
+
+	if (srv->ops->activate_window == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->close_window(srv->arg, wnd_id);
+	async_answer_0(icall, rc);
+}
+
+static void wndmgt_get_event_srv(wndmgt_srv_t *srv, ipc_call_t *icall)
+{
+	wndmgt_ev_t event;
+	ipc_call_t call;
+	size_t size;
+	errno_t rc;
+
+	if (srv->ops->get_event == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->get_event(srv->arg, &event);
+	if (rc != EOK) {
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	/* 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_0(icall, EOK);
+}
+
+void wndmgt_conn(ipc_call_t *icall, wndmgt_srv_t *srv)
+{
+	/* Accept the connection */
+	async_accept_0(icall);
+
+	while (true) {
+		ipc_call_t call;
+
+		async_get_call(&call);
+		sysarg_t method = ipc_get_imethod(&call);
+
+		if (!method) {
+			/* The other side has hung up */
+			async_answer_0(&call, EOK);
+			break;
+		}
+
+		switch (method) {
+		case WNDMGT_CALLBACK_CREATE:
+			wndmgt_callback_create_srv(srv, &call);
+			break;
+		case WNDMGT_GET_WINDOW_LIST:
+			wndmgt_get_window_list_srv(srv, &call);
+			break;
+		case WNDMGT_GET_WINDOW_INFO:
+			wndmgt_get_window_info_srv(srv, &call);
+			break;
+		case WNDMGT_ACTIVATE_WINDOW:
+			wndmgt_activate_window_srv(srv, &call);
+			break;
+		case WNDMGT_CLOSE_WINDOW:
+			wndmgt_close_window_srv(srv, &call);
+			break;
+		case WNDMGT_GET_EVENT:
+			wndmgt_get_event_srv(srv, &call);
+			break;
+		default:
+			async_answer_0(&call, ENOTSUP);
+		}
+	}
+
+	/* Hang up callback session */
+	if (srv->client_sess != NULL) {
+		async_hangup(srv->client_sess);
+		srv->client_sess = NULL;
+	}
+}
+
+/** Initialize window management server structure
+ *
+ * @param srv Window management server structure to initialize
+ */
+void wndmgt_srv_initialize(wndmgt_srv_t *srv)
+{
+	memset(srv, 0, sizeof(*srv));
+}
+
+/** Send 'pending' event to client.
+ *
+ * @param srv Window management server structure
+ */
+void wndmgt_srv_ev_pending(wndmgt_srv_t *srv)
+{
+	async_exch_t *exch;
+
+	exch = async_exchange_begin(srv->client_sess);
+	async_msg_0(exch, WNDMGT_EV_PENDING);
+	async_exchange_end(exch);
+}
+
+/** @}
+ */
Index: uspace/lib/wndmgt/test/main.c
===================================================================
--- uspace/lib/wndmgt/test/main.c	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
+++ uspace/lib/wndmgt/test/main.c	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
@@ -0,0 +1,35 @@
+/*
+ * 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 <pcut/pcut.h>
+
+PCUT_INIT;
+
+PCUT_IMPORT(wndmgt);
+
+PCUT_MAIN();
Index: uspace/lib/wndmgt/test/wndmgt.c
===================================================================
--- uspace/lib/wndmgt/test/wndmgt.c	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
+++ uspace/lib/wndmgt/test/wndmgt.c	(revision 0761448df8f6e5a4d681a24b634ffc5ddbf890ad)
@@ -0,0 +1,396 @@
+/*
+ * 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 <async.h>
+#include <errno.h>
+#include <wndmgt.h>
+#include <wndmgt_srv.h>
+#include <fibril_synch.h>
+#include <loc.h>
+#include <pcut/pcut.h>
+#include "../private/wndmgt.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(wndmgt);
+
+static const char *test_wndmgt_server = "test-wndmgt";
+static const char *test_wndmgt_svc = "test/wndmgt";
+
+static void test_wndmgt_conn(ipc_call_t *, void *);
+
+static errno_t test_get_window_list(void *, wndmgt_window_list_t **);
+static errno_t test_get_window_info(void *, sysarg_t, wndmgt_window_info_t **);
+static errno_t test_activate_window(void *, sysarg_t);
+static errno_t test_close_window(void *, sysarg_t);
+static errno_t test_get_event(void *, wndmgt_ev_t *);
+
+static void test_window_added(void *, sysarg_t);
+static void test_window_removed(void *, sysarg_t);
+
+static wndmgt_ops_t test_wndmgt_srv_ops = {
+	.get_window_list = test_get_window_list,
+	.get_window_info = test_get_window_info,
+	.activate_window = test_activate_window,
+	.close_window = test_close_window,
+	.get_event = test_get_event
+};
+
+static wndmgt_cb_t test_wndmgt_cb = {
+	.window_added = test_window_added,
+	.window_removed = test_window_removed
+};
+
+/** Describes to the server how to respond to our request and pass tracking
+ * data back to the client.
+ */
+typedef struct {
+	errno_t rc;
+	sysarg_t wnd_id;
+	wndmgt_ev_t event;
+	wndmgt_ev_t revent;
+	int event_cnt;
+
+	bool get_window_list_called;
+	wndmgt_window_list_t *get_window_list_rlist;
+
+	bool get_window_info_called;
+	wndmgt_window_info_t *get_window_info_rinfo;
+
+	bool activate_window_called;
+	sysarg_t activate_window_wnd_id;
+
+	bool close_window_called;
+	sysarg_t close_window_wnd_id;
+
+	bool get_event_called;
+
+	bool window_added_called;
+	sysarg_t window_added_wnd_id;
+
+	bool window_removed_called;
+	sysarg_t window_removed_wnd_id;
+
+	fibril_condvar_t event_cv;
+	fibril_mutex_t event_lock;
+	wndmgt_srv_t *srv;
+} test_response_t;
+
+/** wndmgt_open(), wndmgt_close() work for valid window management service */
+PCUT_TEST(open_close)
+{
+	errno_t rc;
+	service_id_t sid;
+	wndmgt_t *wndmgt = NULL;
+	test_response_t resp;
+
+	async_set_fallback_port_handler(test_wndmgt_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_wndmgt_server);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(test_wndmgt_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndmgt_open(test_wndmgt_svc, NULL, NULL, &wndmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wndmgt);
+
+	wndmgt_close(wndmgt);
+	rc = loc_service_unregister(sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+/** wndmgt_activate_window() with server returning error response works */
+PCUT_TEST(activate_window_failure)
+{
+	errno_t rc;
+	service_id_t sid;
+	wndmgt_t *wndmgt = NULL;
+	sysarg_t wnd_id;
+	test_response_t resp;
+
+	async_set_fallback_port_handler(test_wndmgt_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_wndmgt_server);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(test_wndmgt_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndmgt_open(test_wndmgt_svc, NULL, NULL, &wndmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wndmgt);
+
+	wnd_id = 42;
+	resp.rc = ENOMEM;
+	resp.activate_window_called = false;
+
+	rc = wndmgt_activate_window(wndmgt, wnd_id);
+	PCUT_ASSERT_TRUE(resp.activate_window_called);
+	PCUT_ASSERT_INT_EQUALS(wnd_id, resp.activate_window_wnd_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	wndmgt_close(wndmgt);
+	rc = loc_service_unregister(sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** wndmgt_activate_window() with server returning success response works */
+PCUT_TEST(activate_window_success)
+{
+	errno_t rc;
+	service_id_t sid;
+	wndmgt_t *wndmgt = NULL;
+	sysarg_t wnd_id;
+	test_response_t resp;
+
+	async_set_fallback_port_handler(test_wndmgt_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_wndmgt_server);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(test_wndmgt_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndmgt_open(test_wndmgt_svc, NULL, NULL, &wndmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wndmgt);
+
+	wnd_id = 42;
+	resp.rc = EOK;
+	resp.activate_window_called = false;
+
+	rc = wndmgt_activate_window(wndmgt, wnd_id);
+	PCUT_ASSERT_TRUE(resp.activate_window_called);
+	PCUT_ASSERT_INT_EQUALS(wnd_id, resp.activate_window_wnd_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	wndmgt_close(wndmgt);
+	rc = loc_service_unregister(sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Window added event can be delivered from server to client callback function */
+PCUT_TEST(window_added_event_deliver)
+{
+	errno_t rc;
+	service_id_t sid;
+	wndmgt_t *wndmgt = NULL;
+	test_response_t resp;
+
+	async_set_fallback_port_handler(test_wndmgt_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_wndmgt_server);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(test_wndmgt_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndmgt_open(test_wndmgt_svc, &test_wndmgt_cb, &resp, &wndmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wndmgt);
+	PCUT_ASSERT_NOT_NULL(resp.srv);
+
+	resp.event_cnt = 1;
+	resp.event.etype = wmev_window_added;
+	resp.event.wnd_id = 42;
+	resp.window_added_called = false;
+	fibril_mutex_initialize(&resp.event_lock);
+	fibril_condvar_initialize(&resp.event_cv);
+	wndmgt_srv_ev_pending(resp.srv);
+
+	/* Wait for the event handler to be called. */
+	fibril_mutex_lock(&resp.event_lock);
+	while (!resp.window_added_called) {
+		fibril_condvar_wait(&resp.event_cv, &resp.event_lock);
+	}
+	fibril_mutex_unlock(&resp.event_lock);
+
+	/* Verify that the event was delivered correctly */
+	PCUT_ASSERT_INT_EQUALS(resp.event.etype,
+	    resp.revent.etype);
+
+	wndmgt_close(wndmgt);
+
+	rc = loc_service_unregister(sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Window removed event can be delivered from server to client callback function */
+PCUT_TEST(window_removed_event_deliver)
+{
+	errno_t rc;
+	service_id_t sid;
+	wndmgt_t *wndmgt = NULL;
+	test_response_t resp;
+
+	async_set_fallback_port_handler(test_wndmgt_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_wndmgt_server);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(test_wndmgt_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndmgt_open(test_wndmgt_svc, &test_wndmgt_cb, &resp, &wndmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wndmgt);
+	PCUT_ASSERT_NOT_NULL(resp.srv);
+
+	resp.event_cnt = 1;
+	resp.event.etype = wmev_window_removed;
+	resp.event.wnd_id = 42;
+	resp.window_removed_called = false;
+	fibril_mutex_initialize(&resp.event_lock);
+	fibril_condvar_initialize(&resp.event_cv);
+	wndmgt_srv_ev_pending(resp.srv);
+
+	/* Wait for the event handler to be called. */
+	fibril_mutex_lock(&resp.event_lock);
+	while (!resp.window_removed_called) {
+		fibril_condvar_wait(&resp.event_cv, &resp.event_lock);
+	}
+	fibril_mutex_unlock(&resp.event_lock);
+
+	/* Verify that the event was delivered correctly */
+	PCUT_ASSERT_INT_EQUALS(resp.event.etype,
+	    resp.revent.etype);
+
+	wndmgt_close(wndmgt);
+
+	rc = loc_service_unregister(sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Test window management service connection. */
+static void test_wndmgt_conn(ipc_call_t *icall, void *arg)
+{
+	test_response_t *resp = (test_response_t *) arg;
+	wndmgt_srv_t srv;
+
+	/* Set up protocol structure */
+	wndmgt_srv_initialize(&srv);
+	srv.ops = &test_wndmgt_srv_ops;
+	srv.arg = arg;
+	resp->srv = &srv;
+
+	/* Handle connection */
+	wndmgt_conn(icall, &srv);
+
+	resp->srv = NULL;
+}
+
+static void test_window_added(void *arg, sysarg_t wnd_id)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->revent.etype = wmev_window_added;
+
+	fibril_mutex_lock(&resp->event_lock);
+	resp->window_added_called = true;
+	resp->window_added_wnd_id = wnd_id;
+	fibril_condvar_broadcast(&resp->event_cv);
+	fibril_mutex_unlock(&resp->event_lock);
+}
+
+static void test_window_removed(void *arg, sysarg_t wnd_id)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->revent.etype = wmev_window_removed;
+
+	fibril_mutex_lock(&resp->event_lock);
+	resp->window_removed_called = true;
+	resp->window_removed_wnd_id = wnd_id;
+	fibril_condvar_broadcast(&resp->event_cv);
+	fibril_mutex_unlock(&resp->event_lock);
+}
+
+static errno_t test_get_window_list(void *arg, wndmgt_window_list_t **rlist)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	if (resp->rc != EOK)
+		return resp->rc;
+
+	resp->get_window_list_called = true;
+	*rlist = resp->get_window_list_rlist;
+	return EOK;
+}
+
+static errno_t test_get_window_info(void *arg, sysarg_t wnd_id,
+    wndmgt_window_info_t **rinfo)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	if (resp->rc != EOK)
+		return resp->rc;
+
+	resp->get_window_info_called = true;
+	*rinfo = resp->get_window_info_rinfo;
+	return EOK;
+}
+
+static errno_t test_activate_window(void *arg, sysarg_t wnd_id)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->activate_window_called = true;
+	resp->activate_window_wnd_id = wnd_id;
+	return resp->rc;
+}
+
+static errno_t test_close_window(void *arg, sysarg_t wnd_id)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->close_window_called = true;
+	resp->close_window_wnd_id = wnd_id;
+	return resp->rc;
+}
+
+static errno_t test_get_event(void *arg, wndmgt_ev_t *event)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->get_event_called = true;
+	if (resp->event_cnt > 0) {
+		--resp->event_cnt;
+		*event = resp->event;
+		return EOK;
+	}
+
+	return ENOENT;
+}
+
+PCUT_EXPORT(wndmgt);
