Index: README.md
===================================================================
--- README.md	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ README.md	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -10,5 +10,5 @@
 extensible, fault tolerant and easy to understand.
 
-![screenshot](http://www.helenos.org/raw-attachment/wiki/Screenshots/newgui-aio.png "Screenshot")
+![screenshot](http://www.helenos.org/raw-attachment/wiki/Screenshots/gui-14.1-aio.png "Screenshot")
 
 HelenOS aims to be compatible with the C11 and C++14 standards, but does not
Index: abi/include/abi/ipc/interfaces.h
===================================================================
--- abi/include/abi/ipc/interfaces.h	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ abi/include/abi/ipc/interfaces.h	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2014 Martin Decky
  * All rights reserved.
@@ -201,4 +201,6 @@
 	INTERFACE_WNDMGT_CB =
 	    FOURCC_COMPACT('w', 'm', 'g', 't') | IFACE_EXCHANGE_SERIALIZE | IFACE_MOD_CALLBACK,
+	INTERFACE_TBARCFG_NOTIFY =
+	    FOURCC_COMPACT('t', 'b', 'c', 'f') | IFACE_EXCHANGE_SERIALIZE,
 	INTERFACE_PCAP_CONTROL =
 	    FOURCC_COMPACT('p', 'c', 't', 'l') | IFACE_EXCHANGE_SERIALIZE,
Index: defaults/ia64/Makefile.config
===================================================================
--- defaults/ia64/Makefile.config	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ defaults/ia64/Makefile.config	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -51,3 +51,3 @@
 
 # Optimization level
-OPTIMIZATION = 3
+OPTIMIZATION = 2
Index: meson/part/initrd_manifest/meson.build
===================================================================
--- meson/part/initrd_manifest/meson.build	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ meson/part/initrd_manifest/meson.build	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -68,5 +68,5 @@
 if CONFIG_FB
 	rd_essential += [
-		'app/launcher',
+		'app/taskbar',
 		'app/terminal',
 
Index: tools/export.sh
===================================================================
--- tools/export.sh	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ tools/export.sh	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -43,4 +43,5 @@
 	uspace/lib/libc.a \
 	uspace/lib/libcongfx.a \
+	uspace/lib/libconsole.a \
 	uspace/lib/libcpp.a \
 	uspace/lib/libdisplay.a \
@@ -48,8 +49,10 @@
 	uspace/lib/libgfxfont.a \
 	uspace/lib/libgfximage.a \
+	uspace/lib/libinput.a \
 	uspace/lib/libhound.a \
 	uspace/lib/libipcgfx.a \
 	uspace/lib/libmath.a \
 	uspace/lib/libmemgfx.a \
+	uspace/lib/liboutput.a \
 	uspace/lib/libpcm.a \
 	uspace/lib/libpixconv.a \
Index: tools/mkarray.py
===================================================================
--- tools/mkarray.py	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ tools/mkarray.py	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -39,8 +39,8 @@
 def usage(prname):
 	"Print usage syntax"
-	print("%s [--deflate] <DESTINATION> <LABEL> <AS_PROLOG> <SECTION> [SOURCE ...]" % prname)
+	print("%s [--deflate] <DESTINATION> <TYPENAME> <LABEL> <AS_PROLOG> <SECTION> [SOURCE ...]" % prname)
 
 def arg_check():
-	if (len(sys.argv) < 5):
+	if (len(sys.argv) < 6):
 		usage(sys.argv[0])
 		sys.exit()
@@ -66,7 +66,8 @@
 
 	dest = sys.argv[1]
-	label = sys.argv[2]
-	as_prolog = sys.argv[3]
-	section = sys.argv[4]
+	typename = sys.argv[2]
+	label = sys.argv[3]
+	as_prolog = sys.argv[4]
+	section = sys.argv[5]
 
 	timestamp = (1980, 1, 1, 0, 0, 0)
@@ -81,5 +82,5 @@
 	archive = zipfile.ZipFile("%s.zip" % dest, "w", zipfile.ZIP_STORED)
 
-	for src in sys.argv[5:]:
+	for src in sys.argv[6:]:
 		basename = os.path.basename(src)
 		plainname = os.path.splitext(basename)[0]
@@ -147,4 +148,6 @@
 	data += "#include <stdbool.h>\n\n"
 	data += "#define %sS  %u\n\n" % (label.upper(), src_cnt)
+	data += "#ifndef %sS_T_\n" % typename.upper()
+	data += "#define %sS_T_\n\n" % typename.upper()
 	data += "typedef struct {\n"
 	data += "\tconst char *name;\n"
@@ -153,6 +156,7 @@
 	data += "\tsize_t inflated;\n"
 	data += "\tbool compressed;\n"
-	data += "} %s_t;\n\n" % label
-	data += "extern %s_t %ss[];\n\n" % (label, label)
+	data += "} %s_t;\n\n" % typename
+	data += "#endif\n"
+	data += "extern %s_t %ss[];\n\n" % (typename, label)
 	data += "\n".join(header_ctx)
 	data += "\n\n"
@@ -179,5 +183,5 @@
 	data += ' */\n\n'
 	data += "#include \"%s.h\"\n\n" % dest
-	data += "%s_t %ss[] = {\n" % (label, label)
+	data += "%s_t %ss[] = {\n" % (typename, label)
 	data += ",\n".join(desc_ctx)
 	data += "\n"
Index: tools/mkarray_for_meson.sh
===================================================================
--- tools/mkarray_for_meson.sh	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ tools/mkarray_for_meson.sh	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -42,7 +42,8 @@
 _arg3="$4"
 _arg4="$5"
+_arg5="$6"
 _inputs=""
 
-shift 5
+shift 6
 
 for file in "$@"; do
@@ -51,3 +52,3 @@
 
 cd $_outdir
-$TOOLS_DIR/mkarray.py "$_arg1" "$_arg2" "$_arg3" "$_arg4" $_inputs > /dev/null
+$TOOLS_DIR/mkarray.py "$_arg1" "$_arg2" "$_arg3" "$_arg4" "$_arg5" $_inputs > /dev/null
Index: tools/xcw/bin/helenos-pkg-config
===================================================================
--- tools/xcw/bin/helenos-pkg-config	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ tools/xcw/bin/helenos-pkg-config	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -93,5 +93,5 @@
     config = args.parse_args()
 
-    export_dir = os.getenv('EXPORT_DIR', get_build_root())
+    export_dir = os.getenv('EXPORT_DIR', get_build_root() + '/export')
 
     result = []
Index: uspace/app/aboutos/aboutos.c
===================================================================
--- uspace/app/aboutos/aboutos.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
+++ uspace/app/aboutos/aboutos.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -0,0 +1,372 @@
+/*
+ * Copyright (c) 2024 Jiri Svoboda
+ * Copyright (c) 2012 Petr Koupy
+ * 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 aboutos
+ * @{
+ */
+/** @file About HelenOS
+ */
+
+#include <errno.h>
+#include <gfx/coord.h>
+#include <gfximage/tga.h>
+#include <macros.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <str.h>
+#include <str_error.h>
+#include <ui/fixed.h>
+#include <ui/image.h>
+#include <ui/label.h>
+#include <ui/pbutton.h>
+#include <ui/resource.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+
+#include "aboutos.h"
+#include "images.h"
+
+#define NAME  "aboutos"
+
+static void aboutos_wnd_close(ui_window_t *, void *);
+static void aboutos_wnd_kbd(ui_window_t *, void *, kbd_event_t *);
+
+static ui_window_cb_t window_cb = {
+	.close = aboutos_wnd_close,
+	.kbd = aboutos_wnd_kbd
+};
+
+static void pb_clicked(ui_pbutton_t *, void *);
+
+static ui_pbutton_cb_t pbutton_cb = {
+	.clicked = pb_clicked
+};
+
+/** Window close button was clicked.
+ *
+ * @param window Window
+ * @param arg Argument (aboutos)
+ */
+static void aboutos_wnd_close(ui_window_t *window, void *arg)
+{
+	aboutos_t *aboutos = (aboutos_t *) arg;
+
+	ui_quit(aboutos->ui);
+}
+
+/** About HelenOS window keyboard event handler.
+ *
+ * @param window Window
+ * @param arg Argument (ui_prompt_dialog_t *)
+ * @param event Keyboard event
+ */
+static void aboutos_wnd_kbd(ui_window_t *window, void *arg,
+    kbd_event_t *event)
+{
+	aboutos_t *aboutos = (aboutos_t *) arg;
+
+	(void)window;
+
+	if (event->type == KEY_PRESS &&
+	    (event->mods & (KM_CTRL | KM_SHIFT | KM_ALT)) == 0) {
+		if (event->key == KC_ENTER) {
+			/* Quit */
+			ui_quit(aboutos->ui);
+
+		}
+	}
+
+	ui_window_def_kbd(window, event);
+}
+
+/** Push button was clicked.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (aboutos)
+ */
+static void pb_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	aboutos_t *aboutos = (aboutos_t *) arg;
+
+	ui_quit(aboutos->ui);
+}
+
+static void print_syntax(void)
+{
+	printf("Syntax: %s [-d <display-spec>]\n", NAME);
+}
+
+int main(int argc, char *argv[])
+{
+	int i;
+	aboutos_t aboutos;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	ui_resource_t *ui_res;
+	gfx_bitmap_params_t logo_params;
+	gfx_bitmap_t *logo_bmp;
+	gfx_context_t *gc;
+	gfx_rect_t logo_rect;
+	gfx_rect_t rect;
+	gfx_coord2_t off;
+	const char *dspec = UI_ANY_DEFAULT;
+	errno_t rc;
+
+	i = 1;
+	while (i < argc) {
+		if (str_cmp(argv[i], "-d") == 0) {
+			++i;
+			if (i >= argc) {
+				printf("Argument missing.\n");
+				print_syntax();
+				return 1;
+			}
+
+			dspec = argv[i++];
+		} else {
+			printf("Invalid option '%s'.\n", argv[i]);
+			print_syntax();
+			return 1;
+		}
+	}
+
+	rc = ui_create(dspec, &ui);
+	if (rc != EOK) {
+		printf("Error creating UI on display %s.\n", dspec);
+		return rc;
+	}
+
+	ui_wnd_params_init(&params);
+	params.caption = "About HelenOS";
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		params.rect.p0.x = 0;
+		params.rect.p0.y = 0;
+		params.rect.p1.x = 45;
+		params.rect.p1.y = 15;
+	} else {
+		params.rect.p0.x = 0;
+		params.rect.p0.y = 0;
+		params.rect.p1.x = 350;
+		params.rect.p1.y = 275;
+	}
+
+	memset((void *) &aboutos, 0, sizeof(aboutos));
+	aboutos.ui = ui;
+
+	rc = ui_window_create(ui, &params, &window);
+	if (rc != EOK) {
+		printf("Error creating window.\n");
+		return rc;
+	}
+
+	ui_window_set_cb(window, &window_cb, (void *) &aboutos);
+	aboutos.window = window;
+
+	ui_res = ui_window_get_res(window);
+	gc = ui_window_get_gc(window);
+
+	rc = decode_tga(gc, (void *) helenos_tga, helenos_tga_size,
+	    &logo_bmp, &logo_rect);
+	if (rc != EOK) {
+		printf("Unable to decode logo.\n");
+		return 1;
+	}
+
+	gfx_bitmap_params_init(&logo_params);
+	logo_params.rect = logo_rect;
+
+	rc = ui_fixed_create(&aboutos.fixed);
+	if (rc != EOK) {
+		printf("Error creating fixed layout.\n");
+		return rc;
+	}
+
+	rc = ui_image_create(ui_res, logo_bmp, &logo_rect, &aboutos.image);
+	if (rc != EOK) {
+		printf("Error creating label.\n");
+		return rc;
+	}
+
+	off.x = 76;
+	off.y = 42;
+	gfx_rect_translate(&off, &logo_rect, &rect);
+
+	/* Adjust for frame width (2 x 1 pixel) */
+	rect.p1.x += 2;
+	rect.p1.y += 2;
+	ui_image_set_rect(aboutos.image, &rect);
+	ui_image_set_flags(aboutos.image, ui_imgf_frame);
+
+	rc = ui_fixed_add(aboutos.fixed, ui_image_ctl(aboutos.image));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		return rc;
+	}
+
+	/* Release label */
+
+	rc = ui_label_create(ui_res, "HelenOS " STRING(HELENOS_RELEASE)
+	    " (" STRING(HELENOS_CODENAME) ")", &aboutos.lrelease);
+	if (rc != EOK) {
+		printf("Error creating label.\n");
+		return rc;
+	}
+
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 1;
+		rect.p0.y = 5;
+		rect.p1.x = 44;
+		rect.p1.y = 6;
+	} else {
+		rect.p0.x = 10;
+		rect.p0.y = 140;
+		rect.p1.x = 340;
+		rect.p1.y = 160;
+	}
+
+	ui_label_set_rect(aboutos.lrelease, &rect);
+	ui_label_set_halign(aboutos.lrelease, gfx_halign_center);
+
+	rc = ui_fixed_add(aboutos.fixed, ui_label_ctl(aboutos.lrelease));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		return rc;
+	}
+
+	/* Copyright label */
+
+	rc = ui_label_create(ui_res, STRING(HELENOS_COPYRIGHT), &aboutos.lcopy);
+	if (rc != EOK) {
+		printf("Error creating label.\n");
+		return rc;
+	}
+
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 1;
+		rect.p0.y = 6;
+		rect.p1.x = 44;
+		rect.p1.y = 7;
+	} else {
+		rect.p0.x = 10;
+		rect.p0.y = 160;
+		rect.p1.x = 340;
+		rect.p1.y = 180;
+	}
+
+	ui_label_set_rect(aboutos.lcopy, &rect);
+	ui_label_set_halign(aboutos.lcopy, gfx_halign_center);
+
+	rc = ui_fixed_add(aboutos.fixed, ui_label_ctl(aboutos.lcopy));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		return rc;
+	}
+
+	/* Architecture label */
+
+	rc = ui_label_create(ui_res, "Running on " STRING(UARCH), &aboutos.larch);
+	if (rc != EOK) {
+		printf("Error creating label.\n");
+		return rc;
+	}
+
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 1;
+		rect.p0.y = 9;
+		rect.p1.x = 44;
+		rect.p1.y = 10;
+	} else {
+		rect.p0.x = 10;
+		rect.p0.y = 190;
+		rect.p1.x = 340;
+		rect.p1.y = 210;
+	}
+
+	ui_label_set_rect(aboutos.larch, &rect);
+	ui_label_set_halign(aboutos.larch, gfx_halign_center);
+
+	rc = ui_fixed_add(aboutos.fixed, ui_label_ctl(aboutos.larch));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		return rc;
+	}
+
+	/* OK button */
+
+	rc = ui_pbutton_create(ui_res, "OK", &aboutos.pbok);
+	if (rc != EOK) {
+		printf("Error creating button.\n");
+		return rc;
+	}
+
+	ui_pbutton_set_cb(aboutos.pbok, &pbutton_cb, (void *) &aboutos);
+
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 17;
+		rect.p0.y = 13;
+		rect.p1.x = 28;
+		rect.p1.y = 14;
+	} else {
+		rect.p0.x = 125;
+		rect.p0.y = 235;
+		rect.p1.x = 225;
+		rect.p1.y = rect.p0.y + 28;
+	}
+
+	ui_pbutton_set_rect(aboutos.pbok, &rect);
+	ui_pbutton_set_default(aboutos.pbok, true);
+
+	rc = ui_fixed_add(aboutos.fixed, ui_pbutton_ctl(aboutos.pbok));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		return rc;
+	}
+
+	ui_window_add(window, ui_fixed_ctl(aboutos.fixed));
+
+	rc = ui_window_paint(window);
+	if (rc != EOK) {
+		printf("Error painting window.\n");
+		return rc;
+	}
+
+	ui_run(ui);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+
+	return 0;
+}
+
+/** @}
+ */
Index: uspace/app/aboutos/aboutos.h
===================================================================
--- uspace/app/aboutos/aboutos.h	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
+++ uspace/app/aboutos/aboutos.h	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2024 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 aboutos
+ * @{
+ */
+/**
+ * @file About HelenOS
+ */
+
+#ifndef ABOUTOS_H
+#define ABOUTOS_H
+
+#include <ui/fixed.h>
+#include <ui/image.h>
+#include <ui/label.h>
+#include <ui/pbutton.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+
+/** About HelenOS */
+typedef struct {
+	ui_t *ui;
+	ui_window_t *window;
+	ui_fixed_t *fixed;
+
+	ui_image_t *image;
+	ui_label_t *lrelease;
+	ui_label_t *lcopy;
+	ui_label_t *larch;
+
+	ui_pbutton_t *pbok;
+} aboutos_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/aboutos/doc/doxygroups.h
===================================================================
--- uspace/app/aboutos/doc/doxygroups.h	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
+++ uspace/app/aboutos/doc/doxygroups.h	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -0,0 +1,4 @@
+/** @addtogroup aboutos aboutos
+ * @brief About HelenOS
+ * @ingroup apps
+ */
Index: uspace/app/aboutos/meson.build
===================================================================
--- uspace/app/aboutos/meson.build	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
+++ uspace/app/aboutos/meson.build	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -0,0 +1,59 @@
+#
+# Copyright (c) 2024 Jiri Svoboda
+# Copyright (c) 2012 Petr Koupy
+# 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.
+#
+
+deps = [ 'gfximage', 'ui' ]
+
+_images = files('gfx/helenos.tga')
+
+_images_zip = custom_target('aboutos_images.zip',
+	input : _images,
+	output : [ 'images.zip' ],
+	command : [ mkarray, '@OUTDIR@', 'images', 'image', 'image', uspace_as_prolog, '.data', '@INPUT@' ],
+)
+_imgs_s = custom_target('aboutos_images.s',
+	input : _images_zip,
+	output : [ 'images.s' ],
+	command : [ unzip, '-p', '@INPUT@', 'images.s' ],
+	capture : true,
+)
+_imgs_h = custom_target('aboutos_images.h',
+	input : _images_zip,
+	output : [ 'images.h' ],
+	command : [ unzip, '-p', '@INPUT@', 'images.h' ],
+	capture : true,
+)
+_imgs_desc_c = custom_target('aboutos_images_desc.c',
+	input : _images_zip,
+	output : [ 'images_desc.c' ],
+	command : [ unzip, '-p', '@INPUT@', 'images_desc.c' ],
+	capture : true,
+)
+
+c_args += [ '-DHELENOS_RELEASE=' + HELENOS_RELEASE, '-DHELENOS_COPYRIGHT=' + HELENOS_COPYRIGHT, '-DHELENOS_CODENAME=' + HELENOS_CODENAME ]
+src = [ files('aboutos.c'), _imgs_s, _imgs_h, _imgs_desc_c ]
Index: uspace/app/barber/barber.c
===================================================================
--- uspace/app/barber/barber.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/barber/barber.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2014 Martin Decky
  * All rights reserved.
@@ -50,4 +50,5 @@
 #include <ui/image.h>
 #include "images.h"
+#include "images_tiny.h"
 
 #define NAME  "barber"
@@ -60,7 +61,4 @@
 #define MIN_LOAD  (LOAD_UNIT / 4)
 #define MAX_LOAD  (LOAD_UNIT / 3)
-
-#define FRAME_WIDTH   59
-#define FRAME_HEIGHT  192
 
 #define LED_PERIOD  1000000
@@ -97,4 +95,6 @@
 static unsigned int frame = 0;
 static unsigned int fps = MIN_FPS;
+static gfx_coord_t frame_width;
+static gfx_coord_t frame_height;
 
 static void led_timer_callback(void *);
@@ -102,7 +102,9 @@
 
 static void wnd_close(ui_window_t *, void *);
+static void wnd_kbd_event(ui_window_t *, void *, kbd_event_t *);
 
 static ui_window_cb_t window_cb = {
-	.close = wnd_close
+	.close = wnd_close,
+	.kbd = wnd_kbd_event
 };
 
@@ -110,5 +112,5 @@
  *
  * @param window Window
- * @param arg Argument (launcher)
+ * @param arg Argument (barber)
  */
 static void wnd_close(ui_window_t *window, void *arg)
@@ -119,5 +121,50 @@
 }
 
-static bool decode_frames(gfx_context_t *gc)
+/** Barber unmodified key press.
+ *
+ * @param barber Barber
+ * @param event Keyboard event
+ */
+static void barber_kbd_event_unmod(barber_t *barber, kbd_event_t *event)
+{
+	if (event->key == KC_ESCAPE)
+		ui_quit(barber->ui);
+}
+
+/** Barber ctrl-key key press.
+ *
+ * @param barber Barber
+ * @param event Keyboard event
+ */
+static void barber_kbd_event_ctrl(barber_t *barber, kbd_event_t *event)
+{
+	if (event->key == KC_Q)
+		ui_quit(barber->ui);
+}
+
+/** Barber window keyboard event.
+ *
+ * @param window UI window
+ * @param arg Argument (barber_t *)
+ * @param event Keyboard event
+ */
+static void wnd_kbd_event(ui_window_t *window, void *arg, kbd_event_t *event)
+{
+	barber_t *barber = (barber_t *)arg;
+
+	if (event->type != KEY_PRESS)
+		return;
+
+	if ((event->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0)
+		barber_kbd_event_unmod(barber, event);
+
+	if ((event->mods & KM_CTRL) != 0 &&
+	    (event->mods & (KM_ALT | KM_SHIFT)) == 0)
+		barber_kbd_event_ctrl(barber, event);
+
+	ui_window_def_kbd(window, event);
+}
+
+static bool decode_frames(gfx_context_t *gc, image_t *img)
 {
 	gfx_rect_t rect;
@@ -125,5 +172,5 @@
 
 	for (unsigned int i = 0; i < FRAMES; i++) {
-		rc = decode_tga_gz(gc, images[i].addr, images[i].size,
+		rc = decode_tga_gz(gc, img[i].addr, img[i].size,
 		    &frame_bmp[i], &rect);
 		if (rc != EOK) {
@@ -132,5 +179,5 @@
 		}
 
-		(void) rect;
+		(void)rect;
 	}
 
@@ -238,6 +285,6 @@
 	rect.p0.x = 0;
 	rect.p0.y = 0;
-	rect.p1.x = FRAME_WIDTH;
-	rect.p1.y = FRAME_HEIGHT;
+	rect.p1.x = frame_width;
+	rect.p1.y = frame_height;
 
 	ui_image_set_bmp(frame_img, frame_bmp[frame], &rect);
@@ -299,5 +346,5 @@
 int main(int argc, char *argv[])
 {
-	const char *display_spec = UI_DISPLAY_DEFAULT;
+	const char *display_spec = UI_ANY_DEFAULT;
 	barber_t barber;
 	ui_t *ui;
@@ -310,4 +357,5 @@
 	gfx_context_t *gc;
 	gfx_coord2_t off;
+	image_t *img;
 	int i;
 
@@ -355,8 +403,16 @@
 	}
 
+	if (ui_is_textmode(ui)) {
+		frame_width = 10;
+		frame_height = 16;
+	} else {
+		frame_width = 59;
+		frame_height = 192;
+	}
+
 	rect.p0.x = 0;
 	rect.p0.y = 0;
-	rect.p1.x = FRAME_WIDTH;
-	rect.p1.y = FRAME_HEIGHT;
+	rect.p1.x = frame_width;
+	rect.p1.y = frame_height;
 
 	ui_wnd_params_init(&params);
@@ -367,5 +423,5 @@
 	 * to rect
 	 */
-	ui_wdecor_rect_from_app(params.style, &rect, &wrect);
+	ui_wdecor_rect_from_app(ui, params.style, &rect, &wrect);
 	off = wrect.p0;
 	gfx_rect_rtranslate(&off, &wrect, &params.rect);
@@ -384,5 +440,7 @@
 	ui_window_set_cb(window, &window_cb, (void *) &barber);
 
-	if (!decode_frames(gc))
+	img = ui_is_textmode(ui) ? image_tinys : images;
+
+	if (!decode_frames(gc, img))
 		return 1;
 
Index: uspace/app/barber/meson.build
===================================================================
--- uspace/app/barber/meson.build	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/barber/meson.build	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -62,4 +62,37 @@
 )
 
+_images_tiny = files(
+	'gfx-tiny/frame01t.tga.gz',
+	'gfx-tiny/frame02t.tga.gz',
+	'gfx-tiny/frame03t.tga.gz',
+	'gfx-tiny/frame04t.tga.gz',
+	'gfx-tiny/frame05t.tga.gz',
+	'gfx-tiny/frame06t.tga.gz',
+	'gfx-tiny/frame07t.tga.gz',
+	'gfx-tiny/frame08t.tga.gz',
+	'gfx-tiny/frame09t.tga.gz',
+	'gfx-tiny/frame10t.tga.gz',
+	'gfx-tiny/frame11t.tga.gz',
+	'gfx-tiny/frame12t.tga.gz',
+	'gfx-tiny/frame13t.tga.gz',
+	'gfx-tiny/frame14t.tga.gz',
+	'gfx-tiny/frame15t.tga.gz',
+	'gfx-tiny/frame16t.tga.gz',
+	'gfx-tiny/frame17t.tga.gz',
+	'gfx-tiny/frame18t.tga.gz',
+	'gfx-tiny/frame19t.tga.gz',
+	'gfx-tiny/frame20t.tga.gz',
+	'gfx-tiny/frame21t.tga.gz',
+	'gfx-tiny/frame22t.tga.gz',
+	'gfx-tiny/frame23t.tga.gz',
+	'gfx-tiny/frame24t.tga.gz',
+	'gfx-tiny/frame25t.tga.gz',
+	'gfx-tiny/frame26t.tga.gz',
+	'gfx-tiny/frame27t.tga.gz',
+	'gfx-tiny/frame28t.tga.gz',
+	'gfx-tiny/frame29t.tga.gz',
+	'gfx-tiny/frame30t.tga.gz',
+)
+
 _tarball = custom_target('barber_images.tar',
 	input: _images,
@@ -70,8 +103,10 @@
 # TODO
 
+# Normal images
+
 _images_zip = custom_target('barber_images.zip',
 	input : _images,
 	output : [ 'images.zip' ],
-	command : [ mkarray, '@OUTDIR@', 'images', 'image', uspace_as_prolog, '.data', '@INPUT@' ],
+	command : [ mkarray, '@OUTDIR@', 'images', 'image', 'image', uspace_as_prolog, '.data', '@INPUT@' ],
 )
 _imgs_s = custom_target('barber_images.s',
@@ -94,3 +129,30 @@
 )
 
-src = [ files('barber.c'), _imgs_s, _imgs_h, _imgs_desc_c ]
+# Tiny images
+
+_images_tiny_zip = custom_target('barber_images_tiny.zip',
+	input : _images_tiny,
+	output : [ 'images_tiny.zip' ],
+	command : [ mkarray, '@OUTDIR@', 'images_tiny', 'image', 'image_tiny', uspace_as_prolog, '.data', '@INPUT@' ],
+)
+_imgs_tiny_s = custom_target('barber_images_tiny.s',
+	input : _images_tiny_zip,
+	output : [ 'images_tiny.s' ],
+	command : [ unzip, '-p', '@INPUT@', 'images_tiny.s' ],
+	capture : true,
+)
+_imgs_tiny_h = custom_target('barber_images_tiny.h',
+	input : _images_tiny_zip,
+	output : [ 'images_tiny.h' ],
+	command : [ unzip, '-p', '@INPUT@', 'images_tiny.h' ],
+	capture : true,
+)
+_imgs_tiny_desc_c = custom_target('barber_images_tiny_desc.c',
+	input : _images_tiny_zip,
+	output : [ 'images_tiny_desc.c' ],
+	command : [ unzip, '-p', '@INPUT@', 'images_tiny_desc.c' ],
+	capture : true,
+)
+
+src = [ files('barber.c'), _imgs_s, _imgs_h, _imgs_desc_c,
+    _imgs_tiny_s, _imgs_tiny_h, _imgs_tiny_desc_c ]
Index: uspace/app/fontedit/fontedit.c
===================================================================
--- uspace/app/fontedit/fontedit.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/fontedit/fontedit.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2022 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -787,5 +787,5 @@
 	 * to rect
 	 */
-	ui_wdecor_rect_from_app(params.style, &rect, &wrect);
+	ui_wdecor_rect_from_app(ui, params.style, &rect, &wrect);
 	off = wrect.p0;
 	gfx_rect_rtranslate(&off, &wrect, &params.rect);
Index: uspace/app/gfxdemo/gfxdemo.c
===================================================================
--- uspace/app/gfxdemo/gfxdemo.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/gfxdemo/gfxdemo.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -393,5 +393,5 @@
 		for (j = 0; j < h; j++) {
 			pixelmap_put_pixel(&pixelmap, i, j,
-			    PIXEL(0, (i % 30) < 3 ? 255 : 0,
+			    PIXEL(255, (i % 30) < 3 ? 255 : 0,
 			    (j % 30) < 3 ? 255 : 0, i / 2));
 		}
@@ -429,5 +429,5 @@
 			k = i * i + j * j;
 			pixelmap_put_pixel(&pixelmap, i, j,
-			    PIXEL(0, k, k, k));
+			    PIXEL(255, k, k, k));
 		}
 	}
@@ -464,6 +464,6 @@
 			k = i * i + j * j;
 			pixelmap_put_pixel(&pixelmap, i, j,
-			    k < w * w / 2 ? PIXEL(0, 0, 255, 0) :
-			    PIXEL(0, 255, 0, 255));
+			    k < w * w / 2 ? PIXEL(255, 0, 255, 0) :
+			    PIXEL(255, 255, 0, 255));
 		}
 	}
@@ -621,5 +621,5 @@
 	params.rect.p1.y = 40;
 	params.flags = bmpf_color_key;
-	params.key_color = PIXEL(0, 255, 0, 255);
+	params.key_color = PIXEL(255, 255, 0, 255);
 
 	rc = gfx_bitmap_create(gc, &params, NULL, &bitmap);
@@ -1160,5 +1160,5 @@
 	 * to rect
 	 */
-	ui_wdecor_rect_from_app(params.style, &rect, &wrect);
+	ui_wdecor_rect_from_app(ui, params.style, &rect, &wrect);
 	off = wrect.p0;
 	gfx_rect_rtranslate(&off, &wrect, &params.rect);
Index: uspace/app/init/init.c
===================================================================
--- uspace/app/init/init.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/init/init.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -476,5 +476,4 @@
 		rc = display_server();
 		if (rc == EOK) {
-			app_start("/app/launcher", NULL);
 			app_start("/app/taskbar", NULL);
 			app_start("/app/terminal", "-topleft");
Index: pace/app/launcher/doc/doxygroups.h
===================================================================
--- uspace/app/launcher/doc/doxygroups.h	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ 	(revision )
@@ -1,4 +1,0 @@
-/** @addtogroup launcher launcher
- * @brief Application launcher
- * @ingroup apps
- */
Index: pace/app/launcher/launcher.c
===================================================================
--- uspace/app/launcher/launcher.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ 	(revision )
@@ -1,488 +1,0 @@
-/*
- * Copyright (c) 2023 Jiri Svoboda
- * Copyright (c) 2012 Petr Koupy
- * 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 launcher
- * @{
- */
-/** @file Launcher
- */
-
-#include <errno.h>
-#include <gfx/coord.h>
-#include <gfximage/tga.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <str.h>
-#include <str_error.h>
-#include <task.h>
-#include <ui/fixed.h>
-#include <ui/image.h>
-#include <ui/label.h>
-#include <ui/pbutton.h>
-#include <ui/resource.h>
-#include <ui/ui.h>
-#include <ui/window.h>
-
-#include "images.h"
-#include "launcher.h"
-
-#define NAME  "launcher"
-
-static const char *display_spec = UI_DISPLAY_DEFAULT;
-
-static void wnd_close(ui_window_t *, void *);
-static void wnd_pos(ui_window_t *, void *, pos_event_t *);
-
-static ui_window_cb_t window_cb = {
-	.close = wnd_close,
-	.pos = wnd_pos
-};
-
-static void pb_clicked(ui_pbutton_t *, void *);
-
-static ui_pbutton_cb_t pbutton_cb = {
-	.clicked = pb_clicked
-};
-
-static int app_launchl(launcher_t *, const char *, ...);
-
-/** Window close button was clicked.
- *
- * @param window Window
- * @param arg Argument (launcher)
- */
-static void wnd_close(ui_window_t *window, void *arg)
-{
-	launcher_t *launcher = (launcher_t *) arg;
-
-	ui_quit(launcher->ui);
-}
-
-/** Window received position event.
- *
- * @param window Window
- * @param arg Argument (launcher)
- * @param event Position event
- */
-static void wnd_pos(ui_window_t *window, void *arg, pos_event_t *event)
-{
-	launcher_t *launcher = (launcher_t *) arg;
-
-	/* Remember ID of device that sent the last event */
-	launcher->ev_pos_id = event->pos_id;
-
-	ui_window_def_pos(window, event);
-}
-
-/** Push button was clicked.
- *
- * @param pbutton Push button
- * @param arg Argument (launcher)
- */
-static void pb_clicked(ui_pbutton_t *pbutton, void *arg)
-{
-	launcher_t *launcher = (launcher_t *) arg;
-
-	if (pbutton == launcher->pb1) {
-		app_launchl(launcher, "/app/terminal", "-c", "/app/nav", NULL);
-	} else if (pbutton == launcher->pb2) {
-		app_launchl(launcher, "/app/terminal", "-c", "/app/edit", NULL);
-	} else if (pbutton == launcher->pb3) {
-		app_launchl(launcher, "/app/terminal", NULL);
-	} else if (pbutton == launcher->pb4) {
-		app_launchl(launcher, "/app/calculator", NULL);
-	} else if (pbutton == launcher->pb5) {
-		app_launchl(launcher, "/app/uidemo", NULL);
-	} else if (pbutton == launcher->pb6) {
-		app_launchl(launcher, "/app/gfxdemo", "ui", NULL);
-	}
-}
-
-static int app_launchl(launcher_t *launcher, const char *app, ...)
-{
-	errno_t rc;
-	task_id_t id;
-	task_wait_t wait;
-	va_list ap;
-	const char *arg;
-	const char **argv;
-	const char **argp;
-	char *dspec;
-	int cnt = 0;
-	int i;
-	int rv;
-
-	va_start(ap, app);
-	do {
-		arg = va_arg(ap, const char *);
-		cnt++;
-	} while (arg != NULL);
-	va_end(ap);
-
-	argv = calloc(cnt + 4, sizeof(const char *));
-	if (argv == NULL)
-		return -1;
-
-	task_exit_t texit;
-	int retval;
-
-	argp = argv;
-	*argp++ = app;
-
-	rv = asprintf(&dspec, "%s?idev=%zu", display_spec,
-	    (size_t)launcher->ev_pos_id);
-	if (rv < 0) {
-		printf("Out of memory.\n");
-		return -1;
-	}
-
-	/* TODO Might be omitted if default display AND only one seat */
-	*argp++ = "-d";
-	*argp++ = dspec;
-
-	va_start(ap, app);
-	do {
-		arg = va_arg(ap, const char *);
-		*argp++ = arg;
-	} while (arg != NULL);
-	va_end(ap);
-
-	*argp++ = NULL;
-
-	printf("%s: Spawning %s", NAME, app);
-	for (i = 0; argv[i] != NULL; i++) {
-		printf(" %s", argv[i]);
-	}
-	printf("\n");
-
-	rc = task_spawnv(&id, &wait, app, argv);
-	if (rc != EOK) {
-		printf("%s: Error spawning %s (%s)\n", NAME, app, str_error(rc));
-		return -1;
-	}
-
-	rc = task_wait(&wait, &texit, &retval);
-	if ((rc != EOK) || (texit != TASK_EXIT_NORMAL)) {
-		printf("%s: Error retrieving retval from %s (%s)\n", NAME,
-		    app, str_error(rc));
-		return -1;
-	}
-
-	return retval;
-}
-
-static void print_syntax(void)
-{
-	printf("Syntax: %s [-d <display-spec>]\n", NAME);
-}
-
-int main(int argc, char *argv[])
-{
-	int i;
-	launcher_t launcher;
-	ui_t *ui = NULL;
-	ui_wnd_params_t params;
-	ui_window_t *window = NULL;
-	ui_resource_t *ui_res;
-	gfx_bitmap_params_t logo_params;
-	gfx_bitmap_t *logo_bmp;
-	gfx_context_t *gc;
-	gfx_rect_t logo_rect;
-	gfx_rect_t rect;
-	gfx_coord2_t off;
-	const char *dspec = UI_DISPLAY_DEFAULT;
-	char *qmark;
-	errno_t rc;
-
-	i = 1;
-	while (i < argc) {
-		if (str_cmp(argv[i], "-d") == 0) {
-			++i;
-			if (i >= argc) {
-				printf("Argument missing.\n");
-				print_syntax();
-				return 1;
-			}
-
-			dspec = argv[i++];
-		} else {
-			printf("Invalid option '%s'.\n", argv[i]);
-			print_syntax();
-			return 1;
-		}
-	}
-
-	display_spec = str_dup(dspec);
-	if (display_spec == NULL) {
-		printf("Out of memory.\n");
-		return 1;
-	}
-
-	/* Remove additional arguments */
-	qmark = str_chr(display_spec, '?');
-	if (qmark != NULL)
-		*qmark = '\0';
-
-	rc = ui_create(dspec, &ui);
-	if (rc != EOK) {
-		printf("Error creating UI on display %s.\n", display_spec);
-		return rc;
-	}
-
-	ui_wnd_params_init(&params);
-	params.caption = "Launcher";
-	params.placement = ui_wnd_place_top_right;
-	params.rect.p0.x = 0;
-	params.rect.p0.y = 0;
-	params.rect.p1.x = 210;
-	params.rect.p1.y = 345;
-
-	memset((void *) &launcher, 0, sizeof(launcher));
-	launcher.ui = ui;
-
-	rc = ui_window_create(ui, &params, &window);
-	if (rc != EOK) {
-		printf("Error creating window.\n");
-		return rc;
-	}
-
-	ui_window_set_cb(window, &window_cb, (void *) &launcher);
-	launcher.window = window;
-
-	ui_res = ui_window_get_res(window);
-	gc = ui_window_get_gc(window);
-
-	rc = decode_tga(gc, (void *) helenos_tga, helenos_tga_size,
-	    &logo_bmp, &logo_rect);
-	if (rc != EOK) {
-		printf("Unable to decode logo.\n");
-		return 1;
-	}
-
-	gfx_bitmap_params_init(&logo_params);
-	logo_params.rect = logo_rect;
-
-	rc = ui_fixed_create(&launcher.fixed);
-	if (rc != EOK) {
-		printf("Error creating fixed layout.\n");
-		return rc;
-	}
-
-	rc = ui_image_create(ui_res, logo_bmp, &logo_rect, &launcher.image);
-	if (rc != EOK) {
-		printf("Error creating label.\n");
-		return rc;
-	}
-
-	off.x = 6;
-	off.y = 32;
-	gfx_rect_translate(&off, &logo_rect, &rect);
-
-	/* Adjust for frame width (2 x 1 pixel) */
-	rect.p1.x += 2;
-	rect.p1.y += 2;
-	ui_image_set_rect(launcher.image, &rect);
-	ui_image_set_flags(launcher.image, ui_imgf_frame);
-
-	rc = ui_fixed_add(launcher.fixed, ui_image_ctl(launcher.image));
-	if (rc != EOK) {
-		printf("Error adding control to layout.\n");
-		return rc;
-	}
-
-	rc = ui_label_create(ui_res, "Launch application", &launcher.label);
-	if (rc != EOK) {
-		printf("Error creating label.\n");
-		return rc;
-	}
-
-	rect.p0.x = 60;
-	rect.p0.y = 107;
-	rect.p1.x = 160;
-	rect.p1.y = 120;
-	ui_label_set_rect(launcher.label, &rect);
-	ui_label_set_halign(launcher.label, gfx_halign_center);
-
-	rc = ui_fixed_add(launcher.fixed, ui_label_ctl(launcher.label));
-	if (rc != EOK) {
-		printf("Error adding control to layout.\n");
-		return rc;
-	}
-
-	/* Navigator */
-
-	rc = ui_pbutton_create(ui_res, "Navigator", &launcher.pb1);
-	if (rc != EOK) {
-		printf("Error creating button.\n");
-		return rc;
-	}
-
-	ui_pbutton_set_cb(launcher.pb1, &pbutton_cb, (void *) &launcher);
-
-	rect.p0.x = 15;
-	rect.p0.y = 130;
-	rect.p1.x = 190;
-	rect.p1.y = rect.p0.y + 28;
-	ui_pbutton_set_rect(launcher.pb1, &rect);
-
-	rc = ui_fixed_add(launcher.fixed, ui_pbutton_ctl(launcher.pb1));
-	if (rc != EOK) {
-		printf("Error adding control to layout.\n");
-		return rc;
-	}
-
-	/* Text Editor */
-
-	rc = ui_pbutton_create(ui_res, "Text Editor", &launcher.pb2);
-	if (rc != EOK) {
-		printf("Error creating button.\n");
-		return rc;
-	}
-
-	ui_pbutton_set_cb(launcher.pb2, &pbutton_cb, (void *) &launcher);
-
-	rect.p0.x = 15;
-	rect.p0.y = 130 + 35;
-	rect.p1.x = 190;
-	rect.p1.y = rect.p0.y + 28;
-	ui_pbutton_set_rect(launcher.pb2, &rect);
-
-	rc = ui_fixed_add(launcher.fixed, ui_pbutton_ctl(launcher.pb2));
-	if (rc != EOK) {
-		printf("Error adding control to layout.\n");
-		return rc;
-	}
-
-	/* Terminal */
-
-	rc = ui_pbutton_create(ui_res, "Terminal", &launcher.pb3);
-	if (rc != EOK) {
-		printf("Error creating button.\n");
-		return rc;
-	}
-
-	ui_pbutton_set_cb(launcher.pb3, &pbutton_cb, (void *) &launcher);
-
-	rect.p0.x = 15;
-	rect.p0.y = 130 + 2 * 35;
-	rect.p1.x = 190;
-	rect.p1.y = rect.p0.y + 28;
-	ui_pbutton_set_rect(launcher.pb3, &rect);
-
-	rc = ui_fixed_add(launcher.fixed, ui_pbutton_ctl(launcher.pb3));
-	if (rc != EOK) {
-		printf("Error adding control to layout.\n");
-		return rc;
-	}
-
-	/* Calculator */
-
-	rc = ui_pbutton_create(ui_res, "Calculator", &launcher.pb4);
-	if (rc != EOK) {
-		printf("Error creating button.\n");
-		return rc;
-	}
-
-	ui_pbutton_set_cb(launcher.pb4, &pbutton_cb, (void *) &launcher);
-
-	rect.p0.x = 15;
-	rect.p0.y = 130 + 3 * 35;
-	rect.p1.x = 190;
-	rect.p1.y = rect.p0.y + 28;
-	ui_pbutton_set_rect(launcher.pb4, &rect);
-
-	rc = ui_fixed_add(launcher.fixed, ui_pbutton_ctl(launcher.pb4));
-	if (rc != EOK) {
-		printf("Error adding control to layout.\n");
-		return rc;
-	}
-
-	/* UI Demo */
-
-	rc = ui_pbutton_create(ui_res, "UI Demo", &launcher.pb5);
-	if (rc != EOK) {
-		printf("Error creating button.\n");
-		return rc;
-	}
-
-	ui_pbutton_set_cb(launcher.pb5, &pbutton_cb, (void *) &launcher);
-
-	rect.p0.x = 15;
-	rect.p0.y = 130 + 4 * 35;
-	rect.p1.x = 190;
-	rect.p1.y = rect.p0.y + 28;
-	ui_pbutton_set_rect(launcher.pb5, &rect);
-
-	rc = ui_fixed_add(launcher.fixed, ui_pbutton_ctl(launcher.pb5));
-	if (rc != EOK) {
-		printf("Error adding control to layout.\n");
-		return rc;
-	}
-
-	/* GFX Demo */
-
-	rc = ui_pbutton_create(ui_res, "GFX Demo", &launcher.pb6);
-	if (rc != EOK) {
-		printf("Error creating button.\n");
-		return rc;
-	}
-
-	ui_pbutton_set_cb(launcher.pb6, &pbutton_cb, (void *) &launcher);
-
-	rect.p0.x = 15;
-	rect.p0.y = 130 + 5 * 35;
-	rect.p1.x = 190;
-	rect.p1.y = rect.p0.y + 28;
-	ui_pbutton_set_rect(launcher.pb6, &rect);
-
-	rc = ui_fixed_add(launcher.fixed, ui_pbutton_ctl(launcher.pb6));
-	if (rc != EOK) {
-		printf("Error adding control to layout.\n");
-		return rc;
-	}
-
-	ui_window_add(window, ui_fixed_ctl(launcher.fixed));
-
-	rc = ui_window_paint(window);
-	if (rc != EOK) {
-		printf("Error painting window.\n");
-		return rc;
-	}
-
-	ui_run(ui);
-
-	ui_window_destroy(window);
-	ui_destroy(ui);
-
-	return 0;
-}
-
-/** @}
- */
Index: pace/app/launcher/launcher.h
===================================================================
--- uspace/app/launcher/launcher.h	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ 	(revision )
@@ -1,71 +1,0 @@
-/*
- * Copyright (c) 2023 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 launcher
- * @{
- */
-/**
- * @file Launcher
- */
-
-#ifndef LAUNCHER_H
-#define LAUNCHER_H
-
-#include <display.h>
-#include <types/common.h>
-#include <ui/fixed.h>
-#include <ui/image.h>
-#include <ui/label.h>
-#include <ui/pbutton.h>
-#include <ui/ui.h>
-#include <ui/window.h>
-
-/** Launcher */
-typedef struct {
-	ui_t *ui;
-	ui_window_t *window;
-	ui_fixed_t *fixed;
-
-	ui_image_t *image;
-	ui_label_t *label;
-
-	ui_pbutton_t *pb1;
-	ui_pbutton_t *pb2;
-	ui_pbutton_t *pb3;
-	ui_pbutton_t *pb4;
-	ui_pbutton_t *pb5;
-	ui_pbutton_t *pb6;
-
-	/** ID of device that sent last position event */
-	sysarg_t ev_pos_id;
-} launcher_t;
-
-#endif
-
-/** @}
- */
Index: pace/app/launcher/meson.build
===================================================================
--- uspace/app/launcher/meson.build	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ 	(revision )
@@ -1,57 +1,0 @@
-#
-# Copyright (c) 2012 Petr Koupy
-# 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.
-#
-
-deps = [ 'gfximage', 'ui' ]
-
-_images = files('gfx/helenos.tga')
-
-_images_zip = custom_target('launcher_images.zip',
-	input : _images,
-	output : [ 'images.zip' ],
-	command : [ mkarray, '@OUTDIR@', 'images', 'image', uspace_as_prolog, '.data', '@INPUT@' ],
-)
-_imgs_s = custom_target('launcher_images.s',
-	input : _images_zip,
-	output : [ 'images.s' ],
-	command : [ unzip, '-p', '@INPUT@', 'images.s' ],
-	capture : true,
-)
-_imgs_h = custom_target('launcher_images.h',
-	input : _images_zip,
-	output : [ 'images.h' ],
-	command : [ unzip, '-p', '@INPUT@', 'images.h' ],
-	capture : true,
-)
-_imgs_desc_c = custom_target('launcher_images_desc.c',
-	input : _images_zip,
-	output : [ 'images_desc.c' ],
-	command : [ unzip, '-p', '@INPUT@', 'images_desc.c' ],
-	capture : true,
-)
-
-src = [ files('launcher.c'), _imgs_s, _imgs_h, _imgs_desc_c ]
Index: uspace/app/meson.build
===================================================================
--- uspace/app/meson.build	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/meson.build	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -28,4 +28,5 @@
 
 apps = [
+	'aboutos',
 	'barber',
 	'bdsh',
@@ -57,5 +58,4 @@
 	'killall',
 	'kio',
-	'launcher',
 	'loc',
 	'logset',
Index: uspace/app/nav/test/panel.c
===================================================================
--- uspace/app/nav/test/panel.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/nav/test/panel.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2022 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -55,11 +55,25 @@
 PCUT_TEST(create_destroy)
 {
-	panel_t *panel;
-	errno_t rc;
-
-	rc = panel_create(NULL, true, &panel);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	panel_destroy(panel);
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	panel_t *panel;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = panel_create(window, true, &panel);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	panel_destroy(panel);
+	ui_window_destroy(window);
+	ui_destroy(ui);
 }
 
@@ -67,9 +81,21 @@
 PCUT_TEST(set_cb)
 {
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
 	panel_t *panel;
 	errno_t rc;
 	test_resp_t resp;
 
-	rc = panel_create(NULL, true, &panel);
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = panel_create(window, true, &panel);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -79,4 +105,6 @@
 
 	panel_destroy(panel);
+	ui_window_destroy(window);
+	ui_destroy(ui);
 }
 
@@ -113,9 +141,21 @@
 PCUT_TEST(ctl)
 {
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
 	panel_t *panel;
 	ui_control_t *control;
 	errno_t rc;
 
-	rc = panel_create(NULL, true, &panel);
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = panel_create(window, true, &panel);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -124,4 +164,6 @@
 
 	panel_destroy(panel);
+	ui_window_destroy(window);
+	ui_destroy(ui);
 }
 
@@ -129,4 +171,7 @@
 PCUT_TEST(kbd_event)
 {
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
 	panel_t *panel;
 	ui_evclaim_t claimed;
@@ -136,5 +181,14 @@
 	/* Active panel should claim events */
 
-	rc = panel_create(NULL, true, &panel);
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = panel_create(window, true, &panel);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -151,5 +205,5 @@
 	/* Inactive panel should not claim events */
 
-	rc = panel_create(NULL, false, &panel);
+	rc = panel_create(window, false, &panel);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -163,4 +217,6 @@
 
 	panel_destroy(panel);
+	ui_window_destroy(window);
+	ui_destroy(ui);
 }
 
@@ -173,9 +229,21 @@
 PCUT_TEST(set_rect)
 {
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
 	panel_t *panel;
 	gfx_rect_t rect;
 	errno_t rc;
 
-	rc = panel_create(NULL, true, &panel);
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = panel_create(window, true, &panel);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -192,4 +260,6 @@
 
 	panel_destroy(panel);
+	ui_window_destroy(window);
+	ui_destroy(ui);
 }
 
@@ -197,16 +267,30 @@
 PCUT_TEST(is_active)
 {
-	panel_t *panel;
-	errno_t rc;
-
-	rc = panel_create(NULL, true, &panel);
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	panel_t *panel;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = panel_create(window, true, &panel);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_TRUE(panel_is_active(panel));
 	panel_destroy(panel);
 
-	rc = panel_create(NULL, false, &panel);
+	rc = panel_create(window, false, &panel);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_FALSE(panel_is_active(panel));
 	panel_destroy(panel);
+	ui_window_destroy(window);
+	ui_destroy(ui);
 }
 
@@ -275,9 +359,21 @@
 PCUT_TEST(activate_req)
 {
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
 	panel_t *panel;
 	errno_t rc;
 	test_resp_t resp;
 
-	rc = panel_create(NULL, true, &panel);
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = panel_create(window, true, &panel);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -292,4 +388,6 @@
 
 	panel_destroy(panel);
+	ui_window_destroy(window);
+	ui_destroy(ui);
 }
 
Index: uspace/app/taskbar-cfg/smeedit.c
===================================================================
--- uspace/app/taskbar-cfg/smeedit.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/taskbar-cfg/smeedit.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -403,4 +403,5 @@
 
 		startmenu_repaint(smee->startmenu);
+		(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
 	} else {
 		/* Edit existing entry */
@@ -419,4 +420,5 @@
 		(void)smenu_entry_save(smee->smentry->entry);
 		startmenu_entry_update(smee->smentry);
+		(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
 	}
 
Index: uspace/app/taskbar-cfg/startmenu.c
===================================================================
--- uspace/app/taskbar-cfg/startmenu.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/taskbar-cfg/startmenu.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -515,4 +515,5 @@
 
 	(void)smee;
+	(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
 }
 
@@ -533,4 +534,5 @@
 	(void)startmenu_insert(smenu, entry, &smentry);
 	(void)ui_control_paint(ui_list_ctl(smenu->entries_list));
+	(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
 }
 
@@ -633,4 +635,5 @@
 	free(smentry);
 	(void)ui_control_paint(ui_list_ctl(smenu->entries_list));
+	(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
 }
 
@@ -685,4 +688,5 @@
 
 	(void)ui_control_paint(ui_list_ctl(smenu->entries_list));
+	(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
 }
 
@@ -704,5 +708,5 @@
 		return;
 
-	rc = smenu_entry_move_up(smentry->entry);
+	rc = smenu_entry_move_down(smentry->entry);
 	if (rc != EOK)
 		return;
@@ -711,4 +715,5 @@
 
 	(void)ui_control_paint(ui_list_ctl(smenu->entries_list));
+	(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
 }
 
Index: uspace/app/taskbar/meson.build
===================================================================
--- uspace/app/taskbar/meson.build	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/taskbar/meson.build	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -48,5 +48,3 @@
 )
 
-if install_nonessential_data
-	installed_data += { 'name': 'taskbar.sif', 'dir': '/cfg' }
-endif
+installed_data += { 'name': 'taskbar.sif', 'dir': '/cfg' }
Index: uspace/app/taskbar/taskbar.c
===================================================================
--- uspace/app/taskbar/taskbar.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/taskbar/taskbar.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -48,7 +48,10 @@
 #include "wndlist.h"
 
+#define TASKBAR_CONFIG_FILE "/cfg/taskbar.sif"
+
 static void taskbar_wnd_close(ui_window_t *, void *);
 static void taskbar_wnd_kbd(ui_window_t *, void *, kbd_event_t *);
 static void taskbar_wnd_pos(ui_window_t *, void *, pos_event_t *);
+static void taskbar_notif_cb(void *);
 
 static ui_window_cb_t window_cb = {
@@ -128,11 +131,26 @@
 	gfx_rect_t scr_rect;
 	gfx_rect_t rect;
+	char *dspec = NULL;
+	char *qmark;
 	errno_t rc;
 
 	taskbar = calloc(1, sizeof(taskbar_t));
 	if (taskbar == NULL) {
+		printf("Out of memory.\n");
 		rc = ENOMEM;
 		goto error;
 	}
+
+	dspec = str_dup(display_spec);
+	if (dspec == NULL) {
+		printf("Out of memory.\n");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	/* Remove additional arguments */
+	qmark = str_chr(dspec, '?');
+	if (qmark != NULL)
+		*qmark = '\0';
 
 	rc = ui_create(display_spec, &taskbar->ui);
@@ -195,5 +213,6 @@
 	}
 
-	rc = tbsmenu_create(taskbar->window, taskbar->fixed, &taskbar->tbsmenu);
+	rc = tbsmenu_create(taskbar->window, taskbar->fixed, dspec,
+	    &taskbar->tbsmenu);
 	if (rc != EOK) {
 		printf("Error creating start menu.\n");
@@ -201,8 +220,14 @@
 	}
 
-	rc = tbsmenu_load(taskbar->tbsmenu, "/cfg/taskbar.sif");
+	rc = tbsmenu_load(taskbar->tbsmenu, TASKBAR_CONFIG_FILE);
 	if (rc != EOK) {
 		printf("Error loading start menu from '%s'.\n",
-		    "/cfg/taskbar.sif");
+		    TASKBAR_CONFIG_FILE);
+	}
+
+	rc = tbarcfg_listener_create(TBARCFG_NOTIFY_DEFAULT,
+	    taskbar_notif_cb, (void *)taskbar, &taskbar->lst);
+	if (rc != EOK) {
+		printf("Error listening for configuration changes.\n");
 	}
 
@@ -284,7 +309,12 @@
 	}
 
+	free(dspec);
 	*rtaskbar = taskbar;
 	return EOK;
 error:
+	if (dspec != NULL)
+		free(dspec);
+	if (taskbar->lst != NULL)
+		tbarcfg_listener_destroy(taskbar->lst);
 	if (taskbar->clock != NULL)
 		taskbar_clock_destroy(taskbar->clock);
@@ -297,4 +327,5 @@
 	if (taskbar->ui != NULL)
 		ui_destroy(taskbar->ui);
+	free(taskbar);
 	return rc;
 
@@ -304,4 +335,6 @@
 void taskbar_destroy(taskbar_t *taskbar)
 {
+	if (taskbar->lst != NULL)
+		tbarcfg_listener_destroy(taskbar->lst);
 	ui_fixed_remove(taskbar->fixed, taskbar_clock_ctl(taskbar->clock));
 	taskbar_clock_destroy(taskbar->clock);
@@ -312,4 +345,19 @@
 }
 
+/** Configuration change notification callback.
+ *
+ * Called when configuration changed.
+ *
+ * @param arg Argument (taskbar_t *)
+ */
+static void taskbar_notif_cb(void *arg)
+{
+	taskbar_t *taskbar = (taskbar_t *)arg;
+
+	ui_lock(taskbar->ui);
+	tbsmenu_reload(taskbar->tbsmenu);
+	ui_unlock(taskbar->ui);
+}
+
 /** @}
  */
Index: uspace/app/taskbar/taskbar.sif
===================================================================
--- uspace/app/taskbar/taskbar.sif	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/taskbar/taskbar.sif	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,1 +1,1 @@
-[sif](){[entries](){[entry]([caption]=[~N~avigator][cmd]=[/app/nav][terminal]=[y]){}[entry]([caption]=[Text ~E~ditor][cmd]=[/app/edit][terminal]=[y]){}[entry]([caption]=[Co~m~mand Line][cmd]=[/app/bdsh][terminal]=[y]){}[entry]([caption]=[~C~alculator][cmd]=[/app/calculator][terminal]=[n]){}[entry]([separator]=[y]){}[entry]([caption]=[~U~I Demo][cmd]=[/app/uidemo][terminal]=[n]){}[entry]([caption]=[~G~FX Demo][cmd]=[/app/gfxdemo ui][terminal]=[n]){}[entry]([caption]=[~B~arber Pole][cmd]=[/app/barber][terminal]=[n]){}[entry]([caption]=[~T~etris][cmd]=[/app/tetris][terminal]=[y]){}[entry]([separator]=[y]){}[entry]([caption]=[~D~isplay Configuration][cmd]=[/app/display-cfg][terminal]=[n]){}[entry]([caption]=[Ta~s~kbar Configuration][cmd]=[/app/taskbar-cfg][terminal]=[n]){}[entry]([separator]=[y]){}[entry]([caption]=[Tas~k~ Monitor (top)][cmd]=[/app/top][terminal]=[y]){}[entry]([caption]=[~F~disk Disk Editor][cmd]=[/app/fdisk][terminal]=[y]){}}}
+[sif](){[entries](){[entry]([caption]=[~N~avigator][cmd]=[/app/nav][terminal]=[y]){}[entry]([caption]=[Text ~E~ditor][cmd]=[/app/edit][terminal]=[y]){}[entry]([caption]=[Co~m~mand Line][cmd]=[/app/bdsh][terminal]=[y]){}[entry]([caption]=[~C~alculator][cmd]=[/app/calculator -d %d][terminal]=[n]){}[entry]([caption]=[~I~mage Viewer][cmd]=[/app/viewer -d %d][terminal]=[n]){}[entry]([separator]=[y]){}[entry]([caption]=[~U~I Demo][cmd]=[/app/uidemo -d %d][terminal]=[n]){}[entry]([caption]=[~G~FX Demo][cmd]=[/app/gfxdemo -d %d ui][terminal]=[n]){}[entry]([caption]=[~B~arber Pole][cmd]=[/app/barber -d %d][terminal]=[n]){}[entry]([caption]=[~T~etris][cmd]=[/app/tetris][terminal]=[y]){}[entry]([separator]=[y]){}[entry]([caption]=[~D~isplay Configuration][cmd]=[/app/display-cfg -d %d][terminal]=[n]){}[entry]([caption]=[Ta~s~kbar Configuration][cmd]=[/app/taskbar-cfg -d %d][terminal]=[n]){}[entry]([separator]=[y]){}[entry]([caption]=[Tas~k~ Monitor (top)][cmd]=[/app/top][terminal]=[y]){}[entry]([caption]=[~F~disk Disk Editor][cmd]=[/app/fdisk][terminal]=[y]){}[entry]([separator]=[y]){}[entry]([caption]=[~A~bout HelenOS][cmd]=[/app/aboutos -d %d][terminal]=[n]){}}}
Index: uspace/app/taskbar/tbsmenu.c
===================================================================
--- uspace/app/taskbar/tbsmenu.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/taskbar/tbsmenu.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -64,4 +64,5 @@
 static void tbsmenu_smenu_entry_cb(ui_menu_entry_t *, void *);
 static errno_t tbsmenu_entry_start(tbsmenu_entry_t *);
+static void tbsmenu_cmd_fini(tbsmenu_cmd_t *);
 
 /** Create taskbar start menu.
@@ -69,9 +70,10 @@
  * @param window Containing window
  * @param fixed Fixed layout to which start button will be added
+ * @param dspec Display specification (for passing to applications)
  * @param rtbsmenu Place to store pointer to new start menu
  * @return @c EOK on success or an error code
  */
 errno_t tbsmenu_create(ui_window_t *window, ui_fixed_t *fixed,
-    tbsmenu_t **rtbsmenu)
+    const char *dspec, tbsmenu_t **rtbsmenu)
 {
 	ui_resource_t *res = ui_window_get_res(window);
@@ -85,4 +87,10 @@
 	}
 
+	tbsmenu->display_spec = str_dup(dspec);
+	if (tbsmenu->display_spec == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
 	rc = ui_pbutton_create(res, "Start", &tbsmenu->sbutton);
 	if (rc != EOK)
@@ -111,4 +119,6 @@
 	return EOK;
 error:
+	if (tbsmenu != NULL && tbsmenu->display_spec != NULL)
+		free(tbsmenu->display_spec);
 	if (tbsmenu != NULL)
 		ui_pbutton_destroy(tbsmenu->sbutton);
@@ -134,4 +144,18 @@
 	bool terminal;
 	errno_t rc;
+
+	if (tbsmenu->repopath != NULL)
+		free(tbsmenu->repopath);
+
+	tbsmenu->repopath = str_dup(repopath);
+	if (tbsmenu->repopath == NULL)
+		return ENOMEM;
+
+	/* Remove existing entries */
+	tentry = tbsmenu_first(tbsmenu);
+	while (tentry != NULL) {
+		tbsmenu_remove(tbsmenu, tentry, false);
+		tentry = tbsmenu_first(tbsmenu);
+	}
 
 	rc = tbarcfg_open(repopath, &tbcfg);
@@ -170,4 +194,16 @@
 }
 
+/** Reload start menu from repository (or schedule reload).
+ *
+ * @param tbsmenu Start menu
+ */
+void tbsmenu_reload(tbsmenu_t *tbsmenu)
+{
+	if (!tbsmenu_is_open(tbsmenu))
+		(void) tbsmenu_load(tbsmenu, tbsmenu->repopath);
+	else
+		tbsmenu->needs_reload = true;
+}
+
 /** Set start menu rectangle.
  *
@@ -198,4 +234,7 @@
 {
 	ui_menu_close(tbsmenu->smenu);
+
+	if (tbsmenu->needs_reload)
+		(void) tbsmenu_load(tbsmenu, tbsmenu->repopath);
 }
 
@@ -447,15 +486,16 @@
 static errno_t tbsmenu_cmd_split(const char *str, tbsmenu_cmd_t *cmd)
 {
+	char *buf;
 	char *arg;
 	char *next;
 	size_t cnt;
 
-	cmd->buf = str_dup(str);
-	if (cmd->buf == NULL)
+	buf = str_dup(str);
+	if (buf == NULL)
 		return ENOMEM;
 
 	/* Count the entries */
 	cnt = 0;
-	arg = str_tok(cmd->buf, " ", &next);
+	arg = str_tok(buf, " ", &next);
 	while (arg != NULL) {
 		++cnt;
@@ -464,20 +504,26 @@
 
 	/* Need to copy again as buf was mangled */
-	free(cmd->buf);
-	cmd->buf = str_dup(str);
-	if (cmd->buf == NULL)
+	free(buf);
+	buf = str_dup(str);
+	if (buf == NULL)
 		return ENOMEM;
 
 	cmd->argv = calloc(cnt + 1, sizeof(char *));
 	if (cmd->argv == NULL) {
-		free(cmd->buf);
+		free(buf);
 		return ENOMEM;
 	}
 
-	/* Fill in pointers */
+	/* Copy individual arguments */
 	cnt = 0;
-	arg = str_tok(cmd->buf, " ", &next);
+	arg = str_tok(buf, " ", &next);
 	while (arg != NULL) {
-		cmd->argv[cnt++] = arg;
+		cmd->argv[cnt] = str_dup(arg);
+		if (cmd->argv[cnt] == NULL) {
+			tbsmenu_cmd_fini(cmd);
+			return ENOMEM;
+		}
+		++cnt;
+
 		arg = str_tok(next, " ", &next);
 	}
@@ -494,6 +540,45 @@
 static void tbsmenu_cmd_fini(tbsmenu_cmd_t *cmd)
 {
+	char **cp;
+
+	/* Free all pointers in NULL-terminated list */
+	cp = cmd->argv;
+	while (*cp != NULL) {
+		free(*cp);
+		++cp;
+	}
+
+	/* Free the array of pointers */
 	free(cmd->argv);
-	free(cmd->buf);
+}
+
+/** Free command structure.
+ *
+ * @param cmd Command
+ * @param entry Start menu entry
+ * @param dspec Display specification
+ * @return EOK on success or an error code
+ */
+static errno_t tbsmenu_cmd_subst(tbsmenu_cmd_t *cmd, tbsmenu_entry_t *entry,
+    const char *dspec)
+{
+	char **cp;
+
+	(void)entry;
+
+	/* Walk NULL-terminated list of arguments */
+	cp = cmd->argv;
+	while (*cp != NULL) {
+		if (str_cmp(*cp, "%d") == 0) {
+			/* Display specification */
+			free(*cp);
+			*cp = str_dup(dspec);
+			if (*cp == NULL)
+				return ENOMEM;
+		}
+		++cp;
+	}
+
+	return EOK;
 }
 
@@ -516,4 +601,7 @@
 	char **cp;
 	const char **targv = NULL;
+	char *dspec = NULL;
+	sysarg_t idev_id;
+	int rv;
 	errno_t rc;
 	ui_t *ui;
@@ -522,7 +610,22 @@
 	suspended = false;
 
+	idev_id = ui_menu_get_idev_id(entry->tbsmenu->smenu);
+
+	rv = asprintf(&dspec, "%s?idev=%zu",
+	    entry->tbsmenu->display_spec, (size_t)idev_id);
+	if (rv < 0)
+		return ENOMEM;
+
+	/* Split command string into individual arguments */
 	rc = tbsmenu_cmd_split(entry->cmd, &cmd);
-	if (rc != EOK)
+	if (rc != EOK) {
+		free(dspec);
 		return rc;
+	}
+
+	/* Substitute metacharacters in command */
+	rc = tbsmenu_cmd_subst(&cmd, entry, dspec);
+	if (rc != EOK)
+		goto error;
 
 	/* Free up and clean console for the child task. */
@@ -542,13 +645,15 @@
 		}
 
-		targv = calloc(cnt + 3, sizeof(char **));
+		targv = calloc(cnt + 5, sizeof(char **));
 		if (targv == NULL)
 			goto error;
 
 		targv[0] = "/app/terminal";
-		targv[1] = "-c";
+		targv[1] = "-d";
+		targv[2] = dspec;
+		targv[3] = "-c";
 
 		for (i = 0; i <= cnt; i++) {
-			targv[2 + i] = cmd.argv[i];
+			targv[4 + i] = cmd.argv[i];
 		}
 
@@ -578,4 +683,5 @@
 	return EOK;
 error:
+	free(dspec);
 	if (targv != NULL)
 		free(targv);
Index: uspace/app/taskbar/tbsmenu.h
===================================================================
--- uspace/app/taskbar/tbsmenu.h	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/taskbar/tbsmenu.h	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -46,6 +46,8 @@
 #include "types/tbsmenu.h"
 
-extern errno_t tbsmenu_create(ui_window_t *, ui_fixed_t *, tbsmenu_t **);
+extern errno_t tbsmenu_create(ui_window_t *, ui_fixed_t *, const char *,
+    tbsmenu_t **);
 extern errno_t tbsmenu_load(tbsmenu_t *, const char *);
+extern void tbsmenu_reload(tbsmenu_t *);
 extern void tbsmenu_set_rect(tbsmenu_t *, gfx_rect_t *);
 extern void tbsmenu_open(tbsmenu_t *);
Index: uspace/app/taskbar/test/tbsmenu.c
===================================================================
--- uspace/app/taskbar/test/tbsmenu.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/taskbar/test/tbsmenu.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -61,5 +61,5 @@
 	ui_window_add(window, ui_fixed_ctl(fixed));
 
-	rc = tbsmenu_create(window, fixed, &tbsmenu);
+	rc = tbsmenu_create(window, fixed, UI_DISPLAY_DEFAULT, &tbsmenu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -92,5 +92,5 @@
 	ui_window_add(window, ui_fixed_ctl(fixed));
 
-	rc = tbsmenu_create(window, fixed, &tbsmenu);
+	rc = tbsmenu_create(window, fixed, UI_DISPLAY_DEFAULT, &tbsmenu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
Index: uspace/app/taskbar/types/taskbar.h
===================================================================
--- uspace/app/taskbar/types/taskbar.h	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/taskbar/types/taskbar.h	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -60,4 +60,6 @@
 	/** Clock */
 	taskbar_clock_t *clock;
+	/** Configuration change listener */
+	tbarcfg_listener_t *lst;
 } taskbar_t;
 
Index: uspace/app/taskbar/types/tbsmenu.h
===================================================================
--- uspace/app/taskbar/types/tbsmenu.h	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/taskbar/types/tbsmenu.h	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -40,4 +40,5 @@
 #include <gfx/coord.h>
 #include <stdbool.h>
+#include <tbarcfg/tbarcfg.h>
 #include <ui/pbutton.h>
 #include <ui/fixed.h>
@@ -84,10 +85,17 @@
 	/** Device ID of last input event */
 	sysarg_t ev_idev_id;
+
+	/** Repository path name */
+	char *repopath;
+
+	/** Need to reload menu when possible */
+	bool needs_reload;
+
+	/** Display specification */
+	char *display_spec;
 } tbsmenu_t;
 
 /** Command split into individual parts */
 typedef struct {
-	/** Buffer holding broken down command */
-	char *buf;
 	/** NULL-terminated array of string pointers */
 	char **argv;
Index: uspace/app/terminal/terminal.c
===================================================================
--- uspace/app/terminal/terminal.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/terminal/terminal.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2012 Petr Koupy
  * All rights reserved.
@@ -974,19 +974,19 @@
 		wparams.placement = ui_wnd_place_top_left;
 
+	rc = ui_create(display_spec, &term->ui);
+	if (rc != EOK) {
+		printf("Error creating UI on %s.\n", display_spec);
+		goto error;
+	}
+
 	/*
 	 * Compute window rectangle such that application area corresponds
 	 * to rect
 	 */
-	ui_wdecor_rect_from_app(wparams.style, &rect, &wrect);
+	ui_wdecor_rect_from_app(term->ui, wparams.style, &rect, &wrect);
 	off = wrect.p0;
 	gfx_rect_rtranslate(&off, &wrect, &wparams.rect);
 
 	term->off = off;
-
-	rc = ui_create(display_spec, &term->ui);
-	if (rc != EOK) {
-		printf("Error creating UI on %s.\n", display_spec);
-		goto error;
-	}
 
 	rc = ui_window_create(term->ui, &wparams, &term->window);
Index: uspace/app/viewer/viewer.c
===================================================================
--- uspace/app/viewer/viewer.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/app/viewer/viewer.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2013 Martin Decky
  * All rights reserved.
@@ -40,4 +40,5 @@
 #include <stdlib.h>
 #include <str.h>
+#include <ui/filedialog.h>
 #include <ui/image.h>
 #include <ui/ui.h>
@@ -50,20 +51,24 @@
 typedef struct {
 	ui_t *ui;
+
+	size_t imgs_count;
+	size_t imgs_current;
+	char **imgs;
+
+	bool fullscreen;
+	ui_window_t *window;
+	gfx_bitmap_t *bitmap;
+	ui_image_t *image;
+	gfx_context_t *window_gc;
+	ui_file_dialog_t *dialog;
+
+	gfx_rect_t img_rect;
 } viewer_t;
 
-static size_t imgs_count;
-static size_t imgs_current = 0;
-static char **imgs;
-
-static ui_window_t *window;
-static gfx_bitmap_t *bitmap = NULL;
-static ui_image_t *image = NULL;
-static gfx_context_t *window_gc;
-
-static gfx_rect_t img_rect;
-
-static bool img_load(gfx_context_t *gc, const char *, gfx_bitmap_t **,
+static bool viewer_img_load(viewer_t *, const char *, gfx_bitmap_t **,
     gfx_rect_t *);
-static bool img_setup(gfx_context_t *, gfx_bitmap_t *, gfx_rect_t *);
+static bool viewer_img_setup(viewer_t *, gfx_bitmap_t *, gfx_rect_t *);
+static errno_t viewer_window_create(viewer_t *);
+static void viewer_window_destroy(viewer_t *);
 
 static void wnd_close(ui_window_t *, void *);
@@ -75,4 +80,14 @@
 };
 
+static void file_dialog_bok(ui_file_dialog_t *, void *, const char *);
+static void file_dialog_bcancel(ui_file_dialog_t *, void *);
+static void file_dialog_close(ui_file_dialog_t *, void *);
+
+static ui_file_dialog_cb_t file_dialog_cb = {
+	.bok = file_dialog_bok,
+	.bcancel = file_dialog_bcancel,
+	.close = file_dialog_close
+};
+
 /** Window close request
  *
@@ -87,26 +102,30 @@
 }
 
-static void wnd_kbd_event(ui_window_t *window, void *arg,
-    kbd_event_t *event)
+/** Viewer unmodified key press.
+ *
+ * @param viewer Viewer
+ * @param event Keyboard event
+ */
+static void viewer_kbd_event_unmod(viewer_t *viewer, kbd_event_t *event)
 {
 	bool update = false;
 
-	if ((event->type == KEY_PRESS) && (event->c == 'q'))
-		exit(0);
-
-	if ((event->type == KEY_PRESS) && (event->key == KC_PAGE_DOWN)) {
-		if (imgs_current == imgs_count - 1)
-			imgs_current = 0;
+	if (event->key == KC_Q || event->key == KC_ESCAPE)
+		ui_quit(viewer->ui);
+
+	if (event->key == KC_PAGE_DOWN) {
+		if (viewer->imgs_current == viewer->imgs_count - 1)
+			viewer->imgs_current = 0;
 		else
-			imgs_current++;
+			viewer->imgs_current++;
 
 		update = true;
 	}
 
-	if ((event->type == KEY_PRESS) && (event->key == KC_PAGE_UP)) {
-		if (imgs_current == 0)
-			imgs_current = imgs_count - 1;
+	if (event->key == KC_PAGE_UP) {
+		if (viewer->imgs_current == 0)
+			viewer->imgs_current = viewer->imgs_count - 1;
 		else
-			imgs_current--;
+			viewer->imgs_current--;
 
 		update = true;
@@ -117,10 +136,13 @@
 		gfx_rect_t lrect;
 
-		if (!img_load(window_gc, imgs[imgs_current], &lbitmap, &lrect)) {
-			printf("Cannot load image \"%s\".\n", imgs[imgs_current]);
+		if (!viewer_img_load(viewer, viewer->imgs[viewer->imgs_current],
+		    &lbitmap, &lrect)) {
+			printf("Cannot load image \"%s\".\n",
+			    viewer->imgs[viewer->imgs_current]);
 			exit(4);
 		}
-		if (!img_setup(window_gc, lbitmap, &lrect)) {
-			printf("Cannot setup image \"%s\".\n", imgs[imgs_current]);
+		if (!viewer_img_setup(viewer, lbitmap, &lrect)) {
+			printf("Cannot setup image \"%s\".\n",
+			    viewer->imgs[viewer->imgs_current]);
 			exit(6);
 		}
@@ -128,5 +150,101 @@
 }
 
-static bool img_load(gfx_context_t *gc, const char *fname,
+/** Viewer ctrl-key key press.
+ *
+ * @param viewer Viewer
+ * @param event Keyboard event
+ */
+static void viewer_kbd_event_ctrl(viewer_t *viewer, kbd_event_t *event)
+{
+	if (event->key == KC_Q)
+		ui_quit(viewer->ui);
+}
+
+/** Viewer window keyboard event.
+ *
+ * @param window UI window
+ * @param arg Argument (viewer_t *)
+ * @param event Keyboard event
+ */
+static void wnd_kbd_event(ui_window_t *window, void *arg,
+    kbd_event_t *event)
+{
+	viewer_t *viewer = (viewer_t *)arg;
+
+	if (event->type != KEY_PRESS)
+		return;
+
+	if ((event->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0)
+		viewer_kbd_event_unmod(viewer, event);
+
+	if ((event->mods & KM_CTRL) != 0 &&
+	    (event->mods & (KM_ALT | KM_SHIFT)) == 0)
+		viewer_kbd_event_ctrl(viewer, event);
+
+	ui_window_def_kbd(window, event);
+}
+
+/** File dialog OK button press.
+ *
+ * @param dialog File dialog
+ * @param arg Argument (viewer_t *)
+ * @param fname File name
+ */
+static void file_dialog_bok(ui_file_dialog_t *dialog, void *arg,
+    const char *fname)
+{
+	viewer_t *viewer = (viewer_t *) arg;
+	errno_t rc;
+
+	viewer->imgs_count = 1;
+	viewer->imgs = calloc(viewer->imgs_count, sizeof(char *));
+	if (viewer->imgs == NULL) {
+		printf("Out of memory.\n");
+		ui_quit(viewer->ui);
+		return;
+	}
+
+	viewer->imgs[0] = str_dup(fname);
+	if (viewer->imgs[0] == NULL) {
+		printf("Out of memory.\n");
+		ui_quit(viewer->ui);
+		return;
+	}
+
+	rc = viewer_window_create(viewer);
+	if (rc != EOK)
+		ui_quit(viewer->ui);
+
+	ui_file_dialog_destroy(dialog);
+	viewer->dialog = NULL;
+}
+
+/** File dialog cancel button press.
+ *
+ * @param dialog File dialog
+ * @param arg Argument (viewer_t *)
+ */
+static void file_dialog_bcancel(ui_file_dialog_t *dialog, void *arg)
+{
+	viewer_t *viewer = (viewer_t *) arg;
+
+	ui_file_dialog_destroy(dialog);
+	ui_quit(viewer->ui);
+}
+
+/** File dialog close request.
+ *
+ * @param dialog File dialog
+ * @param arg Argument (viewer_t *)
+ */
+static void file_dialog_close(ui_file_dialog_t *dialog, void *arg)
+{
+	viewer_t *viewer = (viewer_t *) arg;
+
+	ui_file_dialog_destroy(dialog);
+	ui_quit(viewer->ui);
+}
+
+static bool viewer_img_load(viewer_t *viewer, const char *fname,
     gfx_bitmap_t **rbitmap, gfx_rect_t *rect)
 {
@@ -159,5 +277,5 @@
 	vfs_put(fd);
 
-	rc = decode_tga(gc, tga, stat.size, rbitmap, rect);
+	rc = decode_tga(viewer->window_gc, tga, stat.size, rbitmap, rect);
 	if (rc != EOK) {
 		free(tga);
@@ -167,9 +285,10 @@
 	free(tga);
 
-	img_rect = *rect;
+	viewer->img_rect = *rect;
 	return true;
 }
 
-static bool img_setup(gfx_context_t *gc, gfx_bitmap_t *bmp, gfx_rect_t *rect)
+static bool viewer_img_setup(viewer_t *viewer, gfx_bitmap_t *bmp,
+    gfx_rect_t *rect)
 {
 	gfx_rect_t arect;
@@ -178,17 +297,17 @@
 	errno_t rc;
 
-	ui_res = ui_window_get_res(window);
-
-	ui_window_get_app_rect(window, &arect);
+	ui_res = ui_window_get_res(viewer->window);
+
+	ui_window_get_app_rect(viewer->window, &arect);
 
 	/* Center image on application area */
 	gfx_rect_ctr_on_rect(rect, &arect, &irect);
 
-	if (image != NULL) {
-		ui_image_set_bmp(image, bmp, rect);
-		(void) ui_image_paint(image);
-		ui_image_set_rect(image, &irect);
+	if (viewer->image != NULL) {
+		ui_image_set_bmp(viewer->image, bmp, rect);
+		(void) ui_image_paint(viewer->image);
+		ui_image_set_rect(viewer->image, &irect);
 	} else {
-		rc = ui_image_create(ui_res, bmp, rect, &image);
+		rc = ui_image_create(ui_res, bmp, rect, &viewer->image);
 		if (rc != EOK) {
 			gfx_bitmap_destroy(bmp);
@@ -196,12 +315,12 @@
 		}
 
-		ui_image_set_rect(image, &irect);
-		ui_window_add(window, ui_image_ctl(image));
-	}
-
-	if (bitmap != NULL)
-		gfx_bitmap_destroy(bitmap);
-
-	bitmap = bmp;
+		ui_image_set_rect(viewer->image, &irect);
+		ui_window_add(viewer->window, ui_image_ctl(viewer->image));
+	}
+
+	if (viewer->bitmap != NULL)
+		gfx_bitmap_destroy(viewer->bitmap);
+
+	viewer->bitmap = bmp;
 	return true;
 }
@@ -214,68 +333,13 @@
 }
 
-int main(int argc, char *argv[])
-{
-	const char *display_spec = UI_DISPLAY_DEFAULT;
+static errno_t viewer_window_create(viewer_t *viewer)
+{
+	ui_wnd_params_t params;
 	gfx_bitmap_t *lbitmap;
 	gfx_rect_t lrect;
-	bool fullscreen = false;
-	gfx_rect_t rect;
 	gfx_rect_t wrect;
 	gfx_coord2_t off;
-	ui_t *ui;
-	ui_wnd_params_t params;
-	viewer_t viewer;
+	gfx_rect_t rect;
 	errno_t rc;
-	int i;
-
-	i = 1;
-	while (i < argc && argv[i][0] == '-') {
-		if (str_cmp(argv[i], "-d") == 0) {
-			++i;
-			if (i >= argc) {
-				printf("Argument missing.\n");
-				print_syntax();
-				return 1;
-			}
-
-			display_spec = argv[i++];
-		} else if (str_cmp(argv[i], "-f") == 0) {
-			++i;
-			fullscreen = true;
-		} else {
-			printf("Invalid option '%s'.\n", argv[i]);
-			print_syntax();
-			return 1;
-		}
-	}
-
-	if (i >= argc) {
-		printf("No image files specified.\n");
-		print_syntax();
-		return 1;
-	}
-
-	imgs_count = argc - i;
-	imgs = calloc(imgs_count, sizeof(char *));
-	if (imgs == NULL) {
-		printf("Out of memory.\n");
-		return 1;
-	}
-
-	for (int j = 0; j < argc - i; j++) {
-		imgs[j] = str_dup(argv[i + j]);
-		if (imgs[j] == NULL) {
-			printf("Out of memory.\n");
-			return 3;
-		}
-	}
-
-	rc = ui_create(display_spec, &ui);
-	if (rc != EOK) {
-		printf("Error creating UI on display %s.\n", display_spec);
-		return 1;
-	}
-
-	viewer.ui = ui;
 
 	/*
@@ -290,22 +354,24 @@
 	params.rect.p1.y = 1;
 
-	if (fullscreen) {
+	if (viewer->fullscreen) {
 		params.style &= ~ui_wds_decorated;
 		params.placement = ui_wnd_place_full_screen;
 	}
 
-	rc = ui_window_create(ui, &params, &window);
+	rc = ui_window_create(viewer->ui, &params, &viewer->window);
 	if (rc != EOK) {
 		printf("Error creating window.\n");
-		return 1;
-	}
-
-	window_gc = ui_window_get_gc(window);
-
-	ui_window_set_cb(window, &window_cb, (void *) &viewer);
-
-	if (!img_load(window_gc, imgs[imgs_current], &lbitmap, &lrect)) {
-		printf("Cannot load image \"%s\".\n", imgs[imgs_current]);
-		return 1;
+		goto error;
+	}
+
+	viewer->window_gc = ui_window_get_gc(viewer->window);
+
+	ui_window_set_cb(viewer->window, &window_cb, (void *)viewer);
+
+	if (!viewer_img_load(viewer, viewer->imgs[viewer->imgs_current],
+	    &lbitmap, &lrect)) {
+		printf("Cannot load image \"%s\".\n",
+		    viewer->imgs[viewer->imgs_current]);
+		goto error;
 	}
 
@@ -314,30 +380,153 @@
 	 * to rect
 	 */
-	ui_wdecor_rect_from_app(params.style, &lrect, &wrect);
+	ui_wdecor_rect_from_app(viewer->ui, params.style, &lrect, &wrect);
 	off = wrect.p0;
 	gfx_rect_rtranslate(&off, &wrect, &rect);
 
-	if (!fullscreen) {
-		rc = ui_window_resize(window, &rect);
+	if (!viewer->fullscreen) {
+		rc = ui_window_resize(viewer->window, &rect);
 		if (rc != EOK) {
 			printf("Error resizing window.\n");
-			return 1;
-		}
-	}
-
-	if (!img_setup(window_gc, lbitmap, &lrect)) {
-		printf("Cannot setup image \"%s\".\n", imgs[imgs_current]);
-		return 1;
-	}
-
-	rc = ui_window_paint(window);
+			goto error;
+		}
+	}
+
+	if (!viewer_img_setup(viewer, lbitmap, &lrect)) {
+		printf("Cannot setup image \"%s\".\n",
+		    viewer->imgs[viewer->imgs_current]);
+		goto error;
+	}
+
+	rc = ui_window_paint(viewer->window);
 	if (rc != EOK) {
 		printf("Error painting window.\n");
-		return 1;
+		goto error;
+	}
+
+	return EOK;
+error:
+	viewer_window_destroy(viewer);
+	ui_quit(viewer->ui);
+	return rc;
+}
+
+static void viewer_window_destroy(viewer_t *viewer)
+{
+	if (viewer->window != NULL)
+		ui_window_destroy(viewer->window);
+	viewer->window = NULL;
+}
+
+int main(int argc, char *argv[])
+{
+	const char *display_spec = UI_ANY_DEFAULT;
+	ui_t *ui = NULL;
+	viewer_t *viewer;
+	errno_t rc;
+	int i;
+	unsigned u;
+	ui_file_dialog_params_t fdparams;
+
+	viewer = calloc(1, sizeof(viewer_t));
+	if (viewer == NULL) {
+		printf("Out of memory.\n");
+		goto error;
+	}
+
+	i = 1;
+	while (i < argc && argv[i][0] == '-') {
+		if (str_cmp(argv[i], "-d") == 0) {
+			++i;
+			if (i >= argc) {
+				printf("Argument missing.\n");
+				print_syntax();
+				goto error;
+			}
+
+			display_spec = argv[i++];
+		} else if (str_cmp(argv[i], "-f") == 0) {
+			++i;
+			viewer->fullscreen = true;
+		} else {
+			printf("Invalid option '%s'.\n", argv[i]);
+			print_syntax();
+			goto error;
+		}
+	}
+
+	/* Images specified? */
+	if (i < argc) {
+		viewer->imgs_count = argc - i;
+		viewer->imgs = calloc(viewer->imgs_count, sizeof(char *));
+		if (viewer->imgs == NULL) {
+			printf("Out of memory.\n");
+			goto error;
+		}
+
+		for (int j = 0; j < argc - i; j++) {
+			viewer->imgs[j] = str_dup(argv[i + j]);
+			if (viewer->imgs[j] == NULL) {
+				printf("Out of memory.\n");
+				goto error;
+			}
+		}
+	}
+
+	rc = ui_create(display_spec, &ui);
+	if (rc != EOK) {
+		printf("Error creating UI on display %s.\n", display_spec);
+		goto error;
+	}
+
+	if (ui_is_fullscreen(ui))
+		viewer->fullscreen = true;
+
+	viewer->ui = ui;
+
+	if (viewer->imgs != NULL) {
+		/* We have images, create viewer window. */
+		rc = viewer_window_create(viewer);
+		if (rc != EOK)
+			goto error;
+	} else {
+		/* No images specified, browse for one. */
+		ui_file_dialog_params_init(&fdparams);
+		fdparams.caption = "Open Image";
+
+		rc = ui_file_dialog_create(viewer->ui, &fdparams,
+		    &viewer->dialog);
+		if (rc != EOK) {
+			printf("Error creating file dialog.\n");
+			goto error;
+		}
+
+		ui_file_dialog_set_cb(viewer->dialog, &file_dialog_cb,
+		    (void *)viewer);
 	}
 
 	ui_run(ui);
 
+	ui_window_destroy(viewer->window);
+	ui_destroy(ui);
+	free(viewer);
+
 	return 0;
+error:
+	if (viewer != NULL && viewer->dialog != NULL)
+		ui_file_dialog_destroy(viewer->dialog);
+	if (viewer != NULL && viewer->imgs != NULL) {
+		for (u = 0; u < viewer->imgs_count; u++) {
+			if (viewer->imgs[i] != NULL)
+				free(viewer->imgs[i]);
+		}
+		free(viewer->imgs);
+	}
+	if (viewer != NULL)
+		viewer_window_destroy(viewer);
+	if (ui != NULL)
+		ui_destroy(ui);
+	if (viewer != NULL)
+		free(viewer);
+	return 1;
 }
 
Index: uspace/lib/congfx/src/console.c
===================================================================
--- uspace/lib/congfx/src/console.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/congfx/src/console.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -91,10 +91,10 @@
 	uint8_t attr;
 
-	if ((clr >> 24) == 0) {
+	if ((clr >> 24) == 0xff) {
 		/* RGB (no text) */
 		ch->ch = 0;
 		ch->flags = CHAR_FLAG_DIRTY;
 		ch->attrs.type = CHAR_ATTR_RGB;
-		ch->attrs.val.rgb.fgcolor = clr ^ 0xffffff;
+		ch->attrs.val.rgb.fgcolor = clr;
 		ch->attrs.val.rgb.bgcolor = clr;
 	} else {
Index: uspace/lib/gfx/src/color.c
===================================================================
--- uspace/lib/gfx/src/color.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/gfx/src/color.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -64,4 +64,5 @@
 	color->g = g;
 	color->b = b;
+	color->attr = 0xff;
 
 	*rcolor = color;
Index: uspace/lib/meson.build
===================================================================
--- uspace/lib/meson.build	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/meson.build	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -33,5 +33,8 @@
 	'c',
 	'math',
+	'console',
 	'display',
+	'input',
+	'output',
 	'pixconv',
 	'posix',
Index: uspace/lib/tbarcfg/include/ipc/tbarcfg.h
===================================================================
--- uspace/lib/tbarcfg/include/ipc/tbarcfg.h	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
+++ uspace/lib/tbarcfg/include/ipc/tbarcfg.h	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2024 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 libtbarcfg
+ * @{
+ */
+/** @file
+ */
+
+#ifndef LIBTBARCFG_IPC_TBARCFG_H
+#define LIBTBARCFG_IPC_TBARCFG_H
+
+#include <ipc/common.h>
+
+typedef enum {
+	TBARCFG_NOTIFY_NOTIFY = IPC_FIRST_USER_METHOD
+} tbarcfg_notify_request_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/tbarcfg/include/tbarcfg/tbarcfg.h
===================================================================
--- uspace/lib/tbarcfg/include/tbarcfg/tbarcfg.h	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/tbarcfg/include/tbarcfg/tbarcfg.h	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -42,4 +42,6 @@
 #include <types/tbarcfg/tbarcfg.h>
 
+#define TBARCFG_NOTIFY_DEFAULT "tbarcfg-notif"
+
 extern errno_t tbarcfg_create(const char *, tbarcfg_t **);
 extern errno_t tbarcfg_open(const char *, tbarcfg_t **);
@@ -63,4 +65,8 @@
 extern errno_t smenu_entry_move_up(smenu_entry_t *);
 extern errno_t smenu_entry_move_down(smenu_entry_t *);
+extern errno_t tbarcfg_listener_create(const char *, void (*)(void *),
+    void *, tbarcfg_listener_t **);
+extern void tbarcfg_listener_destroy(tbarcfg_listener_t *);
+extern errno_t tbarcfg_notify(const char *);
 
 #endif
Index: uspace/lib/tbarcfg/include/types/tbarcfg/tbarcfg.h
===================================================================
--- uspace/lib/tbarcfg/include/types/tbarcfg/tbarcfg.h	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/tbarcfg/include/types/tbarcfg/tbarcfg.h	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -43,4 +43,7 @@
 typedef struct smenu_entry smenu_entry_t;
 
+struct tbarcfg_listener;
+typedef struct tbarcfg_listener tbarcfg_listener_t;
+
 #endif
 
Index: uspace/lib/tbarcfg/private/tbarcfg.h
===================================================================
--- uspace/lib/tbarcfg/private/tbarcfg.h	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/tbarcfg/private/tbarcfg.h	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -71,4 +71,12 @@
 };
 
+/** Taskbar configuration listener */
+typedef struct tbarcfg_listener {
+	/** Notification callback */
+	void (*cb)(void *);
+	/** Callback argument */
+	void *arg;
+} tbarcfg_listener_t;
+
 extern errno_t smenu_entry_new(tbarcfg_t *, sif_node_t *, const char *,
     const char *, bool, smenu_entry_t **);
Index: uspace/lib/tbarcfg/src/tbarcfg.c
===================================================================
--- uspace/lib/tbarcfg/src/tbarcfg.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/tbarcfg/src/tbarcfg.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -34,10 +34,17 @@
  */
 
+#include <async.h>
 #include <errno.h>
 #include <sif.h>
+#include <ipc/tbarcfg.h>
+#include <loc.h>
+#include <task.h>
 #include <tbarcfg/tbarcfg.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <str.h>
 #include "../private/tbarcfg.h"
+
+static void tbarcfg_notify_conn(ipc_call_t *, void *);
 
 /** Create taskbar configuration.
@@ -784,4 +791,176 @@
 }
 
+/** Create taskbar configuration listener.
+ *
+ * Listens for taskbar configuration change notifications.
+ *
+ * @param nchan Notification channel (TBARCFG_NOTIFY_DEFAULT)
+ * @param rlst Place to store pointer to new listener
+ * @return EOK on success or an error code
+ */
+errno_t tbarcfg_listener_create(const char *nchan, void (*cb)(void *),
+    void *arg, tbarcfg_listener_t **rlst)
+{
+	tbarcfg_listener_t *lst;
+	service_id_t svcid = 0;
+	loc_srv_t *srv = NULL;
+	task_id_t taskid;
+	char *svcname = NULL;
+	category_id_t catid;
+	port_id_t port;
+	int rv;
+	errno_t rc;
+
+	lst = calloc(1, sizeof(tbarcfg_listener_t));
+	if (lst == NULL)
+		return ENOMEM;
+
+	lst->cb = cb;
+	lst->arg = arg;
+
+	rc = async_create_port(INTERFACE_TBARCFG_NOTIFY,
+	    tbarcfg_notify_conn, (void *)lst, &port);
+	if (rc != EOK)
+		goto error;
+
+	rc = loc_server_register("tbarcfg-listener", &srv);
+	if (rc != EOK)
+		goto error;
+
+	taskid = task_get_id();
+
+	rv = asprintf(&svcname, "tbarcfg/%u", (unsigned)taskid);
+	if (rv < 0) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	rc = loc_service_register(srv, svcname, &svcid);
+	if (rc != EOK)
+		goto error;
+
+	rc = loc_category_get_id(nchan, &catid, 0);
+	if (rc != EOK)
+		goto error;
+
+	rc = loc_service_add_to_cat(srv, svcid, catid);
+	if (rc != EOK)
+		goto error;
+
+	*rlst = lst;
+	return EOK;
+error:
+	if (svcid != 0)
+		loc_service_unregister(srv, svcid);
+	if (srv != NULL)
+		loc_server_unregister(srv);
+	if (svcname != NULL)
+		free(svcname);
+	return rc;
+}
+
+/** Destroy taskbar configuration listener.
+ *
+ * @param lst Listener
+ */
+void tbarcfg_listener_destroy(tbarcfg_listener_t *lst)
+{
+	free(lst);
+}
+
+/** Send taskbar configuration notification to a particular service ID.
+ *
+ * @param svcid Service ID
+ * @return EOK on success or an error code
+ */
+static errno_t tbarcfg_notify_svc(service_id_t svcid)
+{
+	async_sess_t *sess;
+	async_exch_t *exch;
+	errno_t rc;
+
+	sess = loc_service_connect(svcid, INTERFACE_TBARCFG_NOTIFY, 0);
+	if (sess == NULL)
+		return EIO;
+
+	exch = async_exchange_begin(sess);
+	rc = async_req_0_0(exch, TBARCFG_NOTIFY_NOTIFY);
+	if (rc != EOK) {
+		async_exchange_end(exch);
+		async_hangup(sess);
+		return rc;
+	}
+
+	async_exchange_end(exch);
+	async_hangup(sess);
+	return EOK;
+}
+
+/** Send taskbar configuration change notification.
+ *
+ * @param nchan Notification channel (TBARCFG_NOTIFY_DEFAULT)
+ */
+errno_t tbarcfg_notify(const char *nchan)
+{
+	errno_t rc;
+	category_id_t catid;
+	service_id_t *svcs = NULL;
+	size_t count, i;
+
+	rc = loc_category_get_id(nchan, &catid, 0);
+	if (rc != EOK)
+		return rc;
+
+	rc = loc_category_get_svcs(catid, &svcs, &count);
+	if (rc != EOK)
+		return rc;
+
+	for (i = 0; i < count; i++) {
+		rc = tbarcfg_notify_svc(svcs[i]);
+		if (rc != EOK)
+			goto error;
+	}
+
+	free(svcs);
+	return EOK;
+error:
+	free(svcs);
+	return rc;
+}
+
+/** Taskbar configuration connection handler.
+ *
+ * @param icall Initial call
+ * @param arg Argument (tbarcfg_listener_t *)
+ */
+static void tbarcfg_notify_conn(ipc_call_t *icall, void *arg)
+{
+	tbarcfg_listener_t *lst = (tbarcfg_listener_t *)arg;
+
+	/* 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);
+			return;
+		}
+
+		switch (method) {
+		case TBARCFG_NOTIFY_NOTIFY:
+			lst->cb(lst->arg);
+			async_answer_0(&call, EOK);
+			break;
+		default:
+			async_answer_0(&call, EINVAL);
+		}
+	}
+}
+
 /** @}
  */
Index: uspace/lib/tbarcfg/test/tbarcfg.c
===================================================================
--- uspace/lib/tbarcfg/test/tbarcfg.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/tbarcfg/test/tbarcfg.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -30,4 +30,5 @@
 #include <pcut/pcut.h>
 #include <tbarcfg/tbarcfg.h>
+#include <stdbool.h>
 #include <stdio.h>
 
@@ -35,4 +36,10 @@
 
 PCUT_TEST_SUITE(tbarcfg);
+
+typedef struct {
+	bool notified;
+} tbarcfg_test_resp_t;
+
+static void test_cb(void *);
 
 /** Creating, opening and closing taskbar configuration */
@@ -554,3 +561,32 @@
 }
 
+/** Notifications can be delivered from tbarcfg_notify() to a listener. */
+PCUT_TEST(notify)
+{
+	errno_t rc;
+	tbarcfg_listener_t *lst;
+	tbarcfg_test_resp_t test_resp;
+
+	test_resp.notified = false;
+
+	printf("create listener resp=%p\n", (void *)&test_resp);
+	rc = tbarcfg_listener_create(TBARCFG_NOTIFY_DEFAULT,
+	    test_cb, &test_resp, &lst);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_TRUE(test_resp.notified);
+	tbarcfg_listener_destroy(lst);
+}
+
+static void test_cb(void *arg)
+{
+	tbarcfg_test_resp_t *resp = (tbarcfg_test_resp_t *)arg;
+
+	printf("test_cb: executing resp=%p\n", (void *)resp);
+	resp->notified = true;
+}
+
 PCUT_EXPORT(tbarcfg);
Index: uspace/lib/ui/include/ui/menu.h
===================================================================
--- uspace/lib/ui/include/ui/menu.h	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/ui/include/ui/menu.h	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -59,4 +59,5 @@
 extern ui_evclaim_t ui_menu_pos_event(ui_menu_t *, gfx_coord2_t *,
     pos_event_t *);
+extern sysarg_t ui_menu_get_idev_id(ui_menu_t *);
 
 #endif
Index: uspace/lib/ui/include/ui/popup.h
===================================================================
--- uspace/lib/ui/include/ui/popup.h	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/ui/include/ui/popup.h	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -54,4 +54,5 @@
 extern ui_resource_t *ui_popup_get_res(ui_popup_t *);
 extern gfx_context_t *ui_popup_get_gc(ui_popup_t *);
+extern sysarg_t ui_popup_get_idev_id(ui_popup_t *);
 
 #endif
Index: uspace/lib/ui/include/ui/wdecor.h
===================================================================
--- uspace/lib/ui/include/ui/wdecor.h	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/ui/include/ui/wdecor.h	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -44,4 +44,5 @@
 #include <types/ui/event.h>
 #include <types/ui/resource.h>
+#include <types/ui/ui.h>
 #include <types/ui/wdecor.h>
 
@@ -58,5 +59,5 @@
 extern ui_evclaim_t ui_wdecor_kbd_event(ui_wdecor_t *, kbd_event_t *);
 extern ui_evclaim_t ui_wdecor_pos_event(ui_wdecor_t *, pos_event_t *);
-extern void ui_wdecor_rect_from_app(ui_wdecor_style_t, gfx_rect_t *,
+extern void ui_wdecor_rect_from_app(ui_t *, ui_wdecor_style_t, gfx_rect_t *,
     gfx_rect_t *);
 extern void ui_wdecor_app_from_rect(ui_wdecor_style_t, gfx_rect_t *,
Index: uspace/lib/ui/private/menu.h
===================================================================
--- uspace/lib/ui/private/menu.h	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/ui/private/menu.h	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -70,4 +70,6 @@
 	/** Callback argument */
 	void *arg;
+	/** ID of device that activated entry */
+	sysarg_t idev_id;
 };
 
Index: uspace/lib/ui/private/popup.h
===================================================================
--- uspace/lib/ui/private/popup.h	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/ui/private/popup.h	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -58,4 +58,6 @@
 	/** Placement rectangle */
 	gfx_rect_t place;
+	/** ID of device that sent input event */
+	sysarg_t idev_id;
 };
 
Index: uspace/lib/ui/src/menu.c
===================================================================
--- uspace/lib/ui/src/menu.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/ui/src/menu.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -578,4 +578,5 @@
 	ui_menu_t *menu = (ui_menu_t *)arg;
 
+	menu->idev_id = ui_popup_get_idev_id(menu->popup);
 	ui_menu_kbd_event(menu, event);
 }
@@ -592,4 +593,6 @@
 	gfx_coord2_t spos;
 
+	menu->idev_id = ui_popup_get_idev_id(menu->popup);
+
 	spos.x = 0;
 	spos.y = 0;
@@ -641,4 +644,14 @@
 }
 
+/** Get ID of last device that input event.
+ *
+ * @param menu Menu
+ * @return Input device ID
+ */
+sysarg_t ui_menu_get_idev_id(ui_menu_t *menu)
+{
+	return menu->idev_id;
+}
+
 /** @}
  */
Index: uspace/lib/ui/src/menuentry.c
===================================================================
--- uspace/lib/ui/src/menuentry.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/ui/src/menuentry.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -145,5 +145,20 @@
 		return;
 
+	mentry->menu->total_h -= ui_menu_entry_height(mentry);
+	/* NOTE: max_caption_w/max_shortcut_w not updated (speed) */
+
 	list_remove(&mentry->lentries);
+
+	/*
+	 * If we emptied the menu, reset accumulated dims so they
+	 * can be correctly calculated when (if) the menu is
+	 * re-populated.
+	 */
+	if (list_empty(&mentry->menu->entries)) {
+		mentry->menu->total_h = 0;
+		mentry->menu->max_caption_w = 0;
+		mentry->menu->max_shortcut_w = 0;
+	}
+
 	free(mentry->caption);
 	free(mentry);
Index: uspace/lib/ui/src/popup.c
===================================================================
--- uspace/lib/ui/src/popup.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/ui/src/popup.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -190,4 +190,14 @@
 }
 
+/** Get ID of device that sent the last position event.
+ *
+ * @param popup Popup window
+ * @return Input device ID
+ */
+sysarg_t ui_popup_get_idev_id(ui_popup_t *popup)
+{
+	return popup->idev_id;
+}
+
 /** Handle close event in popup window.
  *
@@ -214,4 +224,7 @@
 	ui_popup_t *popup = (ui_popup_t *)arg;
 
+	/* Remember ID of device that sent the last event */
+	popup->idev_id = event->kbd_id;
+
 	if (popup->cb != NULL && popup->cb->kbd != NULL)
 		popup->cb->kbd(popup, popup->arg, event);
@@ -229,4 +242,7 @@
 	ui_popup_t *popup = (ui_popup_t *)arg;
 
+	/* Remember ID of device that sent the last event */
+	popup->idev_id = event->pos_id;
+
 	if (popup->cb != NULL && popup->cb->pos != NULL)
 		popup->cb->pos(popup, popup->arg, event);
Index: uspace/lib/ui/src/wdecor.c
===================================================================
--- uspace/lib/ui/src/wdecor.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/ui/src/wdecor.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -45,4 +45,5 @@
 #include <ui/paint.h>
 #include <ui/pbutton.h>
+#include <ui/ui.h>
 #include <ui/wdecor.h>
 #include "../private/resource.h"
@@ -94,4 +95,8 @@
 	/** Window resizing edge height */
 	wdecor_edge_h = 4,
+	/** Window resizing edge witdth */
+	wdecor_edge_w_text = 1,
+	/** Window resizing edge height */
+	wdecor_edge_h_text = 1,
 	/** Title bar height */
 	wdecor_tbar_h = 22,
@@ -860,22 +865,34 @@
  * and its decoration.
  *
+ * @param ui UI
  * @param style Decoration style
  * @param app Application area rectangle
  * @param rect Place to store (outer) window decoration rectangle
  */
-void ui_wdecor_rect_from_app(ui_wdecor_style_t style, gfx_rect_t *app,
-    gfx_rect_t *rect)
-{
+void ui_wdecor_rect_from_app(ui_t *ui, ui_wdecor_style_t style,
+    gfx_rect_t *app, gfx_rect_t *rect)
+{
+	bool textmode;
+	gfx_coord_t edge_w, edge_h;
 	*rect = *app;
 
+	textmode = ui_is_textmode(ui);
+	if (textmode) {
+		edge_w = wdecor_edge_w_text;
+		edge_h = wdecor_edge_h_text;
+	} else {
+		edge_w = wdecor_edge_w;
+		edge_h = wdecor_edge_h;
+	}
+
 	if ((style & ui_wds_frame) != 0) {
-		rect->p0.x -= wdecor_edge_w;
-		rect->p0.y -= wdecor_edge_h;
-		rect->p1.x += wdecor_edge_w;
-		rect->p1.y += wdecor_edge_h;
-	}
-
-	if ((style & ui_wds_titlebar) != 0)
-		rect->p0.y -= 22;
+		rect->p0.x -= edge_w;
+		rect->p0.y -= edge_h;
+		rect->p1.x += edge_w;
+		rect->p1.y += edge_h;
+	}
+
+	if ((style & ui_wds_titlebar) != 0 && !textmode)
+		rect->p0.y -= wdecor_tbar_h;
 }
 
Index: uspace/lib/ui/test/wdecor.c
===================================================================
--- uspace/lib/ui/test/wdecor.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/ui/test/wdecor.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -34,4 +34,5 @@
 #include <ui/pbutton.h>
 #include <ui/resource.h>
+#include <ui/ui.h>
 #include <ui/wdecor.h>
 #include "../private/wdecor.h"
@@ -1322,6 +1323,11 @@
 PCUT_TEST(rect_from_app)
 {
+	errno_t rc;
+	ui_t *ui = NULL;
 	gfx_rect_t arect;
 	gfx_rect_t rect;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
 	arect.p0.x = 14;
@@ -1330,5 +1336,5 @@
 	arect.p1.y = 196;
 
-	ui_wdecor_rect_from_app(ui_wds_none, &arect, &rect);
+	ui_wdecor_rect_from_app(ui, ui_wds_none, &arect, &rect);
 
 	PCUT_ASSERT_INT_EQUALS(14, rect.p0.x);
@@ -1337,5 +1343,5 @@
 	PCUT_ASSERT_INT_EQUALS(196, rect.p1.y);
 
-	ui_wdecor_rect_from_app(ui_wds_frame, &arect, &rect);
+	ui_wdecor_rect_from_app(ui, ui_wds_frame, &arect, &rect);
 
 	PCUT_ASSERT_INT_EQUALS(10, rect.p0.x);
@@ -1344,5 +1350,5 @@
 	PCUT_ASSERT_INT_EQUALS(200, rect.p1.y);
 
-	ui_wdecor_rect_from_app(ui_wds_decorated, &arect, &rect);
+	ui_wdecor_rect_from_app(ui, ui_wds_decorated, &arect, &rect);
 
 	PCUT_ASSERT_INT_EQUALS(10, rect.p0.x);
@@ -1351,4 +1357,5 @@
 	PCUT_ASSERT_INT_EQUALS(200, rect.p1.y);
 
+	ui_destroy(ui);
 }
 
Index: uspace/lib/usbhid/src/hidparser.c
===================================================================
--- uspace/lib/usbhid/src/hidparser.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/lib/usbhid/src/hidparser.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -225,5 +225,5 @@
 	/* Than we take the higher bits from the LSB */
 	const unsigned bit_offset = item->offset % 8;
-	const int lsb_bits = min(bits, 8);
+	const int lsb_bits = min((unsigned)bits, 8 - bit_offset);
 
 	value |= (*data >> bit_offset) & BIT_RRANGE(uint8_t, lsb_bits);
Index: uspace/srv/hid/output/port/ega.c
===================================================================
--- uspace/srv/hid/output/port/ega.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/srv/hid/output/port/ega.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2011 Martin Decky
  * All rights reserved.
@@ -90,6 +90,11 @@
 		break;
 	case CHAR_ATTR_RGB:
-		attr = (attrs.val.rgb.bgcolor < attrs.val.rgb.fgcolor) ?
-		    ega.style_inverted : ega.style_normal;
+		attr =
+		    ((RED(attrs.val.rgb.fgcolor) >= 0x80) ? 0x40 : 0) |
+		    ((GREEN(attrs.val.rgb.fgcolor) >= 0x80) ? 0x20 : 0) |
+		    ((BLUE(attrs.val.rgb.fgcolor) >= 0x80) ? 0x10 : 0) |
+		    ((RED(attrs.val.rgb.bgcolor) >= 0x80) ? 0x04 : 0) |
+		    ((GREEN(attrs.val.rgb.bgcolor) >= 0x80) ? 0x02 : 0) |
+		    ((BLUE(attrs.val.rgb.bgcolor) >= 0x80) ? 0x01 : 0);
 		break;
 	}
Index: uspace/srv/locsrv/locsrv.c
===================================================================
--- uspace/srv/locsrv/locsrv.c	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ uspace/srv/locsrv/locsrv.c	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -1,5 +1,5 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2007 Josef Cejka
- * Copyright (c) 2011 Jiri Svoboda
  * All rights reserved.
  *
@@ -1354,4 +1354,7 @@
 	categ_dir_add_cat(&cdir, cat);
 
+	cat = category_new("tbarcfg-notif");
+	categ_dir_add_cat(&cdir, cat);
+
 	cat = category_new("test3");
 	categ_dir_add_cat(&cdir, cat);
Index: version
===================================================================
--- version	(revision 6c1e7c09ca4ee029111d3871ef9c8c35eee7f69d)
+++ version	(revision c37c24cca194a603ab7ea2ef3b1f6981cf99692d)
@@ -28,5 +28,5 @@
 
 
-HELENOS_RELEASE = 0.12.1
-HELENOS_CODENAME = Cathode
-HELENOS_COPYRIGHT = Copyright (c) 2001-2022 HelenOS project
+HELENOS_RELEASE = 0.14.1
+HELENOS_CODENAME = Aladar
+HELENOS_COPYRIGHT = Copyright (c) 2001-2024 HelenOS project
