Index: lang-format
===================================================================
--- .clang-format	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,122 +1,0 @@
-# This format definition attempts to be as close to the style of existing
-# HelenOS code as possible. However, there are several significant issues:
-#
-#    "AlignEscapedNewlines: DontAlign" currently hangs
-#        the formatter on some files.
-#
-#    The formatter currently isn't able to align macro definitions,
-#        and removes existing alignment in files.
-#        (There is a patch for this, but it hasn't been integrated yet.)
-#
-#    SpacesBeforeTrailingComments don't apply to C-style comments.
-#
-#    enum definition is always crammed into a single line if it fits,
-#        and there is no option to put members on separate lines.
-#
-#    Possibly others.
-#
-#
-# Provisions are made to reformat the entire source tree via 'make format',
-# but due to the above, this shouldn't be done except to evaluate the
-# impact. Reformatting a single source file can be done using
-#
-#     clang-format -i source/file.c
-#
----
-Language:        Cpp
-BasedOnStyle:  LLVM
-AccessModifierOffset: 0
-AlignAfterOpenBracket: DontAlign
-AlignConsecutiveAssignments: false
-AlignConsecutiveDeclarations: false
-AlignEscapedNewlines: Left
-AlignOperands:   false
-AlignTrailingComments: true
-AllowAllParametersOfDeclarationOnNextLine: true
-AllowShortBlocksOnASingleLine: false
-AllowShortCaseLabelsOnASingleLine: false
-AllowShortFunctionsOnASingleLine: None
-AllowShortIfStatementsOnASingleLine: false
-AllowShortLoopsOnASingleLine: false
-AlwaysBreakAfterDefinitionReturnType: None
-AlwaysBreakAfterReturnType: None
-AlwaysBreakBeforeMultilineStrings: false
-AlwaysBreakTemplateDeclarations: true
-BinPackArguments: true
-BinPackParameters: true
-BraceWrapping:
-  AfterClass:      false
-  AfterControlStatement: false
-  AfterEnum:       false
-  AfterFunction:   true
-  AfterNamespace:  false
-  AfterObjCDeclaration: false
-  AfterStruct:     false
-  AfterUnion:      false
-  BeforeCatch:     false
-  BeforeElse:      false
-  IndentBraces:    false
-  SplitEmptyFunction: true
-  SplitEmptyRecord: true
-  SplitEmptyNamespace: true
-BreakBeforeBinaryOperators: NonAssignment
-BreakBeforeBraces: Custom
-BreakBeforeInheritanceComma: true
-BreakBeforeTernaryOperators: true
-BreakConstructorInitializers: BeforeColon
-BreakStringLiterals: true
-ColumnLimit:     80
-CompactNamespaces: false
-ConstructorInitializerAllOnOneLineOrOnePerLine: false
-ConstructorInitializerIndentWidth: 4
-ContinuationIndentWidth: 4
-Cpp11BracedListStyle: true
-DerivePointerAlignment: false
-FixNamespaceComments: true
-ForEachMacros:
-  - foreach
-  - Q_FOREACH
-  - BOOST_FOREACH
-IncludeCategories:
-  - Regex:           '^"'
-    Priority:        3
-  - Regex:           '^<'
-    Priority:        2
-  - Regex:           '.*'
-    Priority:        1
-IncludeIsMainRegex: '(-test)?$'
-IndentCaseLabels: false
-IndentWidth:     8
-IndentWrappedFunctionNames: false
-KeepEmptyLinesAtTheStartOfBlocks: true
-MacroBlockBegin: ''
-MacroBlockEnd:   ''
-MaxEmptyLinesToKeep: 1
-NamespaceIndentation: None
-PenaltyBreakAssignment: 2
-PenaltyBreakBeforeFirstCallParameter: 19
-PenaltyBreakComment: 300
-PenaltyBreakFirstLessLess: 120
-PenaltyBreakString: 1000
-PenaltyExcessCharacter: 1000000
-PenaltyReturnTypeOnItsOwnLine: 60
-PointerAlignment: Right
-ReflowComments:  true
-SortIncludes:    true
-SortUsingDeclarations: true
-SpaceAfterCStyleCast: true
-SpaceAfterTemplateKeyword: false
-SpaceBeforeAssignmentOperators: true
-SpaceBeforeParens: ControlStatements
-SpaceInEmptyParentheses: false
-SpacesBeforeTrailingComments: 2
-SpacesInAngles:  false
-SpacesInContainerLiterals: false
-SpacesInCStyleCastParentheses: false
-SpacesInParentheses: false
-SpacesInSquareBrackets: false
-Standard:        Cpp11
-TabWidth:        8
-UseTab:          ForIndentation
-...
-
Index: .gitignore
===================================================================
--- .gitignore	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ .gitignore	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -4,4 +4,5 @@
 /doxygen/latex
 /tools/*.pyc
+/tools/__pycache__
 tags
 tools/sycek/
Index: README.md
===================================================================
--- README.md	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ README.md	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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/_bits/decls.h
===================================================================
--- abi/include/_bits/decls.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ abi/include/_bits/decls.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -61,4 +61,14 @@
 #endif  /* __cplusplus */
 
+/*
+ * __CONSTEXPR for headers shared by C and C++. Headers that are purely for
+ * C++ sources should use constexpr directly.
+ */
+#ifdef __cplusplus
+#define __CONSTEXPR constexpr
+#else
+#define __CONSTEXPR
+#endif
+
 #endif
 
Index: abi/include/_bits/uchar.h
===================================================================
--- abi/include/_bits/uchar.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ abi/include/_bits/uchar.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -38,5 +38,12 @@
 #include <stdint.h>
 
+/*
+ * char8_t became part of C++ with C++20.
+ * It is part of C23 hence we define it for C compilations only.
+ */
+#ifndef __cplusplus
 typedef uint8_t char8_t;
+#endif
+
 typedef uint32_t char32_t;
 
Index: abi/include/abi/ipc/interfaces.h
===================================================================
--- abi/include/abi/ipc/interfaces.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ abi/include/abi/ipc/interfaces.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2014 Martin Decky
  * All rights reserved.
@@ -186,4 +187,8 @@
 	INTERFACE_DDEV =
 	    FOURCC_COMPACT('d', 'd', 'e', 'v') | IFACE_EXCHANGE_SERIALIZE,
+	INTERFACE_DISPCFG =
+	    FOURCC_COMPACT('d', 'c', 'f', 'g') | IFACE_EXCHANGE_SERIALIZE,
+	INTERFACE_DISPCFG_CB =
+	    FOURCC_COMPACT('d', 'c', 'f', 'g') | IFACE_EXCHANGE_SERIALIZE | IFACE_MOD_CALLBACK,
 	INTERFACE_DISPLAY =
 	    FOURCC_COMPACT('d', 's', 'p', 'l') | IFACE_EXCHANGE_SERIALIZE,
@@ -192,4 +197,10 @@
 	INTERFACE_GC =
 	    FOURCC_COMPACT('g', 'f', 'x', 'c') | IFACE_EXCHANGE_SERIALIZE,
+	INTERFACE_WNDMGT =
+	    FOURCC_COMPACT('w', 'm', 'g', 't') | IFACE_EXCHANGE_SERIALIZE,
+	INTERFACE_WNDMGT_CB =
+	    FOURCC_COMPACT('w', 'm', 'g', 't') | IFACE_EXCHANGE_SERIALIZE | IFACE_MOD_CALLBACK,
+	INTERFACE_TBARCFG_NOTIFY =
+	    FOURCC_COMPACT('t', 'b', 'c', 'f') | IFACE_EXCHANGE_SERIALIZE
 } iface_t;
 
Index: abi/include/abi/proc/uarg.h
===================================================================
--- abi/include/abi/proc/uarg.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ abi/include/abi/proc/uarg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -39,5 +39,5 @@
 #include <_bits/native.h>
 
-typedef void (uspace_thread_function_t)(void *);
+typedef void uspace_thread_function_t(void *);
 
 /** Structure passed to uinit kernel thread as argument. */
Index: abi/include/abi/sysinfo.h
===================================================================
--- abi/include/abi/sysinfo.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ abi/include/abi/sysinfo.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -37,4 +37,5 @@
 #define _ABI_SYSINFO_H_
 
+#include <_bits/size_t.h>
 #include <stdbool.h>
 #include <abi/proc/task.h>
Index: boot/arch/arm32/_link.ld.in
===================================================================
--- boot/arch/arm32/_link.ld.in	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/arm32/_link.ld.in	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -7,6 +7,6 @@
 	.text : {
 		loader_start = .;
-		*(BOOTSTRAP);
-		*(.text);
+		KEEP(*(BOOTSTRAP));
+		*(.text .text.*);
 	}
 	. = BOOT_BASE + 0x8000;
@@ -14,16 +14,14 @@
 		*(BOOTPT);      /* bootstrap page table */
 		*(BOOTSTACK);   /* bootstrap stack */
-		*(.data);       /* initialized data */
-		*(.rodata);
-		*(.rodata.*);
-		*(.sdata);
-		*(.reginfo);
-		*(.sbss);
-		*(.scommon);
-		*(.bss);        /* uninitialized static variables */
+		*(.data .data.*);       /* initialized data */
+		*(.rodata .rodata.*);
+		*(.sdata .sdata.*);
+		*(.sbss .sbss.*);
+		*(.scommon .scommon.*);
+		*(.bss .bss.*);        /* uninitialized static variables */
 		*(COMMON);      /* global variables */
 		loader_end = .;
 		payload_start = .;
-		*(.payload);
+		KEEP(*(.payload));
 		payload_end = .;
 	}
Index: boot/arch/arm32/meson.build
===================================================================
--- boot/arch/arm32/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/arm32/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -65,10 +65,10 @@
 	'src/mm.c',
 	'src/putchar.c',
+	'../../../common/stdc/mem.c',
 	'../../genarch/src/division.c',
-	'../../generic/src/memstr.c',
 	'../../generic/src/printf_core.c',
 	'../../generic/src/vprintf.c',
 	'../../generic/src/printf.c',
-	'../../generic/src/str.c',
+	'../../../common/str.c',
 	'../../generic/src/version.c',
 	'../../generic/src/inflate.c',
Index: boot/arch/arm32/src/main.c
===================================================================
--- boot/arch/arm32/src/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/arm32/src/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -40,5 +40,5 @@
 #include <halt.h>
 #include <printf.h>
-#include <memstr.h>
+#include <mem.h>
 #include <version.h>
 #include <macros.h>
Index: boot/arch/arm64/_link.ld.in
===================================================================
--- boot/arch/arm64/_link.ld.in	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/arm64/_link.ld.in	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -7,6 +7,6 @@
 	.text : {
 		HIDDEN(loader_start = .);
-		*(BOOTSTRAP);
-		*(.text);
+		KEEP(*(BOOTSTRAP));
+		*(.text .text.*);
 	}
 	.dynamic : { *(.dynamic); }
@@ -17,12 +17,12 @@
 	. = 0x8000;
 	.data : {
-		*(.data*);      /* initialized data */
-		*(.rodata*);
-		*(.bss);        /* uninitialized static variables */
+		*(.data .data.*);      /* initialized data */
+		*(.rodata .rodata.*);
+		*(.bss .bss.*);        /* uninitialized static variables */
 		*(COMMON);      /* global variables */
-		*(.got .got.plt);
+		*(.got .got.*);
 		HIDDEN(loader_end = .);
 		HIDDEN(payload_start = .);
-		*(.payload);
+		KEEP(*(.payload));
 		HIDDEN(payload_end = .);
 	}
Index: boot/arch/arm64/meson.build
===================================================================
--- boot/arch/arm64/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/arm64/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -48,13 +48,13 @@
 	'src/main.c',
 	'src/relocate.c',
+	'../../../common/stdc/mem.c',
 	'../../genarch/src/efi.c',
 	'../../generic/src/gzip.c',
 	'../../generic/src/inflate.c',
 	'../../generic/src/kernel.c',
-	'../../generic/src/memstr.c',
 	'../../generic/src/payload.c',
 	'../../generic/src/printf.c',
 	'../../generic/src/printf_core.c',
-	'../../generic/src/str.c',
+	'../../../common/str.c',
 	'../../generic/src/tar.c',
 	'../../generic/src/version.c',
Index: boot/arch/arm64/src/main.c
===================================================================
--- boot/arch/arm64/src/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/arm64/src/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -46,5 +46,5 @@
 #include <kernel.h>
 #include <macros.h>
-#include <memstr.h>
+#include <mem.h>
 #include <payload.h>
 #include <printf.h>
Index: boot/arch/ia64/_link.ld.in
===================================================================
--- boot/arch/ia64/_link.ld.in	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/ia64/_link.ld.in	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -5,20 +5,18 @@
 	.boot 0x4400000: AT (0x4400000) {
 		loader_start = .;
-		*(BOOTSTRAP);
-		*(.text);
-		*(.text.*);
-		*(.sdata);
-		*(.sdata2);
-		*(.sbss);
-		*(.rodata);
-		*(.rodata.*);
+		KEEP(*(BOOTSTRAP));
+		*(.text .text.*);
+		*(.sdata .sdata.*);
+		*(.sdata2 .sdata2.*);
+		*(.sbss .sbss.*);
+		*(.rodata .rodata.*);
 		*(.data .data.*);       /* initialized data */
 		__gp = . ;
 		*(.got .got.*);
-		*(.bss);                /* uninitialized static variables */
+		*(.bss .bss.*);                /* uninitialized static variables */
 		*(COMMON);
 		loader_end = .;
 		payload_start = .;
-		*(.payload);
+		KEEP(*(.payload));
 		payload_end = .;
 	}
Index: boot/arch/ia64/meson.build
===================================================================
--- boot/arch/ia64/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/ia64/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -41,12 +41,12 @@
 	'src/pal_asm.S',
 	'src/putchar.c',
+	'../../../common/stdc/mem.c',
 	'../../genarch/src/efi.c',
 	'../../genarch/src/division.c',
 	'../../generic/src/balloc.c',
-	'../../generic/src/memstr.c',
 	'../../generic/src/printf_core.c',
 	'../../generic/src/vprintf.c',
 	'../../generic/src/printf.c',
-	'../../generic/src/str.c',
+	'../../../common/str.c',
 	'../../generic/src/version.c',
 	'../../generic/src/inflate.c',
Index: boot/arch/ia64/src/main.c
===================================================================
--- boot/arch/ia64/src/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/ia64/src/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -37,5 +37,5 @@
 #include <halt.h>
 #include <printf.h>
-#include <memstr.h>
+#include <mem.h>
 #include <version.h>
 #include <macros.h>
Index: boot/arch/mips32/_link.ld.in
===================================================================
--- boot/arch/mips32/_link.ld.in	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/mips32/_link.ld.in	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -9,20 +9,19 @@
 	.text : {
 		loader_start = .;
-		*(BOOTSTRAP);
-		*(.text);
+		KEEP(*(BOOTSTRAP));
+		*(.text .text.*);
 	}
 	.data : {
-		*(.data);       /* initialized data */
-		*(.rodata);
-		*(.rodata.*);
-		*(.sdata);
+		*(.data .data.*);       /* initialized data */
+		*(.rodata .rodata.*);
+		*(.sdata .sdata.*);
 		*(.reginfo);
-		*(.sbss);
-		*(.scommon);
-		*(.bss);        /* uninitialized static variables */
+		*(.sbss .sbss.*);
+		*(.scommon .scommon.*);
+		*(.bss .bss.*);        /* uninitialized static variables */
 		*(COMMON);      /* global variables */
 		loader_end = .;
 		payload_start = .;
-		*(.payload);
+		KEEP(*(.payload));
 		payload_end = .;
 	}
Index: boot/arch/mips32/meson.build
===================================================================
--- boot/arch/mips32/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/mips32/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -42,11 +42,11 @@
 	'src/main.c',
 	'src/putchar.c',
+	'../../../common/stdc/mem.c',
 	'../../genarch/src/division.c',
 	'../../genarch/src/multiplication.c',
-	'../../generic/src/memstr.c',
 	'../../generic/src/printf_core.c',
 	'../../generic/src/vprintf.c',
 	'../../generic/src/printf.c',
-	'../../generic/src/str.c',
+	'../../../common/str.c',
 	'../../generic/src/version.c',
 	'../../generic/src/inflate.c',
Index: boot/arch/mips32/src/main.c
===================================================================
--- boot/arch/mips32/src/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/mips32/src/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -33,5 +33,5 @@
 #include <halt.h>
 #include <printf.h>
-#include <memstr.h>
+#include <mem.h>
 #include <version.h>
 #include <macros.h>
Index: boot/arch/ppc32/_link.ld.in
===================================================================
--- boot/arch/ppc32/_link.ld.in	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/ppc32/_link.ld.in	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -5,21 +5,18 @@
 	.text : {
 		loader_start = .;
-		*(BOOTSTRAP);
-		*(.text);
-		*(.text.*);
+		KEEP(*(BOOTSTRAP));
+		*(.text .text.*);
 	}
 	.data : {
-		*(.data);       /* initialized data */
-		*(.rodata);
-		*(.rodata.*);
-		*(.sdata);
-		*(.reginfo);
-		*(.sbss);
-		*(.scommon);
-		*(.bss);        /* uninitialized static variables */
+		*(.data .data.*);       /* initialized data */
+		*(.rodata .rodata.*);
+		*(.sdata .sdata.*);
+		*(.sbss .sbss.*);
+		*(.scommon .scommon.*);
+		*(.bss .bss.*);        /* uninitialized static variables */
 		*(COMMON);      /* global variables */
 		loader_end = .;
 		payload_start = .;
-		*(.payload);
+		KEEP(*(.payload));
 		payload_end = .;
 	}
Index: boot/arch/ppc32/meson.build
===================================================================
--- boot/arch/ppc32/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/ppc32/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -37,4 +37,5 @@
 	'src/ofw.c',
 
+	'../../../common/stdc/mem.c',
 	'../../genarch/src/ofw.c',
 	'../../genarch/src/ofw_tree.c',
@@ -42,9 +43,8 @@
 	'../../genarch/src/multiplication.c',
 	'../../generic/src/balloc.c',
-	'../../generic/src/memstr.c',
 	'../../generic/src/printf_core.c',
 	'../../generic/src/vprintf.c',
 	'../../generic/src/printf.c',
-	'../../generic/src/str.c',
+	'../../../common/str.c',
 	'../../generic/src/version.c',
 	'../../generic/src/inflate.c',
Index: boot/arch/ppc32/src/main.c
===================================================================
--- boot/arch/ppc32/src/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/ppc32/src/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -35,5 +35,5 @@
 #include <halt.h>
 #include <printf.h>
-#include <memstr.h>
+#include <mem.h>
 #include <version.h>
 #include <macros.h>
Index: boot/arch/riscv64/_link.ld.in
===================================================================
--- boot/arch/riscv64/_link.ld.in	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/riscv64/_link.ld.in	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -8,6 +8,6 @@
 	.text : {
 		loader_start = .;
-		*(BOOTSTRAP);
-		*(.text);
+		KEEP(*(BOOTSTRAP));
+		*(.text .text.*);
 	}
 
@@ -27,16 +27,14 @@
 
 	.data : {
-		*(.data);       /* initialized data */
-		*(.rodata);
-		*(.rodata.*);
-		*(.sdata);
-		*(.reginfo);
-		*(.sbss);
-		*(.scommon);
-		*(.bss);        /* uninitialized static variables */
+		*(.data .data.*);       /* initialized data */
+		*(.rodata .rodata.*);
+		*(.sdata .sdata.*);
+		*(.sbss .sbss.*);
+		*(.scommon .scommon.*);
+		*(.bss .bss.*);        /* uninitialized static variables */
 		*(COMMON);      /* global variables */
 		loader_end = .;
 		payload_start = .;
-		*(.payload);
+		KEEP(*(.payload));
 		payload_end = .;
 	}
Index: boot/arch/riscv64/meson.build
===================================================================
--- boot/arch/riscv64/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/riscv64/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -37,9 +37,10 @@
 	'src/ucb.c',
 	'src/putchar.c',
-	'../../generic/src/memstr.c',
+
+	'../../../common/stdc/mem.c',
 	'../../generic/src/printf_core.c',
 	'../../generic/src/vprintf.c',
 	'../../generic/src/printf.c',
-	'../../generic/src/str.c',
+	'../../../common/str.c',
 	'../../generic/src/version.c',
 	'../../generic/src/inflate.c',
Index: boot/arch/sparc64/_link.ld.in
===================================================================
--- boot/arch/sparc64/_link.ld.in	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/sparc64/_link.ld.in	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -5,18 +5,16 @@
 	.boot : {
 		loader_start = .;
-		*(BOOTSTRAP);
-		*(.text.*);
+		KEEP(*(BOOTSTRAP));
+		*(.text .text.*);
 
-		*(.data);       /* initialized data */
-		*(.rodata);
-		*(.rodata.*);
-		*(.sdata);
-		*(.reginfo);
-		*(.sbss);
-		*(.bss);        /* uninitialized static variables */
+		*(.data .data.*);       /* initialized data */
+		*(.rodata .rodata.*);
+		*(.sdata .sdata.*);
+		*(.sbss .sbss.*);
+		*(.bss .bss.*);        /* uninitialized static variables */
 		*(COMMON);      /* global variables */
 		loader_end = .;
 		payload_start = .;
-		*(.payload);
+		KEEP(*(.payload));
 		payload_end = .;
 	}
Index: boot/arch/sparc64/meson.build
===================================================================
--- boot/arch/sparc64/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/sparc64/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -37,12 +37,12 @@
 	'src/ofw.c',
 
+	'../../../common/stdc/mem.c',
 	'../../genarch/src/ofw.c',
 	'../../genarch/src/ofw_tree.c',
 	'../../generic/src/balloc.c',
-	'../../generic/src/memstr.c',
 	'../../generic/src/printf_core.c',
 	'../../generic/src/vprintf.c',
 	'../../generic/src/printf.c',
-	'../../generic/src/str.c',
+	'../../../common/str.c',
 	'../../generic/src/version.c',
 	'../../generic/src/inflate.c',
Index: boot/arch/sparc64/src/main.c
===================================================================
--- boot/arch/sparc64/src/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/arch/sparc64/src/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,5 +36,5 @@
 #include <halt.h>
 #include <printf.h>
-#include <memstr.h>
+#include <mem.h>
 #include <version.h>
 #include <macros.h>
Index: boot/genarch/src/ofw_tree.c
===================================================================
--- boot/genarch/src/ofw_tree.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/genarch/src/ofw_tree.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -33,5 +33,5 @@
 #include <str.h>
 #include <balloc.h>
-#include <memstr.h>
+#include <mem.h>
 
 static char path[OFW_TREE_PATH_MAX_LEN + 1];
Index: boot/generic/include/assert.h
===================================================================
--- boot/generic/include/assert.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ boot/generic/include/assert.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * 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.
+ */
+
+#ifndef BOOT_ASSERT_H_
+#define BOOT_ASSERT_H_
+
+#define assert(expr) ((void) (expr))
+
+#endif /* BOOT_ASSERT_H_ */
Index: boot/generic/include/errno.h
===================================================================
--- boot/generic/include/errno.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/generic/include/errno.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -39,4 +39,5 @@
 #define EINVAL     -14  /* Invalid value. */
 #define EOVERFLOW  -16  /* The result does not fit its size. */
+#define EIO        -17
 
 typedef int errno_t;
Index: ot/generic/include/memstr.h
===================================================================
--- boot/generic/include/memstr.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,49 +1,0 @@
-/*
- * Copyright (c) 2001-2004 Jakub Jermar
- * 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.
- */
-
-/** @file
- */
-
-#ifndef BOOT_MEMSTR_H_
-#define BOOT_MEMSTR_H_
-
-#include <stddef.h>
-
-extern void *memcpy(void *, const void *, size_t)
-    __attribute__((nonnull(1, 2)))
-    __attribute__((optimize("-fno-tree-loop-distribute-patterns")));
-extern void *memset(void *, int, size_t)
-    __attribute__((nonnull(1)))
-    __attribute__((optimize("-fno-tree-loop-distribute-patterns")));
-extern void *memmove(void *, const void *, size_t)
-    __attribute__((nonnull(1, 2)));
-
-#endif
-
-/** @}
- */
Index: ot/generic/include/str.h
===================================================================
--- boot/generic/include/str.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,64 +1,0 @@
-/*
- * Copyright (c) 2001-2004 Jakub Jermar
- * Copyright (c) 2005 Martin Decky
- * Copyright (c) 2011 Oleg Romanenko
- * 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.
- */
-
-/** @file
- */
-
-#ifndef BOOT_STR_H_
-#define BOOT_STR_H_
-
-#include <errno.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <uchar.h>
-
-/* Common Unicode characters */
-#define U_SPECIAL  '?'
-
-/** No size limit constant */
-#define STR_NO_LIMIT  ((size_t) -1)
-
-extern char32_t str_decode(const char *str, size_t *offset, size_t sz);
-extern errno_t chr_encode(char32_t ch, char *str, size_t *offset, size_t sz);
-
-extern size_t str_size(const char *str);
-extern size_t str_lsize(const char *str, size_t max_len);
-extern size_t str_length(const char *str);
-
-extern bool ascii_check(char32_t ch);
-extern bool chr_check(char32_t ch);
-
-extern int str_cmp(const char *s1, const char *s2);
-extern void str_cpy(char *dest, size_t size, const char *src);
-
-#endif
-
-/** @}
- */
Index: boot/generic/src/gzip.c
===================================================================
--- boot/generic/src/gzip.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/generic/src/gzip.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -32,5 +32,5 @@
 #include <stddef.h>
 #include <errno.h>
-#include <memstr.h>
+#include <mem.h>
 #include <byteorder.h>
 #include <gzip.h>
Index: boot/generic/src/inflate.c
===================================================================
--- boot/generic/src/inflate.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/generic/src/inflate.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -69,5 +69,5 @@
 #include <stdint.h>
 #include <errno.h>
-#include <memstr.h>
+#include <mem.h>
 #include <inflate.h>
 
Index: ot/generic/src/memstr.c
===================================================================
--- boot/generic/src/memstr.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,123 +1,0 @@
-/*
- * Copyright (c) 2010 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include <memstr.h>
-#include <stddef.h>
-#include <stdint.h>
-
-/** Move memory block without overlapping.
- *
- * Copy cnt bytes from src address to dst address. The source
- * and destination memory areas cannot overlap.
- *
- * @param dst Destination address to copy to.
- * @param src Source address to copy from.
- * @param cnt Number of bytes to copy.
- *
- * @return Destination address.
- *
- */
-void *memcpy(void *dst, const void *src, size_t cnt)
-{
-	uint8_t *dp = (uint8_t *) dst;
-	const uint8_t *sp = (uint8_t *) src;
-
-	while (cnt-- != 0)
-		*dp++ = *sp++;
-
-	return dst;
-}
-
-/** Fill block of memory.
- *
- * Fill cnt bytes at dst address with the value val.
- *
- * @param dst Destination address to fill.
- * @param val Value to fill.
- * @param cnt Number of bytes to fill.
- *
- * @return Destination address.
- *
- */
-void *memset(void *dst, int val, size_t cnt)
-{
-	uint8_t *dp = (uint8_t *) dst;
-
-	while (cnt-- != 0)
-		*dp++ = val;
-
-	return dst;
-}
-
-/** Move memory block with possible overlapping.
- *
- * Copy cnt bytes from src address to dst address. The source
- * and destination memory areas may overlap.
- *
- * @param dst Destination address to copy to.
- * @param src Source address to copy from.
- * @param cnt Number of bytes to copy.
- *
- * @return Destination address.
- *
- */
-void *memmove(void *dst, const void *src, size_t cnt)
-{
-	/* Nothing to do? */
-	if (src == dst)
-		return dst;
-
-	/* Non-overlapping? */
-	if ((dst >= src + cnt) || (src >= dst + cnt))
-		return memcpy(dst, src, cnt);
-
-	uint8_t *dp;
-	const uint8_t *sp;
-
-	/* Which direction? */
-	if (src > dst) {
-		/* Forwards. */
-		dp = dst;
-		sp = src;
-
-		while (cnt-- != 0)
-			*dp++ = *sp++;
-	} else {
-		/* Backwards. */
-		dp = dst + (cnt - 1);
-		sp = src + (cnt - 1);
-
-		while (cnt-- != 0)
-			*dp-- = *sp--;
-	}
-
-	return dst;
-}
-
-/** @}
- */
Index: boot/generic/src/payload.c
===================================================================
--- boot/generic/src/payload.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/generic/src/payload.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,5 +36,5 @@
 #include <gzip.h>
 #include <stdbool.h>
-#include <memstr.h>
+#include <mem.h>
 #include <errno.h>
 #include <str.h>
Index: ot/generic/src/str.c
===================================================================
--- boot/generic/src/str.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,426 +1,0 @@
-/*
- * Copyright (c) 2001-2004 Jakub Jermar
- * Copyright (c) 2005 Martin Decky
- * Copyright (c) 2008 Jiri Svoboda
- * Copyright (c) 2011 Martin Sucha
- * Copyright (c) 2011 Oleg Romanenko
- * 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.
- */
-
-/**
- * @file
- * @brief String functions.
- *
- * Strings and characters use the Universal Character Set (UCS). The standard
- * strings, called just strings are encoded in UTF-8. Wide strings (encoded
- * in UTF-32) are supported to a limited degree. A single character is
- * represented as char32_t.@n
- *
- * Overview of the terminology:@n
- *
- *  Term                  Meaning
- *  --------------------  ----------------------------------------------------
- *  byte                  8 bits stored in uint8_t (unsigned 8 bit integer)
- *
- *  character             UTF-32 encoded Unicode character, stored in char32_t
- *                        (unsigned 32 bit integer), code points 0 .. 1114111
- *                        are valid
- *
- *  ASCII character       7 bit encoded ASCII character, stored in char
- *                        (usually signed 8 bit integer), code points 0 .. 127
- *                        are valid
- *
- *  string                UTF-8 encoded NULL-terminated Unicode string, char *
- *
- *  wide string           UTF-32 encoded NULL-terminated Unicode string,
- *                        char32_t *
- *
- *  [wide] string size    number of BYTES in a [wide] string (excluding
- *                        the NULL-terminator), size_t
- *
- *  [wide] string length  number of CHARACTERS in a [wide] string (excluding
- *                        the NULL-terminator), size_t
- *
- *  [wide] string width   number of display cells on a monospace display taken
- *                        by a [wide] string, size_t
- *
- *
- * Overview of string metrics:@n
- *
- *  Metric  Abbrev.  Type     Meaning
- *  ------  ------   ------   -------------------------------------------------
- *  size    n        size_t   number of BYTES in a string (excluding the
- *                            NULL-terminator)
- *
- *  length  l        size_t   number of CHARACTERS in a string (excluding the
- *                            null terminator)
- *
- *  width  w         size_t   number of display cells on a monospace display
- *                            taken by a string
- *
- *
- * Function naming prefixes:@n
- *
- *  chr_    operate on characters
- *  ascii_  operate on ASCII characters
- *  str_    operate on strings
- *  wstr_   operate on wide strings
- *
- *  [w]str_[n|l|w]  operate on a prefix limited by size, length
- *                  or width
- *
- *
- * A specific character inside a [wide] string can be referred to by:@n
- *
- *  pointer (char *, char32_t *)
- *  byte offset (size_t)
- *  character index (size_t)
- *
- */
-
-#include <str.h>
-
-#include <errno.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-
-/** Byte mask consisting of lowest @n bits (out of 8) */
-#define LO_MASK_8(n)  ((uint8_t) ((1 << (n)) - 1))
-
-/** Byte mask consisting of lowest @n bits (out of 32) */
-#define LO_MASK_32(n)  ((uint32_t) ((1 << (n)) - 1))
-
-/** Byte mask consisting of highest @n bits (out of 8) */
-#define HI_MASK_8(n)  (~LO_MASK_8(8 - (n)))
-
-/** Number of data bits in a UTF-8 continuation byte */
-#define CONT_BITS  6
-
-/** Decode a single character from a string.
- *
- * Decode a single character from a string of size @a size. Decoding starts
- * at @a offset and this offset is moved to the beginning of the next
- * character. In case of decoding error, offset generally advances at least
- * by one. However, offset is never moved beyond size.
- *
- * @param str    String (not necessarily NULL-terminated).
- * @param offset Byte offset in string where to start decoding.
- * @param size   Size of the string (in bytes).
- *
- * @return Value of decoded character, U_SPECIAL on decoding error or
- *         NULL if attempt to decode beyond @a size.
- *
- */
-char32_t str_decode(const char *str, size_t *offset, size_t size)
-{
-	if (*offset + 1 > size)
-		return 0;
-
-	/* First byte read from string */
-	uint8_t b0 = (uint8_t) str[(*offset)++];
-
-	/* Determine code length */
-
-	unsigned int b0_bits;  /* Data bits in first byte */
-	unsigned int cbytes;   /* Number of continuation bytes */
-
-	if ((b0 & 0x80) == 0) {
-		/* 0xxxxxxx (Plain ASCII) */
-		b0_bits = 7;
-		cbytes = 0;
-	} else if ((b0 & 0xe0) == 0xc0) {
-		/* 110xxxxx 10xxxxxx */
-		b0_bits = 5;
-		cbytes = 1;
-	} else if ((b0 & 0xf0) == 0xe0) {
-		/* 1110xxxx 10xxxxxx 10xxxxxx */
-		b0_bits = 4;
-		cbytes = 2;
-	} else if ((b0 & 0xf8) == 0xf0) {
-		/* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
-		b0_bits = 3;
-		cbytes = 3;
-	} else {
-		/* 10xxxxxx -- unexpected continuation byte */
-		return U_SPECIAL;
-	}
-
-	if (*offset + cbytes > size)
-		return U_SPECIAL;
-
-	char32_t ch = b0 & LO_MASK_8(b0_bits);
-
-	/* Decode continuation bytes */
-	while (cbytes > 0) {
-		uint8_t b = (uint8_t) str[(*offset)++];
-
-		/* Must be 10xxxxxx */
-		if ((b & 0xc0) != 0x80)
-			return U_SPECIAL;
-
-		/* Shift data bits to ch */
-		ch = (ch << CONT_BITS) | (char32_t) (b & LO_MASK_8(CONT_BITS));
-		cbytes--;
-	}
-
-	return ch;
-}
-
-/** Encode a single character to string representation.
- *
- * Encode a single character to string representation (i.e. UTF-8) and store
- * it into a buffer at @a offset. Encoding starts at @a offset and this offset
- * is moved to the position where the next character can be written to.
- *
- * @param ch     Input character.
- * @param str    Output buffer.
- * @param offset Byte offset where to start writing.
- * @param size   Size of the output buffer (in bytes).
- *
- * @return EOK if the character was encoded successfully, EOVERFLOW if there
- *         was not enough space in the output buffer or EINVAL if the character
- *         code was invalid.
- */
-errno_t chr_encode(const char32_t ch, char *str, size_t *offset, size_t size)
-{
-	if (*offset >= size)
-		return EOVERFLOW;
-
-	if (!chr_check(ch))
-		return EINVAL;
-
-	/*
-	 * Unsigned version of ch (bit operations should only be done
-	 * on unsigned types).
-	 */
-	uint32_t cc = (uint32_t) ch;
-
-	/* Determine how many continuation bytes are needed */
-
-	unsigned int b0_bits;  /* Data bits in first byte */
-	unsigned int cbytes;   /* Number of continuation bytes */
-
-	if ((cc & ~LO_MASK_32(7)) == 0) {
-		b0_bits = 7;
-		cbytes = 0;
-	} else if ((cc & ~LO_MASK_32(11)) == 0) {
-		b0_bits = 5;
-		cbytes = 1;
-	} else if ((cc & ~LO_MASK_32(16)) == 0) {
-		b0_bits = 4;
-		cbytes = 2;
-	} else if ((cc & ~LO_MASK_32(21)) == 0) {
-		b0_bits = 3;
-		cbytes = 3;
-	} else {
-		/* Codes longer than 21 bits are not supported */
-		return EINVAL;
-	}
-
-	/* Check for available space in buffer */
-	if (*offset + cbytes >= size)
-		return EOVERFLOW;
-
-	/* Encode continuation bytes */
-	unsigned int i;
-	for (i = cbytes; i > 0; i--) {
-		str[*offset + i] = 0x80 | (cc & LO_MASK_32(CONT_BITS));
-		cc = cc >> CONT_BITS;
-	}
-
-	/* Encode first byte */
-	str[*offset] = (cc & LO_MASK_32(b0_bits)) | HI_MASK_8(8 - b0_bits - 1);
-
-	/* Advance offset */
-	*offset += cbytes + 1;
-
-	return EOK;
-}
-
-/** Get size of string.
- *
- * Get the number of bytes which are used by the string @a str (excluding the
- * NULL-terminator).
- *
- * @param str String to consider.
- *
- * @return Number of bytes used by the string
- *
- */
-size_t str_size(const char *str)
-{
-	size_t size = 0;
-
-	while (*str++ != 0)
-		size++;
-
-	return size;
-}
-
-/** Get size of string with length limit.
- *
- * Get the number of bytes which are used by up to @a max_len first
- * characters in the string @a str. If @a max_len is greater than
- * the length of @a str, the entire string is measured (excluding the
- * NULL-terminator).
- *
- * @param str     String to consider.
- * @param max_len Maximum number of characters to measure.
- *
- * @return Number of bytes used by the characters.
- *
- */
-size_t str_lsize(const char *str, size_t max_len)
-{
-	size_t len = 0;
-	size_t offset = 0;
-
-	while (len < max_len) {
-		if (str_decode(str, &offset, STR_NO_LIMIT) == 0)
-			break;
-
-		len++;
-	}
-
-	return offset;
-}
-
-/** Get number of characters in a string.
- *
- * @param str NULL-terminated string.
- *
- * @return Number of characters in string.
- *
- */
-size_t str_length(const char *str)
-{
-	size_t len = 0;
-	size_t offset = 0;
-
-	while (str_decode(str, &offset, STR_NO_LIMIT) != 0)
-		len++;
-
-	return len;
-}
-
-/** Check whether character is plain ASCII.
- *
- * @return True if character is plain ASCII.
- *
- */
-bool ascii_check(char32_t ch)
-{
-	if (ch <= 127)
-		return true;
-
-	return false;
-}
-
-/** Check whether character is valid
- *
- * @return True if character is a valid Unicode code point.
- *
- */
-bool chr_check(char32_t ch)
-{
-	if (ch <= 1114111)
-		return true;
-
-	return false;
-}
-
-/** Compare two NULL terminated strings.
- *
- * Do a char-by-char comparison of two NULL-terminated strings.
- * The strings are considered equal iff their length is equal
- * and both strings consist of the same sequence of characters.
- *
- * A string S1 is less than another string S2 if it has a character with
- * lower value at the first character position where the strings differ.
- * If the strings differ in length, the shorter one is treated as if
- * padded by characters with a value of zero.
- *
- * @param s1 First string to compare.
- * @param s2 Second string to compare.
- *
- * @return 0 if the strings are equal, -1 if the first is less than the second,
- *         1 if the second is less than the first.
- *
- */
-int str_cmp(const char *s1, const char *s2)
-{
-	char32_t c1 = 0;
-	char32_t c2 = 0;
-
-	size_t off1 = 0;
-	size_t off2 = 0;
-
-	while (true) {
-		c1 = str_decode(s1, &off1, STR_NO_LIMIT);
-		c2 = str_decode(s2, &off2, STR_NO_LIMIT);
-
-		if (c1 < c2)
-			return -1;
-
-		if (c1 > c2)
-			return 1;
-
-		if (c1 == 0 || c2 == 0)
-			break;
-	}
-
-	return 0;
-}
-
-/** Copy string.
- *
- * Copy source string @a src to destination buffer @a dest.
- * No more than @a size bytes are written. If the size of the output buffer
- * is at least one byte, the output string will always be well-formed, i.e.
- * null-terminated and containing only complete characters.
- *
- * @param dest  Destination buffer.
- * @param count Size of the destination buffer (must be > 0).
- * @param src   Source string.
- *
- */
-void str_cpy(char *dest, size_t size, const char *src)
-{
-	size_t src_off = 0;
-	size_t dest_off = 0;
-
-	char32_t ch;
-	while ((ch = str_decode(src, &src_off, STR_NO_LIMIT)) != 0) {
-		if (chr_encode(ch, dest, &dest_off, size - 1) != EOK)
-			break;
-	}
-
-	dest[dest_off] = '\0';
-}
-
-/** @}
- */
Index: boot/meson.build
===================================================================
--- boot/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ boot/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -78,4 +78,5 @@
 		'../abi/arch'/BARCH/'include',
 		'../abi/include',
+		'../common/include',
 	)
 
@@ -98,5 +99,4 @@
 		'-Wl,--nmagic',
 		'-T', meson.current_build_dir()/'_link.ld',
-		'-Wl,--no-gc-sections',
 	]
 
Index: common/adt/bitmap.c
===================================================================
--- common/adt/bitmap.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/adt/bitmap.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,338 @@
+/*
+ * Copyright (c) 2006 Jakub Jermar
+ * 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 kernel_generic_adt
+ * @{
+ */
+/**
+ * @file
+ * @brief Implementation of bitmap ADT.
+ *
+ * This file implements bitmap ADT and provides functions for
+ * setting and clearing ranges of bits and for finding ranges
+ * of unset bits.
+ */
+
+#include <adt/bitmap.h>
+#include <align.h>
+#include <assert.h>
+#include <macros.h>
+#include <typedefs.h>
+
+#define ALL_ONES    0xff
+#define ALL_ZEROES  0x00
+
+/** Unchecked version of bitmap_get()
+ *
+ * This version of bitmap_get() does not do any boundary checks.
+ *
+ * @param bitmap  Bitmap to access.
+ * @param element Element to access.
+ *
+ * @return Bit value of the element in the bitmap.
+ *
+ */
+static unsigned int bitmap_get_fast(bitmap_t *bitmap, size_t element)
+{
+	size_t byte = element / BITMAP_ELEMENT;
+	uint8_t mask = 1 << (element & BITMAP_REMAINER);
+
+	return !!((bitmap->bits)[byte] & mask);
+}
+
+/** Get bitmap size
+ *
+ * Return the size (in bytes) required for the bitmap.
+ *
+ * @param elements   Number bits stored in bitmap.
+ *
+ * @return Size (in bytes) required for the bitmap.
+ *
+ */
+size_t bitmap_size(size_t elements)
+{
+	size_t size = elements / BITMAP_ELEMENT;
+
+	if ((elements % BITMAP_ELEMENT) != 0)
+		size++;
+
+	return size;
+}
+
+/** Initialize bitmap.
+ *
+ * No portion of the bitmap is set or cleared by this function.
+ *
+ * @param bitmap     Bitmap structure.
+ * @param elements   Number of bits stored in bitmap.
+ * @param data       Address of the memory used to hold the map.
+ *                   The optional 2nd level bitmap follows the 1st
+ *                   level bitmap.
+ *
+ */
+void bitmap_initialize(bitmap_t *bitmap, size_t elements, void *data)
+{
+	bitmap->elements = elements;
+	bitmap->bits = (uint8_t *) data;
+	bitmap->next_fit = 0;
+}
+
+/** Set range of bits.
+ *
+ * @param bitmap Bitmap structure.
+ * @param start  Starting bit.
+ * @param count  Number of bits to set.
+ *
+ */
+void bitmap_set_range(bitmap_t *bitmap, size_t start, size_t count)
+{
+	assert(start + count <= bitmap->elements);
+
+	if (count == 0)
+		return;
+
+	size_t start_byte = start / BITMAP_ELEMENT;
+	size_t aligned_start = ALIGN_UP(start, BITMAP_ELEMENT);
+
+	/* Leading unaligned bits */
+	size_t lub = min(aligned_start - start, count);
+
+	/* Aligned middle bits */
+	size_t amb = (count > lub) ? (count - lub) : 0;
+
+	/* Trailing aligned bits */
+	size_t tab = amb % BITMAP_ELEMENT;
+
+	if (start + count < aligned_start) {
+		/* Set bits in the middle of byte. */
+		bitmap->bits[start_byte] |=
+		    ((1 << lub) - 1) << (start & BITMAP_REMAINER);
+		return;
+	}
+
+	if (lub) {
+		/* Make sure to set any leading unaligned bits. */
+		bitmap->bits[start_byte] |=
+		    ~((1 << (BITMAP_ELEMENT - lub)) - 1);
+	}
+
+	size_t i;
+
+	for (i = 0; i < amb / BITMAP_ELEMENT; i++) {
+		/* The middle bits can be set byte by byte. */
+		bitmap->bits[aligned_start / BITMAP_ELEMENT + i] =
+		    ALL_ONES;
+	}
+
+	if (tab) {
+		/* Make sure to set any trailing aligned bits. */
+		bitmap->bits[aligned_start / BITMAP_ELEMENT + i] |=
+		    (1 << tab) - 1;
+	}
+}
+
+/** Clear range of bits.
+ *
+ * @param bitmap Bitmap structure.
+ * @param start  Starting bit.
+ * @param count  Number of bits to clear.
+ *
+ */
+void bitmap_clear_range(bitmap_t *bitmap, size_t start, size_t count)
+{
+	assert(start + count <= bitmap->elements);
+
+	if (count == 0)
+		return;
+
+	size_t start_byte = start / BITMAP_ELEMENT;
+	size_t aligned_start = ALIGN_UP(start, BITMAP_ELEMENT);
+
+	/* Leading unaligned bits */
+	size_t lub = min(aligned_start - start, count);
+
+	/* Aligned middle bits */
+	size_t amb = (count > lub) ? (count - lub) : 0;
+
+	/* Trailing aligned bits */
+	size_t tab = amb % BITMAP_ELEMENT;
+
+	if (start + count < aligned_start) {
+		/* Set bits in the middle of byte */
+		bitmap->bits[start_byte] &=
+		    ~(((1 << lub) - 1) << (start & BITMAP_REMAINER));
+		return;
+	}
+
+	if (lub) {
+		/* Make sure to clear any leading unaligned bits. */
+		bitmap->bits[start_byte] &=
+		    (1 << (BITMAP_ELEMENT - lub)) - 1;
+	}
+
+	size_t i;
+
+	for (i = 0; i < amb / BITMAP_ELEMENT; i++) {
+		/* The middle bits can be cleared byte by byte. */
+		bitmap->bits[aligned_start / BITMAP_ELEMENT + i] =
+		    ALL_ZEROES;
+	}
+
+	if (tab) {
+		/* Make sure to clear any trailing aligned bits. */
+		bitmap->bits[aligned_start / BITMAP_ELEMENT + i] &=
+		    ~((1 << tab) - 1);
+	}
+
+	bitmap->next_fit = start_byte;
+}
+
+/** Copy portion of one bitmap into another bitmap.
+ *
+ * @param dst   Destination bitmap.
+ * @param src   Source bitmap.
+ * @param count Number of bits to copy.
+ *
+ */
+void bitmap_copy(bitmap_t *dst, bitmap_t *src, size_t count)
+{
+	assert(count <= dst->elements);
+	assert(count <= src->elements);
+
+	size_t i;
+
+	for (i = 0; i < count / BITMAP_ELEMENT; i++)
+		dst->bits[i] = src->bits[i];
+
+	if (count % BITMAP_ELEMENT) {
+		bitmap_clear_range(dst, i * BITMAP_ELEMENT,
+		    count % BITMAP_ELEMENT);
+		dst->bits[i] |= src->bits[i] &
+		    ((1 << (count % BITMAP_ELEMENT)) - 1);
+	}
+}
+
+static int constraint_satisfy(size_t index, size_t base, size_t constraint)
+{
+	return (((base + index) & constraint) == 0);
+}
+
+/** Find a continuous zero bit range
+ *
+ * Find a continuous zero bit range in the bitmap. The address
+ * computed as the sum of the index of the first zero bit and
+ * the base argument needs to be compliant with the constraint
+ * (those bits that are set in the constraint cannot be set in
+ * the address).
+ *
+ * If the index argument is non-NULL, the continuous zero range
+ * is set and the index of the first bit is stored to index.
+ * Otherwise the bitmap stays untouched.
+ *
+ * @param bitmap     Bitmap structure.
+ * @param count      Number of continuous zero bits to find.
+ * @param base       Address of the first bit in the bitmap.
+ * @param prefered   Prefered address to start searching from.
+ * @param constraint Constraint for the address of the first zero bit.
+ * @param index      Place to store the index of the first zero
+ *                   bit. Can be NULL (in which case the bitmap
+ *                   is not modified).
+ *
+ * @return Non-zero if a continuous range of zero bits satisfying
+ *         the constraint has been found.
+ * @return Zero otherwise.
+ *
+ */
+bool bitmap_allocate_range(bitmap_t *bitmap, size_t count, size_t base,
+    size_t prefered, size_t constraint, size_t *index)
+{
+	if (count == 0)
+		return false;
+
+	size_t size = bitmap_size(bitmap->elements);
+	size_t next_fit = bitmap->next_fit;
+
+	/*
+	 * Adjust the next-fit value according to the address
+	 * the caller prefers to start the search at.
+	 */
+	if ((prefered > base) && (prefered < base + bitmap->elements)) {
+		size_t prefered_fit = (prefered - base) / BITMAP_ELEMENT;
+
+		if (prefered_fit > next_fit)
+			next_fit = prefered_fit;
+	}
+
+	for (size_t pos = 0; pos < size; pos++) {
+		size_t byte = (next_fit + pos) % size;
+
+		/* Skip if the current byte has all bits set */
+		if (bitmap->bits[byte] == ALL_ONES)
+			continue;
+
+		size_t byte_bit = byte * BITMAP_ELEMENT;
+
+		for (size_t bit = 0; bit < BITMAP_ELEMENT; bit++) {
+			size_t i = byte_bit + bit;
+
+			if (i >= bitmap->elements)
+				break;
+
+			if (!constraint_satisfy(i, base, constraint))
+				continue;
+
+			if (!bitmap_get_fast(bitmap, i)) {
+				size_t continuous = 1;
+
+				for (size_t j = 1; j < count; j++) {
+					if ((i + j < bitmap->elements) &&
+					    (!bitmap_get_fast(bitmap, i + j)))
+						continuous++;
+					else
+						break;
+				}
+
+				if (continuous == count) {
+					if (index != NULL) {
+						bitmap_set_range(bitmap, i, count);
+						bitmap->next_fit = i / BITMAP_ELEMENT;
+						*index = i;
+					}
+
+					return true;
+				} else
+					i += continuous;
+			}
+		}
+	}
+
+	return false;
+}
+
+/** @}
+ */
Index: common/adt/checksum.c
===================================================================
--- common/adt/checksum.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/adt/checksum.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2012 Dominik Taborsky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <adt/checksum.h>
+
+/**
+ * 256-value table of precomputed polynomials for CRC32. Note
+ * the values depend on the selected divisor polynomial (currently
+ * 0xedb88320) and whether the CRC computation is reflected or not.
+ * See http://www.repairfaq.org/filipg/LINK/F_crc_v3.html for a perfect
+ * source of info about this.
+ */
+uint32_t poly_table[256] = {
+	0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
+	0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+	0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+	0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+	0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
+	0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+	0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
+	0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+	0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+	0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+	0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
+	0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+	0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
+	0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+	0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+	0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+	0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
+	0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+	0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
+	0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+	0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+	0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+	0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
+	0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+	0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
+	0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+	0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+	0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+	0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
+	0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+	0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
+	0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+	0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+	0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+	0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
+	0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+	0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
+	0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+	0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+	0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+	0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
+	0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+	0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
+	0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+	0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+	0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+	0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
+	0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+	0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
+	0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+	0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+	0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+	0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
+	0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+	0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
+	0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+	0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+	0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+	0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
+	0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+	0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
+	0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+	0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+	0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+};
+
+/** Compute CRC32 value.
+ *
+ * See wiki.osdev.org/CRC32 for reference.
+ *
+ * @param[in] data   Data to process.
+ * @param[in] length Length of the data in bytes.
+ *
+ * @return Computed CRC32 of the data.
+ *
+ */
+uint32_t compute_crc32(uint8_t *data, size_t length)
+{
+	return compute_crc32_seed(data, length, 0);
+}
+
+/** Compute CRC32 value with initial seed.
+ *
+ * Use this when checksumming non-continous data (linked lists, trees,
+ * etc.). On the first data block call compute_crc32() and use the result
+ * as the seed for the following call of compute_crc32_seed() on the next
+ * block. Then use the result of that call as the seed of the following
+ * call, etc.
+ *
+ * @param[in] data   Fata to process.
+ * @param[in] length Length of the data in bytes.
+ * @param[in] seed   The starting value of the CRC.
+ *
+ * @return Computed CRC32 of the data of all the previous blocks.
+ *
+ */
+uint32_t compute_crc32_seed(uint8_t *data, size_t length, uint32_t seed)
+{
+	uint32_t crc;
+
+	for (crc = ~seed; length > 0; length--)
+		crc = poly_table[((uint8_t) crc ^ *(data++))] ^ (crc >> 8);
+
+	return (~crc);
+}
+
+/** @}
+ */
Index: common/adt/circ_buf.c
===================================================================
--- common/adt/circ_buf.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/adt/circ_buf.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2017 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+
+/** @file Circular buffer
+ */
+
+#include <adt/circ_buf.h>
+#include <errno.h>
+#include <mem.h>
+#include <stddef.h>
+
+/** Initialize circular buffer.
+ *
+ * @param cbuf Circular buffer
+ * @param buf Buffer for storing data
+ * @param nmemb Number of entries in @a buf
+ * @param size Size of individual buffer entry
+ */
+void circ_buf_init(circ_buf_t *cbuf, void *buf, size_t nmemb, size_t size)
+{
+	cbuf->buf = buf;
+	cbuf->nmemb = nmemb;
+	cbuf->size = size;
+	cbuf->rp = 0;
+	cbuf->wp = 0;
+	cbuf->nused = 0;
+}
+
+/** Return number of free buffer entries.
+ *
+ * @param cbuf Circular buffer
+ * @return Number of free buffer entries
+ */
+size_t circ_buf_nfree(circ_buf_t *cbuf)
+{
+	return cbuf->nmemb - cbuf->nused;
+}
+
+/** Return number of used buffer entries.
+ *
+ * @param cbuf Circular buffer
+ * @return Number of used buffer entries
+ */
+size_t circ_buf_nused(circ_buf_t *cbuf)
+{
+	return cbuf->nused;
+}
+
+/** Push new entry into circular buffer.
+ *
+ * @param cbuf Circular buffer
+ * @param data Pointer to entry data
+ * @return EOK on success, EAGAIN if circular buffer is full
+ */
+errno_t circ_buf_push(circ_buf_t *cbuf, const void *data)
+{
+	if (circ_buf_nfree(cbuf) == 0)
+		return EAGAIN;
+
+	memcpy(cbuf->buf + cbuf->size * cbuf->wp, data, cbuf->size);
+	cbuf->wp = (cbuf->wp + 1) % cbuf->nmemb;
+	cbuf->nused++;
+	return EOK;
+}
+
+/** Pop entry from circular buffer.
+ *
+ * @param cbuf Circular buffer
+ * @param datab Pointer to data buffer for storing entry
+ * @return EOK on success, EAGAIN if circular buffer is full
+ */
+errno_t circ_buf_pop(circ_buf_t *cbuf, void *datab)
+{
+	if (cbuf->nused == 0)
+		return EAGAIN;
+
+	memcpy(datab, cbuf->buf + cbuf->size * cbuf->rp, cbuf->size);
+	cbuf->rp = (cbuf->rp + 1) % cbuf->nmemb;
+	cbuf->nused--;
+	return EOK;
+}
+
+/** @}
+ */
Index: common/adt/hash_table.c
===================================================================
--- common/adt/hash_table.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/adt/hash_table.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,473 @@
+/*
+ * Copyright (c) 2008 Jakub Jermar
+ * Copyright (c) 2012 Adam Hraska
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+/*
+ * This is an implementation of a generic resizable chained hash table.
+ *
+ * The table grows to 2*n+1 buckets each time, starting at n == 89,
+ * per Thomas Wang's recommendation:
+ * http://www.concentric.net/~Ttwang/tech/hashsize.htm
+ *
+ * This policy produces prime table sizes for the first five resizes
+ * and generally produces table sizes which are either prime or
+ * have fairly large (prime/odd) divisors. Having a prime table size
+ * mitigates the use of suboptimal hash functions and distributes
+ * items over the whole table.
+ */
+
+#include <adt/hash_table.h>
+#include <adt/list.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <str.h>
+
+/* Optimal initial bucket count. See comment above. */
+#define HT_MIN_BUCKETS  89
+/* The table is resized when the average load per bucket exceeds this number. */
+#define HT_MAX_LOAD     2
+
+static size_t round_up_size(size_t);
+static bool alloc_table(size_t, list_t **);
+static void clear_items(hash_table_t *);
+static void resize(hash_table_t *, size_t);
+static void grow_if_needed(hash_table_t *);
+static void shrink_if_needed(hash_table_t *);
+
+/* Dummy do nothing callback to invoke in place of remove_callback == NULL. */
+static void nop_remove_callback(ht_link_t *item)
+{
+	/* no-op */
+}
+
+/** Create chained hash table.
+ *
+ * @param h        Hash table structure. Will be initialized by this call.
+ * @param init_size Initial desired number of hash table buckets. Pass zero
+ *                 if you want the default initial size.
+ * @param max_load The table is resized when the average load per bucket
+ *                 exceeds this number. Pass zero if you want the default.
+ * @param op       Hash table operations structure. remove_callback()
+ *                 is optional and can be NULL if no action is to be taken
+ *                 upon removal. equal() is optional if and only if
+ *                 hash_table_insert_unique() will never be invoked.
+ *                 All other operations are mandatory.
+ *
+ * @return True on success
+ *
+ */
+bool hash_table_create(hash_table_t *h, size_t init_size, size_t max_load,
+    const hash_table_ops_t *op)
+{
+	assert(h);
+	assert(op && op->hash && op->key_hash && op->key_equal);
+
+	/* Check for compulsory ops. */
+	if (!op || !op->hash || !op->key_hash || !op->key_equal)
+		return false;
+
+	h->bucket_cnt = round_up_size(init_size);
+
+	if (!alloc_table(h->bucket_cnt, &h->bucket))
+		return false;
+
+	h->max_load = (max_load == 0) ? HT_MAX_LOAD : max_load;
+	h->item_cnt = 0;
+	h->op = op;
+	h->full_item_cnt = h->max_load * h->bucket_cnt;
+	h->apply_ongoing = false;
+
+	return true;
+}
+
+/** Destroy a hash table instance.
+ *
+ * @param h Hash table to be destroyed.
+ *
+ */
+void hash_table_destroy(hash_table_t *h)
+{
+	assert(h && h->bucket);
+	assert(!h->apply_ongoing);
+
+	clear_items(h);
+
+	free(h->bucket);
+
+	h->bucket = NULL;
+	h->bucket_cnt = 0;
+}
+
+/** Returns true if there are no items in the table. */
+bool hash_table_empty(hash_table_t *h)
+{
+	assert(h && h->bucket);
+	return h->item_cnt == 0;
+}
+
+/** Returns the number of items in the table. */
+size_t hash_table_size(hash_table_t *h)
+{
+	assert(h && h->bucket);
+	return h->item_cnt;
+}
+
+/** Remove all elements from the hash table
+ *
+ * @param h Hash table to be cleared
+ */
+void hash_table_clear(hash_table_t *h)
+{
+	assert(h && h->bucket);
+	assert(!h->apply_ongoing);
+
+	clear_items(h);
+
+	/* Shrink the table to its minimum size if possible. */
+	if (HT_MIN_BUCKETS < h->bucket_cnt) {
+		resize(h, HT_MIN_BUCKETS);
+	}
+}
+
+/** Unlinks and removes all items but does not resize. */
+static void clear_items(hash_table_t *h)
+{
+	if (h->item_cnt == 0)
+		return;
+
+	void (*remove_cb)(ht_link_t *) = h->op->remove_callback ? h->op->remove_callback : nop_remove_callback;
+
+	for (size_t idx = 0; idx < h->bucket_cnt; ++idx) {
+		list_foreach_safe(h->bucket[idx], cur, next) {
+			assert(cur);
+			ht_link_t *cur_link = member_to_inst(cur, ht_link_t, link);
+
+			list_remove(cur);
+			remove_cb(cur_link);
+		}
+	}
+
+	h->item_cnt = 0;
+}
+
+/** Insert item into a hash table.
+ *
+ * @param h    Hash table.
+ * @param item Item to be inserted into the hash table.
+ */
+void hash_table_insert(hash_table_t *h, ht_link_t *item)
+{
+	assert(item);
+	assert(h && h->bucket);
+	assert(!h->apply_ongoing);
+
+	size_t idx = h->op->hash(item) % h->bucket_cnt;
+
+	list_append(&item->link, &h->bucket[idx]);
+	++h->item_cnt;
+	grow_if_needed(h);
+}
+
+/** Insert item into a hash table if not already present.
+ *
+ * @param h    Hash table.
+ * @param item Item to be inserted into the hash table.
+ *
+ * @return False if such an item had already been inserted.
+ * @return True if the inserted item was the only item with such a lookup key.
+ */
+bool hash_table_insert_unique(hash_table_t *h, ht_link_t *item)
+{
+	assert(item);
+	assert(h && h->bucket && h->bucket_cnt);
+	assert(h->op && h->op->hash && h->op->equal);
+	assert(!h->apply_ongoing);
+
+	size_t idx = h->op->hash(item) % h->bucket_cnt;
+
+	/* Check for duplicates. */
+	list_foreach(h->bucket[idx], link, ht_link_t, cur_link) {
+		/*
+		 * We could filter out items using their hashes first, but
+		 * calling equal() might very well be just as fast.
+		 */
+		if (h->op->equal(cur_link, item))
+			return false;
+	}
+
+	list_append(&item->link, &h->bucket[idx]);
+	++h->item_cnt;
+	grow_if_needed(h);
+
+	return true;
+}
+
+/** Search hash table for an item matching keys.
+ *
+ * @param h   Hash table.
+ * @param key Array of all keys needed to compute hash index.
+ *
+ * @return Matching item on success, NULL if there is no such item.
+ *
+ */
+ht_link_t *hash_table_find(const hash_table_t *h, const void *key)
+{
+	assert(h && h->bucket);
+
+	size_t idx = h->op->key_hash(key) % h->bucket_cnt;
+
+	list_foreach(h->bucket[idx], link, ht_link_t, cur_link) {
+		/*
+		 * Is this is the item we are looking for? We could have first
+		 * checked if the hashes match but op->key_equal() may very well be
+		 * just as fast as op->hash().
+		 */
+		if (h->op->key_equal(key, cur_link)) {
+			return cur_link;
+		}
+	}
+
+	return NULL;
+}
+
+/** Find the next item equal to item. */
+ht_link_t *
+hash_table_find_next(const hash_table_t *h, ht_link_t *first, ht_link_t *item)
+{
+	assert(item);
+	assert(h && h->bucket);
+
+	size_t idx = h->op->hash(item) % h->bucket_cnt;
+
+	/* Traverse the circular list until we reach the starting item again. */
+	for (link_t *cur = item->link.next; cur != &first->link;
+	    cur = cur->next) {
+		assert(cur);
+
+		if (cur == &h->bucket[idx].head)
+			continue;
+
+		ht_link_t *cur_link = member_to_inst(cur, ht_link_t, link);
+		/*
+		 * Is this is the item we are looking for? We could have first
+		 * checked if the hashes match but op->equal() may very well be
+		 * just as fast as op->hash().
+		 */
+		if (h->op->equal(cur_link, item)) {
+			return cur_link;
+		}
+	}
+
+	return NULL;
+}
+
+/** Remove all matching items from hash table.
+ *
+ * For each removed item, h->remove_callback() is called.
+ *
+ * @param h    Hash table.
+ * @param key  Array of keys that will be compared against items of
+ *             the hash table.
+ *
+ * @return Returns the number of removed items.
+ */
+size_t hash_table_remove(hash_table_t *h, const void *key)
+{
+	assert(h && h->bucket);
+	assert(!h->apply_ongoing);
+
+	size_t idx = h->op->key_hash(key) % h->bucket_cnt;
+
+	size_t removed = 0;
+
+	list_foreach_safe(h->bucket[idx], cur, next) {
+		ht_link_t *cur_link = member_to_inst(cur, ht_link_t, link);
+
+		if (h->op->key_equal(key, cur_link)) {
+			++removed;
+			list_remove(cur);
+
+			if (h->op->remove_callback)
+				h->op->remove_callback(cur_link);
+		}
+	}
+
+	h->item_cnt -= removed;
+	shrink_if_needed(h);
+
+	return removed;
+}
+
+/** Removes an item already present in the table. The item must be in the table. */
+void hash_table_remove_item(hash_table_t *h, ht_link_t *item)
+{
+	assert(item);
+	assert(h && h->bucket);
+	assert(link_in_use(&item->link));
+
+	list_remove(&item->link);
+	--h->item_cnt;
+
+	if (h->op->remove_callback)
+		h->op->remove_callback(item);
+	shrink_if_needed(h);
+}
+
+/** Apply function to all items in hash table.
+ *
+ * @param h   Hash table.
+ * @param f   Function to be applied. Return false if no more items
+ *            should be visited. The functor may only delete the supplied
+ *            item. It must not delete the successor of the item passed
+ *            in the first argument.
+ * @param arg Argument to be passed to the function.
+ */
+void hash_table_apply(hash_table_t *h, bool (*f)(ht_link_t *, void *), void *arg)
+{
+	assert(f);
+	assert(h && h->bucket);
+
+	if (h->item_cnt == 0)
+		return;
+
+	h->apply_ongoing = true;
+
+	for (size_t idx = 0; idx < h->bucket_cnt; ++idx) {
+		list_foreach_safe(h->bucket[idx], cur, next) {
+			ht_link_t *cur_link = member_to_inst(cur, ht_link_t, link);
+			/*
+			 * The next pointer had already been saved. f() may safely
+			 * delete cur (but not next!).
+			 */
+			if (!f(cur_link, arg))
+				goto out;
+		}
+	}
+out:
+	h->apply_ongoing = false;
+
+	shrink_if_needed(h);
+	grow_if_needed(h);
+}
+
+/** Rounds up size to the nearest suitable table size. */
+static size_t round_up_size(size_t size)
+{
+	size_t rounded_size = HT_MIN_BUCKETS;
+
+	while (rounded_size < size) {
+		rounded_size = 2 * rounded_size + 1;
+	}
+
+	return rounded_size;
+}
+
+/** Allocates and initializes the desired number of buckets. True if successful. */
+static bool alloc_table(size_t bucket_cnt, list_t **pbuckets)
+{
+	assert(pbuckets && HT_MIN_BUCKETS <= bucket_cnt);
+
+	list_t *buckets = malloc(bucket_cnt * sizeof(list_t));
+	if (!buckets)
+		return false;
+
+	for (size_t i = 0; i < bucket_cnt; i++)
+		list_initialize(&buckets[i]);
+
+	*pbuckets = buckets;
+	return true;
+}
+
+/** Shrinks the table if the table is only sparely populated. */
+static inline void shrink_if_needed(hash_table_t *h)
+{
+	if (h->item_cnt <= h->full_item_cnt / 4 && HT_MIN_BUCKETS < h->bucket_cnt) {
+		/*
+		 * Keep the bucket_cnt odd (possibly also prime).
+		 * Shrink from 2n + 1 to n. Integer division discards the +1.
+		 */
+		size_t new_bucket_cnt = h->bucket_cnt / 2;
+		resize(h, new_bucket_cnt);
+	}
+}
+
+/** Grows the table if table load exceeds the maximum allowed. */
+static inline void grow_if_needed(hash_table_t *h)
+{
+	/* Grow the table if the average bucket load exceeds the maximum. */
+	if (h->full_item_cnt < h->item_cnt) {
+		/* Keep the bucket_cnt odd (possibly also prime). */
+		size_t new_bucket_cnt = 2 * h->bucket_cnt + 1;
+		resize(h, new_bucket_cnt);
+	}
+}
+
+/** Allocates and rehashes items to a new table. Frees the old table. */
+static void resize(hash_table_t *h, size_t new_bucket_cnt)
+{
+	assert(h && h->bucket);
+	assert(HT_MIN_BUCKETS <= new_bucket_cnt);
+
+	/* We are traversing the table and resizing would mess up the buckets. */
+	if (h->apply_ongoing)
+		return;
+
+	list_t *new_buckets;
+
+	/* Leave the table as is if we cannot resize. */
+	if (!alloc_table(new_bucket_cnt, &new_buckets))
+		return;
+
+	if (0 < h->item_cnt) {
+		/* Rehash all the items to the new table. */
+		for (size_t old_idx = 0; old_idx < h->bucket_cnt; ++old_idx) {
+			list_foreach_safe(h->bucket[old_idx], cur, next) {
+				ht_link_t *cur_link = member_to_inst(cur, ht_link_t, link);
+
+				size_t new_idx = h->op->hash(cur_link) % new_bucket_cnt;
+				list_remove(cur);
+				list_append(cur, &new_buckets[new_idx]);
+			}
+		}
+	}
+
+	free(h->bucket);
+	h->bucket = new_buckets;
+	h->bucket_cnt = new_bucket_cnt;
+	h->full_item_cnt = h->max_load * h->bucket_cnt;
+}
+
+/** @}
+ */
Index: common/adt/list.c
===================================================================
--- common/adt/list.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/adt/list.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2004 Jakub Jermar
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+
+/**
+ * @file
+ * @brief	Functions completing doubly linked circular list implementation.
+ *
+ * This file contains some of the functions implementing doubly linked circular lists.
+ * However, this ADT is mostly implemented in @ref list.h.
+ */
+
+#include <adt/list.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+/** Check for membership
+ *
+ * Check whether link is contained in a list.
+ * Membership is defined as pointer equivalence.
+ *
+ * @param link	Item to look for.
+ * @param list	List to look in.
+ *
+ * @return true if link is contained in list, false otherwise.
+ *
+ */
+bool list_member(const link_t *link, const list_t *list)
+{
+	bool found = false;
+	link_t *hlp = list->head.next;
+
+	while (hlp != &list->head) {
+		if (hlp == link) {
+			found = true;
+			break;
+		}
+		hlp = hlp->next;
+	}
+
+	return found;
+}
+
+/** Moves items of one list into another after the specified item.
+ *
+ * Inserts all items of @a list after item at @a pos in another list.
+ * Both lists may be empty.
+ *
+ * @param list Source list to move after pos. Empty afterwards.
+ * @param pos Source items will be placed after this item.
+ */
+void list_splice(list_t *list, link_t *pos)
+{
+	if (list_empty(list))
+		return;
+
+	/* Attach list to destination. */
+	list->head.next->prev = pos;
+	list->head.prev->next = pos->next;
+
+	/* Link destination list to the added list. */
+	pos->next->prev = list->head.prev;
+	pos->next = list->head.next;
+
+	list_initialize(list);
+}
+
+/** Count list items
+ *
+ * Return the number of items in the list.
+ *
+ * @param list		List to count.
+ * @return		Number of items in the list.
+ */
+size_t list_count(const list_t *list)
+{
+	size_t count = 0;
+
+	link_t *link = list_first(list);
+	while (link != NULL) {
+		count++;
+		link = list_next(link, list);
+	}
+
+	return count;
+}
+
+/** @}
+ */
Index: common/adt/odict.c
===================================================================
--- common/adt/odict.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/adt/odict.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,1144 @@
+/*
+ * Copyright (c) 2016 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 kernel_generic_adt
+ * @{
+ */
+
+/** @file Ordered dictionary.
+ *
+ * Implementation based on red-black trees.
+ * Note that non-data ('leaf') nodes are implemented as NULLs, not
+ * as actual nodes.
+ */
+
+#include <adt/list.h>
+#include <adt/odict.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+
+static void odict_pgu(odlink_t *, odlink_t **, odict_child_sel_t *,
+    odlink_t **, odict_child_sel_t *, odlink_t **);
+
+static void odict_rotate_left(odlink_t *);
+static void odict_rotate_right(odlink_t *);
+static void odict_swap_node(odlink_t *, odlink_t *);
+static void odict_replace_subtree(odlink_t *, odlink_t *);
+static void odict_unlink(odlink_t *);
+static void odict_link_child_a(odlink_t *, odlink_t *);
+static void odict_link_child_b(odlink_t *, odlink_t *);
+static void odict_sibling(odlink_t *, odlink_t *, odict_child_sel_t *,
+    odlink_t **);
+static odlink_t *odict_search_start_node(odict_t *, void *, odlink_t *);
+
+/** Print subtree.
+ *
+ * Print subtree rooted at @a cur
+ *
+ * @param cur Root of tree to print
+ */
+static void odict_print_tree(odlink_t *cur)
+{
+	if (cur == NULL) {
+		printf("0");
+		return;
+	}
+
+	printf("[%p/%c", cur, cur->color == odc_red ? 'r' : 'b');
+	if (cur->a != NULL || cur->b != NULL) {
+		printf(" ");
+		odict_print_tree(cur->a);
+		printf(" ");
+		odict_print_tree(cur->b);
+	}
+	printf("]");
+}
+
+/** Validate ordered dictionary subtree.
+ *
+ * Verify that red-black tree properties are satisfied.
+ *
+ * @param cur Root of tree to verify
+ * @param rbd Place to store black depth of the subtree
+ *
+ * @return EOK on success, EINVAL on failure
+ */
+static errno_t odict_validate_tree(odlink_t *cur, int *rbd)
+{
+	errno_t rc;
+	int bd_a, bd_b;
+	int cur_d;
+
+	if (cur->up == NULL) {
+		/* Verify root pointer */
+		if (cur->odict->root != cur) {
+			printf("cur->up == NULL and yet cur != root\n");
+			return EINVAL;
+		}
+
+		/* Verify root color */
+		if (cur->color != odc_black) {
+			printf("Root is not black\n");
+			return EINVAL;
+		}
+	}
+
+	if (cur->a != NULL) {
+		/* Verify symmetry of a - up links */
+		if (cur->a->up != cur) {
+			printf("cur->a->up != cur\n");
+			return EINVAL;
+		}
+
+		/* Verify that if a node is red, its left child is red */
+		if (cur->a->color == odc_red && cur->color == odc_red) {
+			printf("cur->a is red, cur is red\n");
+			return EINVAL;
+		}
+
+		/* Recurse to left child */
+		rc = odict_validate_tree(cur->a, &bd_a);
+		if (rc != EOK)
+			return rc;
+	} else {
+		bd_a = -1;
+	}
+
+	if (cur->b != NULL) {
+		/* Verify symmetry of b - up links */
+		if (cur->b->up != cur) {
+			printf("cur->b->up != cur\n");
+			return EINVAL;
+		}
+
+		/* Verify that if a node is red, its right child is red */
+		if (cur->b->color == odc_red && cur->color == odc_red) {
+			printf("cur->b is red, cur is red\n");
+			return EINVAL;
+		}
+
+		/* Recurse to right child */
+		rc = odict_validate_tree(cur->b, &bd_b);
+		if (rc != EOK)
+			return rc;
+	} else {
+		bd_b = -1;
+	}
+
+	/* Verify that black depths of both children are equal */
+	if (bd_a >= 0 && bd_b >= 0) {
+		if (bd_a != bd_b) {
+			printf("Black depth %d != %d\n", bd_a, bd_b);
+			return EINVAL;
+		}
+	}
+
+	cur_d = cur->color == odc_black ? 1 : 0;
+	if (bd_a >= 0)
+		*rbd = bd_a + cur_d;
+	else if (bd_b >= 0)
+		*rbd = bd_b + cur_d;
+	else
+		*rbd = cur_d;
+
+	return EOK;
+}
+
+/** Validate ordered dictionary properties.
+ *
+ * @param odict Ordered dictionary
+ */
+errno_t odict_validate(odict_t *odict)
+{
+	int bd;
+	errno_t rc;
+
+	if (odict->root == NULL)
+		return EOK;
+
+	rc = odict_validate_tree(odict->root, &bd);
+	if (rc != EOK)
+		odict_print_tree(odict->root);
+
+	return rc;
+}
+
+/** Initialize ordered dictionary.
+ *
+ * @param odict Ordered dictionary
+ * @param getkey Function to get key
+ * @param cmp Function to compare entries
+ */
+void odict_initialize(odict_t *odict, odgetkey_t getkey, odcmp_t cmp)
+{
+	odict->root = NULL;
+	list_initialize(&odict->entries);
+	odict->getkey = getkey;
+	odict->cmp = cmp;
+}
+
+/** Finalize ordered dictionary.
+ *
+ * @param odict Ordered dictionary (must be empty)
+ */
+void odict_finalize(odict_t *odict)
+{
+	assert(odict->root == NULL);
+}
+
+/** Initialize ordered dictionary link.
+ *
+ * @param odlink Ordered dictionary link
+ */
+void odlink_initialize(odlink_t *odlink)
+{
+	odlink->odict = NULL;
+	odlink->up = NULL;
+	odlink->a = NULL;
+	odlink->b = NULL;
+	link_initialize(&odlink->lentries);
+}
+
+/** Insert entry in ordered dictionary.
+ *
+ * Insert entry in ordered dictionary, placing it after other entries
+ * with the same key.
+ *
+ * @param odlink New entry
+ * @param odict Ordered dictionary
+ * @param hint An entry that might be near the new entry or @c NULL
+ */
+void odict_insert(odlink_t *odlink, odict_t *odict, odlink_t *hint)
+{
+	int d;
+	odlink_t *cur;
+	odlink_t *p;
+	odlink_t *g;
+	odlink_t *u;
+	odict_child_sel_t pcs, gcs;
+
+	assert(!odlink_used(odlink));
+
+	if (odict->root == NULL) {
+		/* odlink is the root node */
+		odict->root = odlink;
+		odlink->odict = odict;
+		odlink->color = odc_black;
+		list_append(&odlink->lentries, &odict->entries);
+		return;
+	}
+
+	cur = odict_search_start_node(odict, odict->getkey(odlink), hint);
+	while (true) {
+		d = odict->cmp(odict->getkey(odlink), odict->getkey(cur));
+		if (d < 0) {
+			if (cur->a == NULL) {
+				odict_link_child_a(odlink, cur);
+				break;
+			}
+			cur = cur->a;
+		} else {
+			if (cur->b == NULL) {
+				odict_link_child_b(odlink, cur);
+				break;
+			}
+			cur = cur->b;
+		}
+	}
+
+	odlink->color = odc_red;
+
+	while (true) {
+		/* Fix up odlink and its parent potentially being red */
+		if (odlink->up == NULL) {
+			odlink->color = odc_black;
+			break;
+		}
+
+		if (odlink->up->color == odc_black)
+			break;
+
+		/* Get parent, grandparent, uncle */
+		odict_pgu(odlink, &p, &pcs, &g, &gcs, &u);
+
+		if (g == NULL) {
+			p->color = odc_black;
+			break;
+		}
+
+		if (p->color == odc_red && u != NULL && u->color == odc_red) {
+			/* Parent and uncle are both red */
+			p->color = odc_black;
+			u->color = odc_black;
+			g->color = odc_red;
+			odlink = g;
+			continue;
+		}
+
+		/* Parent is red but uncle is black, odlink-P-G is trans */
+		if (pcs != gcs) {
+			if (gcs == odcs_a) {
+				/* odlink is right child of P */
+				/* P is left child of G */
+				odict_rotate_left(p);
+			} else {
+				/* odlink is left child of P */
+				/* P is right child of G */
+				odict_rotate_right(p);
+			}
+
+			odlink = p;
+			odict_pgu(odlink, &p, &pcs, &g, &gcs, &u);
+		}
+
+		/* odlink-P-G is now cis */
+		assert(pcs == gcs);
+		if (pcs == odcs_a) {
+			/* odlink is left child of P */
+			/* P is left child of G */
+			odict_rotate_right(g);
+		} else {
+			/* odlink is right child of P */
+			/* P is right child of G */
+			odict_rotate_left(g);
+		}
+
+		p->color = odc_black;
+		g->color = odc_red;
+		break;
+	}
+}
+
+/** Remove entry from ordered dictionary.
+ *
+ * @param odlink Ordered dictionary link
+ */
+void odict_remove(odlink_t *odlink)
+{
+	odlink_t *n;
+	odlink_t *c;
+	odlink_t *p;
+	odlink_t *s;
+	odlink_t *sc, *st;
+	odict_child_sel_t pcs;
+
+	if (odlink->a != NULL && odlink->b != NULL) {
+		n = odict_next(odlink, odlink->odict);
+		assert(n != NULL);
+
+		odict_swap_node(odlink, n);
+	}
+
+	/* odlink has at most one child */
+	if (odlink->a != NULL) {
+		assert(odlink->b == NULL);
+		c = odlink->a;
+	} else {
+		c = odlink->b;
+	}
+
+	if (odlink->color == odc_red) {
+		/* We can remove it harmlessly */
+		assert(c == NULL);
+		odict_unlink(odlink);
+		return;
+	}
+
+	/* odlink->color == odc_black */
+	if (c != NULL && c->color == odc_red) {
+		/* Child is red: swap colors of S and C */
+		c->color = odc_black;
+		odict_replace_subtree(c, odlink);
+		odlink->up = odlink->a = odlink->b = NULL;
+		odlink->odict = NULL;
+		list_remove(&odlink->lentries);
+		return;
+	}
+
+	/* There cannot be one black child */
+	assert(c == NULL);
+
+	n = NULL;
+	p = odlink->up;
+	odict_unlink(odlink);
+	/* We removed one black node, creating imbalance */
+again:
+	/* Case 1: N is the new root */
+	if (p == NULL)
+		return;
+
+	odict_sibling(n, p, &pcs, &s);
+
+	/* Paths through N have one less black node than paths through S */
+
+	/* Case 2: S is red */
+	if (s->color == odc_red) {
+		assert(p->color == odc_black);
+		p->color = odc_red;
+		s->color = odc_black;
+		if (n == p->a)
+			odict_rotate_left(p);
+		else
+			odict_rotate_right(p);
+		odict_sibling(n, p, &pcs, &s);
+		/* Now S is black */
+		assert(s->color == odc_black);
+	}
+
+	/* Case 3: P, S and S's children are black */
+	if (p->color == odc_black &&
+	    s->color == odc_black &&
+	    (s->a == NULL || s->a->color == odc_black) &&
+	    (s->b == NULL || s->b->color == odc_black)) {
+		/*
+		 * Changing S to red means all paths through S or N have one
+		 * less black node than they should. So redo the same for P.
+		 */
+		s->color = odc_red;
+		n = p;
+		p = n->up;
+		goto again;
+	}
+
+	/* Case 4: P is red, S and S's children are black */
+	if (p->color == odc_red &&
+	    s->color == odc_black &&
+	    (s->a == NULL || s->a->color == odc_black) &&
+	    (s->b == NULL || s->b->color == odc_black)) {
+		/* Swap colors of S and P */
+		s->color = odc_red;
+		p->color = odc_black;
+		return;
+	}
+
+	/* N is the left child */
+	if (pcs == odcs_a) {
+		st = s->a;
+		sc = s->b;
+	} else {
+		st = s->b;
+		sc = s->a;
+	}
+
+	/* Case 5: S is black and S's trans child is red, S's cis child is black */
+	if (s->color == odc_black &&
+	    (st != NULL && st->color == odc_red) &&
+	    (sc == NULL || sc->color == odc_black)) {
+		/* N is the left child */
+		if (pcs == odcs_a)
+			odict_rotate_right(s);
+		else
+			odict_rotate_left(s);
+		s->color = odc_red;
+		s->up->color = odc_black;
+		/* Now N has a black sibling whose cis child is red */
+		odict_sibling(n, p, &pcs, &s);
+		/* N is the left child */
+		if (pcs == odcs_a) {
+			st = s->a;
+			sc = s->b;
+		} else {
+			st = s->b;
+			sc = s->a;
+		}
+	}
+
+	/* Case 6: S is black, S's cis child is red */
+	assert(s->color == odc_black);
+	assert(sc != NULL);
+	assert(sc->color == odc_red);
+
+	if (pcs == odcs_a)
+		odict_rotate_left(p);
+	else
+		odict_rotate_right(p);
+
+	s->color = p->color;
+	p->color = odc_black;
+	sc->color = odc_black;
+}
+
+/** Update dictionary after entry key has been changed.
+ *
+ * After the caller modifies the key of an entry, they need to call
+ * this function so that the dictionary can update itself accordingly.
+ *
+ * @param odlink Ordered dictionary entry
+ * @param odict Ordered dictionary
+ */
+void odict_key_update(odlink_t *odlink, odict_t *odict)
+{
+	odlink_t *n;
+
+	n = odict_next(odlink, odict);
+	odict_remove(odlink);
+	odict_insert(odlink, odict, n);
+}
+
+/** Return true if entry is in a dictionary.
+ *
+ * @param odlink Ordered dictionary entry
+ * @return @c true if entry is in a dictionary, @c false otherwise
+ */
+bool odlink_used(odlink_t *odlink)
+{
+	return odlink->odict != NULL;
+}
+
+/** Return true if ordered dictionary is empty.
+ *
+ * @param odict Ordered dictionary
+ * @return @c true if @a odict is emptry, @c false otherwise
+ */
+bool odict_empty(odict_t *odict)
+{
+	return odict->root == NULL;
+}
+
+/** Return the number of entries in @a odict.
+ *
+ * @param odict Ordered dictionary
+ */
+unsigned long odict_count(odict_t *odict)
+{
+	unsigned long cnt;
+	odlink_t *cur;
+
+	cnt = 0;
+	cur = odict_first(odict);
+	while (cur != NULL) {
+		++cnt;
+		cur = odict_next(cur, odict);
+	}
+
+	return cnt;
+}
+
+/** Return first entry in a list or @c NULL if list is empty.
+ *
+ * @param odict Ordered dictionary
+ * @return First entry
+ */
+odlink_t *odict_first(odict_t *odict)
+{
+	link_t *link;
+
+	link = list_first(&odict->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, odlink_t, lentries);
+}
+
+/** Return last entry in a list or @c NULL if list is empty
+ *
+ * @param odict Ordered dictionary
+ * @return Last entry
+ */
+odlink_t *odict_last(odict_t *odict)
+{
+	link_t *link;
+
+	link = list_last(&odict->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, odlink_t, lentries);
+}
+
+/** Return previous entry in list or @c NULL if @a link is the first one.
+ *
+ * @param odlink Entry
+ * @param odict Ordered dictionary
+ * @return Previous entry
+ */
+odlink_t *odict_prev(odlink_t *odlink, odict_t *odict)
+{
+	link_t *link;
+
+	link = list_prev(&odlink->lentries, &odlink->odict->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, odlink_t, lentries);
+}
+
+/** Return next entry in dictionary or @c NULL if @a odlink is the last one
+ *
+ * @param odlink Entry
+ * @param odict Ordered dictionary
+ * @return Next entry
+ */
+odlink_t *odict_next(odlink_t *odlink, odict_t *odict)
+{
+	link_t *link;
+
+	link = list_next(&odlink->lentries, &odlink->odict->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, odlink_t, lentries);
+}
+
+/** Find first entry whose key is equal to @a key/
+ *
+ * @param odict Ordered dictionary
+ * @param key Key
+ * @param hint Nearby entry
+ * @return Pointer to entry on success, @c NULL on failure
+ */
+odlink_t *odict_find_eq(odict_t *odict, void *key, odlink_t *hint)
+{
+	odlink_t *geq;
+
+	geq = odict_find_geq(odict, key, hint);
+	if (geq == NULL)
+		return NULL;
+
+	if (odict->cmp(odict->getkey(geq), key) == 0)
+		return geq;
+	else
+		return NULL;
+}
+
+/** Find last entry whose key is equal to @a key/
+ *
+ * @param odict Ordered dictionary
+ * @param key Key
+ * @param hint Nearby entry
+ * @return Pointer to entry on success, @c NULL on failure
+ */
+odlink_t *odict_find_eq_last(odict_t *odict, void *key, odlink_t *hint)
+{
+	odlink_t *leq;
+
+	leq = odict_find_leq(odict, key, hint);
+	if (leq == NULL)
+		return NULL;
+
+	if (odict->cmp(odict->getkey(leq), key) == 0)
+		return leq;
+	else
+		return NULL;
+}
+
+/** Find first entry whose key is greater than or equal to @a key
+ *
+ * @param odict Ordered dictionary
+ * @param key Key
+ * @param hint Nearby entry
+ * @return Pointer to entry on success, @c NULL on failure
+ */
+odlink_t *odict_find_geq(odict_t *odict, void *key, odlink_t *hint)
+{
+	odlink_t *cur;
+	odlink_t *next;
+	int d;
+
+	cur = odict_search_start_node(odict, key, hint);
+	if (cur == NULL)
+		return NULL;
+
+	while (true) {
+		d = odict->cmp(odict->getkey(cur), key);
+		if (d >= 0)
+			next = cur->a;
+		else
+			next = cur->b;
+
+		if (next == NULL)
+			break;
+
+		cur = next;
+	}
+
+	if (d >= 0) {
+		return cur;
+	} else {
+		return odict_next(cur, odict);
+	}
+}
+
+/** Find last entry whose key is greater than @a key.
+ *
+ * @param odict Ordered dictionary
+ * @param key Key
+ * @param hint Nearby entry
+ * @return Pointer to entry on success, @c NULL on failure
+ */
+odlink_t *odict_find_gt(odict_t *odict, void *key, odlink_t *hint)
+{
+	odlink_t *leq;
+
+	leq = odict_find_leq(odict, key, hint);
+	if (leq != NULL)
+		return odict_next(leq, odict);
+	else
+		return odict_first(odict);
+}
+
+/** Find last entry whose key is less than or equal to @a key
+ *
+ * @param odict Ordered dictionary
+ * @param key Key
+ * @param hint Nearby entry
+ * @return Pointer to entry on success, @c NULL on failure
+ */
+odlink_t *odict_find_leq(odict_t *odict, void *key, odlink_t *hint)
+{
+	odlink_t *cur;
+	odlink_t *next;
+	int d;
+
+	cur = odict_search_start_node(odict, key, hint);
+	if (cur == NULL)
+		return NULL;
+
+	while (true) {
+		d = odict->cmp(key, odict->getkey(cur));
+		if (d >= 0)
+			next = cur->b;
+		else
+			next = cur->a;
+
+		if (next == NULL)
+			break;
+
+		cur = next;
+	}
+
+	if (d >= 0) {
+		return cur;
+	} else {
+		return odict_prev(cur, odict);
+	}
+}
+
+/** Find last entry whose key is less than @a key.
+ *
+ * @param odict Ordered dictionary
+ * @param key Key
+ * @param hint Nearby entry
+ * @return Pointer to entry on success, @c NULL on failure
+ */
+odlink_t *odict_find_lt(odict_t *odict, void *key, odlink_t *hint)
+{
+	odlink_t *geq;
+
+	geq = odict_find_geq(odict, key, hint);
+	if (geq != NULL)
+		return odict_prev(geq, odict);
+	else
+		return odict_last(odict);
+}
+
+/** Return parent, grandparent and uncle.
+ *
+ * @param n Node
+ * @param p Place to store pointer to parent of @a n
+ * @param pcs Place to store position of @a n w.r.t. @a p
+ * @param g Place to store pointer to grandparent of @a n
+ * @param gcs Place to store position of @a p w.r.t. @a g
+ * @param u Place to store pointer to uncle of @a n
+ */
+static void odict_pgu(odlink_t *n, odlink_t **p, odict_child_sel_t *pcs,
+    odlink_t **g, odict_child_sel_t *gcs, odlink_t **u)
+{
+	*p = n->up;
+
+	if (*p == NULL) {
+		/* No parent */
+		*g = NULL;
+		*u = NULL;
+		return;
+	}
+
+	if ((*p)->a == n) {
+		*pcs = odcs_a;
+	} else {
+		assert((*p)->b == n);
+		*pcs = odcs_b;
+	}
+
+	*g = (*p)->up;
+	if (*g == NULL) {
+		/* No grandparent */
+		*u = NULL;
+		return;
+	}
+
+	if ((*g)->a == *p) {
+		*gcs = odcs_a;
+		*u = (*g)->b;
+	} else {
+		assert((*g)->b == *p);
+		*gcs = odcs_b;
+		*u = (*g)->a;
+	}
+}
+
+/** Return sibling and parent w.r.t. parent.
+ *
+ * @param n Node
+ * @param p Parent of @ an
+ * @param pcs Place to store position of @a n w.r.t. @a p.
+ * @param rs Place to strore pointer to sibling
+ */
+static void odict_sibling(odlink_t *n, odlink_t *p, odict_child_sel_t *pcs,
+    odlink_t **rs)
+{
+	if (p->a == n) {
+		*pcs = odcs_a;
+		*rs = p->b;
+	} else {
+		*pcs = odcs_b;
+		*rs = p->a;
+	}
+}
+
+/** Ordered dictionary left rotation.
+ *
+ *    Q           P
+ *  P   C   <- A    Q
+ * A B             B C
+ *
+ */
+static void odict_rotate_left(odlink_t *p)
+{
+	odlink_t *q;
+
+	q = p->b;
+	assert(q != NULL);
+
+	/* Replace P with Q as the root of the subtree */
+	odict_replace_subtree(q, p);
+
+	/* Relink P under Q, B under P */
+	p->up = q;
+	p->b = q->a;
+	if (p->b != NULL)
+		p->b->up = p;
+	q->a = p;
+
+	/* Fix odict root */
+	if (p->odict->root == p)
+		p->odict->root = q;
+}
+
+/** Ordered dictionary right rotation.
+ *
+ *    Q           P
+ *  P   C   -> A    Q
+ * A B             B C
+ *
+ */
+static void odict_rotate_right(odlink_t *q)
+{
+	odlink_t *p;
+
+	p = q->a;
+	assert(p != NULL);
+
+	/* Replace Q with P as the root of the subtree */
+	odict_replace_subtree(p, q);
+
+	/* Relink Q under P, B under Q */
+	q->up = p;
+	q->a = p->b;
+	if (q->a != NULL)
+		q->a->up = q;
+	p->b = q;
+
+	/* Fix odict root */
+	if (q->odict->root == q)
+		q->odict->root = p;
+}
+
+/** Swap two nodes.
+ *
+ * Swap position of two nodes in the tree, keeping their identity.
+ * This means we don't copy the contents, instead we shuffle around pointers
+ * from and to the nodes.
+ *
+ * @param a First node
+ * @param b Second node
+ */
+static void odict_swap_node(odlink_t *a, odlink_t *b)
+{
+	odlink_t *n;
+	odict_color_t c;
+
+	/* Backlink from A's parent */
+	if (a->up != NULL && a->up != b) {
+		if (a->up->a == a) {
+			a->up->a = b;
+		} else {
+			assert(a->up->b == a);
+			a->up->b = b;
+		}
+	}
+
+	/* Backlink from A's left child */
+	if (a->a != NULL && a->a != b)
+		a->a->up = b;
+	/* Backling from A's right child */
+	if (a->b != NULL && a->b != b)
+		a->b->up = b;
+
+	/* Backlink from B's parent */
+	if (b->up != NULL && b->up != a) {
+		if (b->up->a == b) {
+			b->up->a = a;
+		} else {
+			assert(b->up->b == b);
+			b->up->b = a;
+		}
+	}
+
+	/* Backlink from B's left child */
+	if (b->a != NULL && b->a != a)
+		b->a->up = a;
+	/* Backling from B's right child */
+	if (b->b != NULL && b->b != a)
+		b->b->up = a;
+
+	/*
+	 * Swap links going out of A and out of B
+	 */
+	n = a->up;
+	a->up = b->up;
+	b->up = n;
+
+	n = a->a;
+	a->a = b->a;
+	b->a = n;
+
+	n = a->b;
+	a->b = b->b;
+	b->b = n;
+
+	c = a->color;
+	a->color = b->color;
+	b->color = c;
+
+	/* When A and B are adjacent, fix self-loops that might have arisen */
+	if (a->up == a)
+		a->up = b;
+	if (a->a == a)
+		a->a = b;
+	if (a->b == a)
+		a->b = b;
+	if (b->up == b)
+		b->up = a;
+	if (b->a == b)
+		b->a = a;
+	if (b->b == b)
+		b->b = a;
+
+	/* Fix odict root */
+	if (a == a->odict->root)
+		a->odict->root = b;
+	else if (b == a->odict->root)
+		a->odict->root = a;
+}
+
+/** Replace subtree.
+ *
+ * Replace subtree @a old with another subtree @a n. This makes the parent
+ * point to the new subtree root and the up pointer of @a n to point to
+ * the parent.
+ *
+ * @param old Subtree to be replaced
+ * @param n New subtree
+ */
+static void odict_replace_subtree(odlink_t *n, odlink_t *old)
+{
+	if (old->up != NULL) {
+		if (old->up->a == old) {
+			old->up->a = n;
+		} else {
+			assert(old->up->b == old);
+			old->up->b = n;
+		}
+	} else {
+		assert(old->odict->root == old);
+		old->odict->root = n;
+	}
+
+	n->up = old->up;
+}
+
+/** Unlink node.
+ *
+ * @param n Ordered dictionary node
+ */
+static void odict_unlink(odlink_t *n)
+{
+	if (n->up != NULL) {
+		if (n->up->a == n) {
+			n->up->a = NULL;
+		} else {
+			assert(n->up->b == n);
+			n->up->b = NULL;
+		}
+
+		n->up = NULL;
+	} else {
+		assert(n->odict->root == n);
+		n->odict->root = NULL;
+	}
+
+	if (n->a != NULL) {
+		n->a->up = NULL;
+		n->a = NULL;
+	}
+
+	if (n->b != NULL) {
+		n->b->up = NULL;
+		n->b = NULL;
+	}
+
+	n->odict = NULL;
+	list_remove(&n->lentries);
+}
+
+/** Link node as left child.
+ *
+ * Append new node @a n as left child of existing node @a old.
+ *
+ * @param n New node
+ * @param old Old node
+ */
+static void odict_link_child_a(odlink_t *n, odlink_t *old)
+{
+	old->a = n;
+	n->up = old;
+	n->odict = old->odict;
+	list_insert_before(&n->lentries, &old->lentries);
+}
+
+/** Link node as right child.
+ *
+ * Append new node @a n as right child of existing node @a old.
+ *
+ * @param n New node
+ * @param old Old node
+ */
+static void odict_link_child_b(odlink_t *n, odlink_t *old)
+{
+	old->b = n;
+	n->up = old;
+	n->odict = old->odict;
+	list_insert_after(&n->lentries, &old->lentries);
+}
+
+/** Get node where search should be started.
+ *
+ * @param odict Ordered dictionary
+ * @param key Key being searched for
+ * @param hint Node that might be near the search target or @c NULL
+ *
+ * @return Node from where search should be started
+ */
+static odlink_t *odict_search_start_node(odict_t *odict, void *key,
+    odlink_t *hint)
+{
+	odlink_t *a;
+	odlink_t *b;
+	odlink_t *cur;
+	int d, da, db;
+
+	assert(hint == NULL || hint->odict == odict);
+
+	/* If the key is greater than the maximum, start search in the maximum */
+	b = odict_last(odict);
+	if (b != NULL) {
+		d = odict->cmp(odict->getkey(b), key);
+		if (d < 0)
+			return b;
+	}
+
+	/* If the key is less tna the minimum, start search in the minimum */
+	a = odict_first(odict);
+	if (a != NULL) {
+		d = odict->cmp(key, odict->getkey(a));
+		if (d < 0)
+			return a;
+	}
+
+	/*
+	 * Proposition: Let A, B be two BST nodes such that B is a descendant
+	 * of A. Let N be a node such that either key(A) < key(N) < key(B)
+	 * Then N is a descendant of A.
+	 * Corollary: We can start searching for N from A, instead from
+	 * the root.
+	 *
+	 * Proof: When walking the BST in order, visit_tree(A) does a
+	 * visit_tree(A->a), visit(A), visit(A->b). If key(A) < key(B),
+	 * we will first visit A, then while visiting all nodes with values
+	 * between A and B we will not leave subtree A->b.
+	 */
+
+	/* If there is no hint, start search from the root */
+	if (hint == NULL)
+		return odict->root;
+
+	/*
+	 * Start from hint and walk up to the root, keeping track of
+	 * minimum and maximum. Once key is strictly between them,
+	 * we can return the current node, which we've proven to be
+	 * an ancestor of a potential node with the given key
+	 */
+	a = b = cur = hint;
+	while (cur->up != NULL) {
+		cur = cur->up;
+
+		d = odict->cmp(odict->getkey(cur), odict->getkey(a));
+		if (d < 0)
+			a = cur;
+
+		d = odict->cmp(odict->getkey(b), odict->getkey(cur));
+		if (d < 0)
+			b = cur;
+
+		da = odict->cmp(odict->getkey(a), key);
+		db = odict->cmp(key, odict->getkey(b));
+		if (da < 0 && db < 0) {
+			/* Both a and b are descendants of cur */
+			return cur;
+		}
+	}
+
+	return odict->root;
+}
+
+/** @}
+ */
Index: common/gsort.c
===================================================================
--- common/gsort.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/gsort.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2005 Sergey Bondari
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+
+/**
+ * @file
+ * @brief Gnome Sort.
+ *
+ * This file contains an implementation of gnome sort
+ *
+ */
+
+#include <gsort.h>
+#include <inttypes.h>
+#include <mem.h>
+#include <stdlib.h>
+
+/** Immediate buffer size.
+ *
+ * For small buffer sizes avoid doing malloc()
+ * and use the stack.
+ *
+ */
+#define IBUF_SIZE  32
+
+/** Array accessor.
+ *
+ */
+#define INDEX(buf, i, elem_size)  ((buf) + (i) * (elem_size))
+
+/** Gnome sort
+ *
+ * Apply generic gnome sort algorithm on supplied data,
+ * using pre-allocated buffer.
+ *
+ * @param data      Pointer to data to be sorted.
+ * @param cnt       Number of elements to be sorted.
+ * @param elem_size Size of one element.
+ * @param cmp       Comparator function.
+ * @param arg       3rd argument passed to cmp.
+ * @param slot      Pointer to scratch memory buffer
+ *                  elem_size bytes long.
+ *
+ */
+static void _gsort(void *data, size_t cnt, size_t elem_size, sort_cmp_t cmp,
+    void *arg, void *slot)
+{
+	size_t i = 0;
+
+	while (i < cnt) {
+		if ((i != 0) &&
+		    (cmp(INDEX(data, i, elem_size),
+		    INDEX(data, i - 1, elem_size), arg) < 0)) {
+			memcpy(slot, INDEX(data, i, elem_size), elem_size);
+			memcpy(INDEX(data, i, elem_size), INDEX(data, i - 1, elem_size),
+			    elem_size);
+			memcpy(INDEX(data, i - 1, elem_size), slot, elem_size);
+			i--;
+		} else
+			i++;
+	}
+}
+
+/** Gnome sort wrapper
+ *
+ * This is only a wrapper that takes care of memory
+ * allocations for storing the slot element for generic
+ * gnome sort algorithm.
+ *
+ * @param data      Pointer to data to be sorted.
+ * @param cnt       Number of elements to be sorted.
+ * @param elem_size Size of one element.
+ * @param cmp       Comparator function.
+ * @param arg       3rd argument passed to cmp.
+ *
+ * @return True if sorting succeeded.
+ *
+ */
+bool gsort(void *data, size_t cnt, size_t elem_size, sort_cmp_t cmp, void *arg)
+{
+	uint8_t ibuf_slot[IBUF_SIZE];
+	void *slot;
+
+	if (elem_size > IBUF_SIZE) {
+		slot = (void *) malloc(elem_size);
+		if (!slot)
+			return false;
+	} else
+		slot = (void *) ibuf_slot;
+
+	_gsort(data, cnt, elem_size, cmp, arg, slot);
+
+	if (elem_size > IBUF_SIZE)
+		free(slot);
+
+	return true;
+}
+
+/** @}
+ */
Index: common/include/adt/bitmap.h
===================================================================
--- common/include/adt/bitmap.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/include/adt/bitmap.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2006 Jakub Jermar
+ * 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 kernel_generic_adt
+ * @{
+ */
+/** @file
+ */
+
+#ifndef KERN_BITMAP_H_
+#define KERN_BITMAP_H_
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#define BITMAP_ELEMENT   8
+#define BITMAP_REMAINER  7
+
+typedef struct {
+	size_t elements;
+	uint8_t *bits;
+	size_t next_fit;
+} bitmap_t;
+
+static inline void bitmap_set(bitmap_t *bitmap, size_t element,
+    unsigned int value)
+{
+	if (element >= bitmap->elements)
+		return;
+
+	size_t byte = element / BITMAP_ELEMENT;
+	uint8_t mask = 1 << (element & BITMAP_REMAINER);
+
+	if (value) {
+		bitmap->bits[byte] |= mask;
+	} else {
+		bitmap->bits[byte] &= ~mask;
+		bitmap->next_fit = byte;
+	}
+}
+
+static inline unsigned int bitmap_get(bitmap_t *bitmap, size_t element)
+{
+	if (element >= bitmap->elements)
+		return 0;
+
+	size_t byte = element / BITMAP_ELEMENT;
+	uint8_t mask = 1 << (element & BITMAP_REMAINER);
+
+	return !!((bitmap->bits)[byte] & mask);
+}
+
+extern size_t bitmap_size(size_t);
+extern void bitmap_initialize(bitmap_t *, size_t, void *);
+
+extern void bitmap_set_range(bitmap_t *, size_t, size_t);
+extern void bitmap_clear_range(bitmap_t *, size_t, size_t);
+
+extern bool bitmap_allocate_range(bitmap_t *, size_t, size_t, size_t, size_t,
+    size_t *);
+extern void bitmap_copy(bitmap_t *, bitmap_t *, size_t);
+
+#endif
+
+/** @}
+ */
Index: common/include/adt/checksum.h
===================================================================
--- common/include/adt/checksum.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/include/adt/checksum.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2012 Dominik Taborsky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBC_CHECKSUM_H_
+#define _LIBC_CHECKSUM_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+extern uint32_t compute_crc32(uint8_t *, size_t);
+extern uint32_t compute_crc32_seed(uint8_t *, size_t, uint32_t);
+
+#endif
+
+/** @}
+ */
Index: common/include/adt/circ_buf.h
===================================================================
--- common/include/adt/circ_buf.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/include/adt/circ_buf.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2017 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file Circular buffer
+ */
+
+#ifndef _LIBC_CIRC_BUF_H_
+#define _LIBC_CIRC_BUF_H_
+
+#include <errno.h>
+#include <stddef.h>
+
+/** Circular buffer */
+typedef struct {
+	/** Buffer */
+	void *buf;
+	/** Number of buffer members */
+	size_t nmemb;
+	/** Member size */
+	size_t size;
+	/** Read position */
+	size_t rp;
+	/** Write position */
+	size_t wp;
+	/** Number of used entries */
+	size_t nused;
+} circ_buf_t;
+
+extern void circ_buf_init(circ_buf_t *, void *, size_t, size_t);
+extern size_t circ_buf_nfree(circ_buf_t *);
+extern size_t circ_buf_nused(circ_buf_t *);
+extern errno_t circ_buf_push(circ_buf_t *, const void *);
+extern errno_t circ_buf_pop(circ_buf_t *, void *);
+
+#endif
+
+/** @}
+ */
Index: common/include/adt/fifo.h
===================================================================
--- common/include/adt/fifo.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/include/adt/fifo.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2006 Jakub Jermar
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+/*
+ * This implementation of FIFO stores values in an array
+ * (static or dynamic). As such, these FIFOs have upper bound
+ * on number of values they can store. Push and pop operations
+ * are done via accessing the array through head and tail indices.
+ * Because of better operation ordering in fifo_pop(), the access
+ * policy for these two indices is to 'increment (mod size of FIFO)
+ * and use'.
+ */
+
+#ifndef _LIBC_FIFO_H_
+#define _LIBC_FIFO_H_
+
+#include <stdlib.h>
+
+typedef unsigned long fifo_count_t;
+typedef unsigned long fifo_index_t;
+
+#define FIFO_CREATE_STATIC(name, t, itms) \
+	struct { \
+		t fifo[(itms)]; \
+		fifo_count_t items; \
+		fifo_index_t head; \
+		fifo_index_t tail; \
+	} name
+
+/** Create and initialize static FIFO.
+ *
+ * FIFO is allocated statically.
+ * This macro is suitable for creating smaller FIFOs.
+ *
+ * @param name Name of FIFO.
+ * @param t Type of values stored in FIFO.
+ * @param itms Number of items that can be stored in FIFO.
+ */
+#define FIFO_INITIALIZE_STATIC(name, t, itms)		\
+	FIFO_CREATE_STATIC(name, t, itms) = {		\
+		.items = (itms),			\
+		.head = 0,				\
+		.tail = 0				\
+	}
+
+/** Create and prepare dynamic FIFO.
+ *
+ * FIFO is allocated dynamically.
+ * This macro is suitable for creating larger FIFOs.
+ *
+ * @param name Name of FIFO.
+ * @param t Type of values stored in FIFO.
+ * @param itms Number of items that can be stored in FIFO.
+ */
+#define FIFO_INITIALIZE_DYNAMIC(name, t, itms)		\
+	struct {					\
+		t *fifo;				\
+		fifo_count_t items;			\
+		fifo_index_t head;			\
+		fifo_index_t tail;			\
+	} name = {					\
+		.fifo = NULL,				\
+		.items = (itms),			\
+		.head = 0,				\
+		.tail = 0				\
+	}
+
+/** Pop value from head of FIFO.
+ *
+ * @param name FIFO name.
+ *
+ * @return Leading value in FIFO.
+ */
+#define fifo_pop(name) \
+	name.fifo[name.head = (name.head + 1) < name.items ? (name.head + 1) : 0]
+
+/** Push value to tail of FIFO.
+ *
+ * @param name FIFO name.
+ * @param value Value to be appended to FIFO.
+ *
+ */
+#define fifo_push(name, value) \
+	name.fifo[name.tail = (name.tail + 1) < name.items ? (name.tail + 1) : 0] = (value)
+
+/** Allocate memory for dynamic FIFO.
+ *
+ * @param name FIFO name.
+ */
+#define fifo_create(name) \
+	name.fifo = malloc(sizeof(*name.fifo) * name.items)
+
+#endif
+
+/** @}
+ */
Index: common/include/adt/gcdlcm.h
===================================================================
--- common/include/adt/gcdlcm.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/include/adt/gcdlcm.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2009 Martin Decky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBC_GCDLCM_H_
+#define _LIBC_GCDLCM_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define DECLARE_GCD(type, name) \
+	static inline type name(type a, type b) \
+	{ \
+		if (a == 0) \
+			return b; \
+		 \
+		while (b != 0) { \
+			if (a > b) \
+				a -= b; \
+			else \
+				b -= a; \
+		} \
+		 \
+		return a; \
+	}
+
+#define DECLARE_LCM(type, name, gcd) \
+	static inline type name(type a, type b) \
+	{ \
+		return (a * b) / gcd(a, b); \
+	}
+
+DECLARE_GCD(uint32_t, gcd32);
+DECLARE_GCD(uint64_t, gcd64);
+DECLARE_GCD(size_t, gcd);
+
+DECLARE_LCM(uint32_t, lcm32, gcd32);
+DECLARE_LCM(uint64_t, lcm64, gcd64);
+DECLARE_LCM(size_t, lcm, gcd);
+
+#endif
+
+/** @}
+ */
Index: common/include/adt/hash.h
===================================================================
--- common/include/adt/hash.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/include/adt/hash.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2012 Adam Hraska
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+#ifndef _LIBC_ADT_HASH_H_
+#define _LIBC_ADT_HASH_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+/** Produces a uniform hash affecting all output bits from the skewed input. */
+static inline uint32_t hash_mix32(uint32_t hash)
+{
+	/*
+	 * Thomas Wang's modification of Bob Jenkin's hash mixing function:
+	 * http://www.concentric.net/~Ttwang/tech/inthash.htm
+	 * Public domain.
+	 */
+	hash = ~hash + (hash << 15);
+	hash = hash ^ (hash >> 12);
+	hash = hash + (hash << 2);
+	hash = hash ^ (hash >> 4);
+	hash = hash * 2057;
+	hash = hash ^ (hash >> 16);
+	return hash;
+}
+
+/** Produces a uniform hash affecting all output bits from the skewed input. */
+static inline uint64_t hash_mix64(uint64_t hash)
+{
+	/*
+	 * Thomas Wang's public domain 64-bit hash mixing function:
+	 * http://www.concentric.net/~Ttwang/tech/inthash.htm
+	 */
+	hash = (hash ^ 61) ^ (hash >> 16);
+	hash = hash + (hash << 3);
+	hash = hash ^ (hash >> 4);
+	hash = hash * 0x27d4eb2d;
+	hash = hash ^ (hash >> 15);
+	/*
+	 * Lower order bits are mixed more thoroughly. Swap them with
+	 * the higher order bits and make the resulting higher order bits
+	 * more usable.
+	 */
+	return (hash << 32) | (hash >> 32);
+}
+
+/** Produces a uniform hash affecting all output bits from the skewed input. */
+static inline size_t hash_mix(size_t hash)
+{
+	if (sizeof(long) == 4)
+		return hash_mix32(hash);
+	else
+		return hash_mix64(hash);
+}
+
+/** Use to create a hash from multiple values.
+ *
+ * Typical usage:
+ * @code
+ * int car_id;
+ * bool car_convertible;
+ * // ..
+ * size_t hash = 0;
+ * hash = hash_combine(hash, car_id);
+ * hash = hash_combine(hash, car_convertible);
+ * // Now use hash as a hash of both car_id and car_convertible.
+ * @endcode
+ */
+static inline size_t hash_combine(size_t seed, size_t hash)
+{
+	/*
+	 * todo: use Bob Jenkin's proper mixing hash pass:
+	 * http://burtleburtle.net/bob/c/lookup3.c
+	 */
+	seed ^= hash + 0x9e3779b9 +
+	    ((seed << 5) | (seed >> (sizeof(size_t) * 8 - 5)));
+	return seed;
+}
+
+#endif
Index: common/include/adt/hash_table.h
===================================================================
--- common/include/adt/hash_table.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/include/adt/hash_table.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2006 Jakub Jermar
+ * Copyright (c) 2012 Adam Hraska
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBC_HASH_TABLE_H_
+#define _LIBC_HASH_TABLE_H_
+
+#include <adt/list.h>
+#include <stdbool.h>
+#include <macros.h>
+#include <member.h>
+
+/** Opaque hash table link type. */
+typedef struct ht_link {
+	link_t link;
+} ht_link_t;
+
+/** Set of operations for hash table. */
+typedef struct {
+	/** Returns the hash of the key stored in the item (ie its lookup key). */
+	size_t (*hash)(const ht_link_t *item);
+
+	/** Returns the hash of the key. */
+	size_t (*key_hash)(const void *key);
+
+	/** True if the items are equal (have the same lookup keys). */
+	bool (*equal)(const ht_link_t *item1, const ht_link_t *item2);
+
+	/** Returns true if the key is equal to the item's lookup key. */
+	bool (*key_equal)(const void *key, const ht_link_t *item);
+
+	/** Hash table item removal callback.
+	 *
+	 * Must not invoke any mutating functions of the hash table.
+	 *
+	 * @param item Item that was removed from the hash table.
+	 */
+	void (*remove_callback)(ht_link_t *item);
+} hash_table_ops_t;
+
+/** Hash table structure. */
+typedef struct {
+	const hash_table_ops_t *op;
+	list_t *bucket;
+	size_t bucket_cnt;
+	size_t full_item_cnt;
+	size_t item_cnt;
+	size_t max_load;
+	bool apply_ongoing;
+} hash_table_t;
+
+#define hash_table_get_inst(item, type, member) \
+	member_to_inst((item), type, member)
+
+extern bool hash_table_create(hash_table_t *, size_t, size_t,
+    const hash_table_ops_t *);
+extern void hash_table_destroy(hash_table_t *);
+
+extern bool hash_table_empty(hash_table_t *);
+extern size_t hash_table_size(hash_table_t *);
+
+extern void hash_table_clear(hash_table_t *);
+extern void hash_table_insert(hash_table_t *, ht_link_t *);
+extern bool hash_table_insert_unique(hash_table_t *, ht_link_t *);
+extern ht_link_t *hash_table_find(const hash_table_t *, const void *);
+extern ht_link_t *hash_table_find_next(const hash_table_t *, ht_link_t *,
+    ht_link_t *);
+extern size_t hash_table_remove(hash_table_t *, const void *);
+extern void hash_table_remove_item(hash_table_t *, ht_link_t *);
+extern void hash_table_apply(hash_table_t *, bool (*)(ht_link_t *, void *),
+    void *);
+
+#endif
+
+/** @}
+ */
Index: common/include/adt/list.h
===================================================================
--- common/include/adt/list.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/include/adt/list.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,473 @@
+/*
+ * Copyright (c) 2001-2004 Jakub Jermar
+ * Copyright (c) 2013 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBC_LIST_H_
+#define _LIBC_LIST_H_
+
+#include <assert.h>
+#include <member.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <trace.h>
+#include <_bits/decls.h>
+
+#ifndef __cplusplus
+
+/**
+ * We don't define the macros in C++ to avoid polluting headers with
+ * namespaceless names. We don't actually need them, so this is fine.
+ * We still allow including the rest of the file (in `helenos` namespace)
+ * so that we can expose publicly visible types that have list_t members.
+ */
+
+/** Declare and initialize statically allocated list.
+ *
+ * @param name Name of the new statically allocated list.
+ *
+ */
+#define LIST_INITIALIZE(name) \
+	list_t name = LIST_INITIALIZER(name)
+
+/** Initializer for statically allocated list.
+ *
+ * @code
+ * struct named_list {
+ *     const char *name;
+ *     list_t list;
+ * } var = {
+ *     .name = "default name",
+ *     .list = LIST_INITIALIZER(name_list.list)
+ * };
+ * @endcode
+ *
+ * @param name Name of the new statically allocated list.
+ *
+ */
+#define LIST_INITIALIZER(name) \
+	{ \
+		.head = { \
+			.prev = &(name).head, \
+			.next = &(name).head \
+		} \
+	}
+
+#define list_get_instance(link, type, member) \
+	member_to_inst(link, type, member)
+
+#define list_foreach(list, member, itype, iterator) \
+	for (itype *iterator = NULL; iterator == NULL; iterator = (itype *) sizeof(itype)) \
+		for (link_t *_link = (list).head.next; \
+		    iterator = list_get_instance(_link, itype, member), \
+		    _link != &(list).head; _link = _link->next)
+
+#define list_foreach_rev(list, member, itype, iterator) \
+	for (itype *iterator = NULL; iterator == NULL; iterator = (itype *) sizeof(itype)) \
+		for (link_t *_link = (list).head.prev; \
+		    iterator = list_get_instance(_link, itype, member), \
+		    _link != &(list).head; _link = _link->prev)
+
+/** Unlike list_foreach(), allows removing items while traversing a list.
+ *
+ * @code
+ * list_t mylist;
+ * typedef struct item {
+ *     int value;
+ *     link_t item_link;
+ * } item_t;
+ *
+ * //..
+ *
+ * // Print each list element's value and remove the element from the list.
+ * list_foreach_safe(mylist, cur_link, next_link) {
+ *     item_t *cur_item = list_get_instance(cur_link, item_t, item_link);
+ *     printf("%d\n", cur_item->value);
+ *     list_remove(cur_link);
+ * }
+ * @endcode
+ *
+ * @param list List to traverse.
+ * @param iterator Iterator to the current element of the list.
+ *             The item this iterator points may be safely removed
+ *             from the list.
+ * @param next_iter Iterator to the next element of the list.
+ */
+#define list_foreach_safe(list, iterator, next_iter) \
+	for (link_t *iterator = (list).head.next, \
+	    *next_iter = iterator->next; \
+	    iterator != &(list).head; \
+	    iterator = next_iter, next_iter = iterator->next)
+
+#define assert_link_not_used(link) \
+	assert(!link_used(link))
+
+#define list_pop(list, type, member) \
+	((type *) list_pop_internal(list, \
+	    (list_link_to_void(&(((type *) NULL)->member)) - NULL)))
+
+#endif  /* !__cplusplus */
+
+__HELENOS_DECLS_BEGIN;
+
+/** Doubly linked list link. */
+typedef struct __adt_list_link {
+	struct __adt_list_link *prev;  /**< Pointer to the previous item in the list. */
+	struct __adt_list_link *next;  /**< Pointer to the next item in the list. */
+} link_t;
+
+/** Doubly linked list. */
+typedef struct {
+	link_t head;  /**< List head. Does not have any data. */
+} list_t;
+
+extern bool list_member(const link_t *, const list_t *);
+extern void list_splice(list_t *, link_t *);
+extern size_t list_count(const list_t *);
+
+/** Returns true if the link is definitely part of a list. False if not sure. */
+static inline bool link_in_use(const link_t *link)
+{
+	return link->prev != NULL && link->next != NULL;
+}
+
+/** Initialize doubly-linked circular list link
+ *
+ * Initialize doubly-linked list link.
+ *
+ * @param link Pointer to link_t structure to be initialized.
+ *
+ */
+_NO_TRACE static inline void link_initialize(link_t *link)
+{
+	link->prev = NULL;
+	link->next = NULL;
+}
+
+/** Initialize doubly-linked circular list
+ *
+ * Initialize doubly-linked circular list.
+ *
+ * @param list Pointer to list_t structure.
+ *
+ */
+_NO_TRACE static inline __CONSTEXPR void list_initialize(list_t *list)
+{
+	list->head.prev = &list->head;
+	list->head.next = &list->head;
+}
+
+/** Insert item before another item in doubly-linked circular list.
+ *
+ */
+static inline void list_insert_before(link_t *lnew, link_t *lold)
+{
+	lnew->next = lold;
+	lnew->prev = lold->prev;
+	lold->prev->next = lnew;
+	lold->prev = lnew;
+}
+
+/** Insert item after another item in doubly-linked circular list.
+ *
+ */
+static inline void list_insert_after(link_t *lnew, link_t *lold)
+{
+	lnew->prev = lold;
+	lnew->next = lold->next;
+	lold->next->prev = lnew;
+	lold->next = lnew;
+}
+
+/** Add item to the beginning of doubly-linked circular list
+ *
+ * Add item to the beginning of doubly-linked circular list.
+ *
+ * @param link Pointer to link_t structure to be added.
+ * @param list Pointer to list_t structure.
+ *
+ */
+_NO_TRACE static inline void list_prepend(link_t *link, list_t *list)
+{
+	list_insert_after(link, &list->head);
+}
+
+/** Add item to the end of doubly-linked circular list
+ *
+ * Add item to the end of doubly-linked circular list.
+ *
+ * @param link Pointer to link_t structure to be added.
+ * @param list Pointer to list_t structure.
+ *
+ */
+_NO_TRACE static inline void list_append(link_t *link, list_t *list)
+{
+	list_insert_before(link, &list->head);
+}
+
+/** Remove item from doubly-linked circular list
+ *
+ * Remove item from doubly-linked circular list.
+ *
+ * @param link Pointer to link_t structure to be removed from the list
+ *             it is contained in.
+ *
+ */
+_NO_TRACE static inline void list_remove(link_t *link)
+{
+	if ((link->prev != NULL) && (link->next != NULL)) {
+		link->next->prev = link->prev;
+		link->prev->next = link->next;
+	}
+
+	link_initialize(link);
+}
+
+/** Query emptiness of doubly-linked circular list
+ *
+ * Query emptiness of doubly-linked circular list.
+ *
+ * @param list Pointer to lins_t structure.
+ *
+ */
+_NO_TRACE static inline bool list_empty(const list_t *list)
+{
+	return (list->head.next == &list->head);
+}
+
+/** Get first item in list.
+ *
+ * @param list Pointer to list_t structure.
+ *
+ * @return Head item of the list.
+ * @return NULL if the list is empty.
+ *
+ */
+static inline link_t *list_first(const list_t *list)
+{
+	return ((list->head.next == &list->head) ? NULL : list->head.next);
+}
+
+/** Get last item in list.
+ *
+ * @param list Pointer to list_t structure.
+ *
+ * @return Head item of the list.
+ * @return NULL if the list is empty.
+ *
+ */
+static inline link_t *list_last(const list_t *list)
+{
+	return (list->head.prev == &list->head) ? NULL : list->head.prev;
+}
+
+/** Get next item in list.
+ *
+ * @param link Current item link
+ * @param list List containing @a link
+ *
+ * @return Next item or NULL if @a link is the last item.
+ */
+static inline link_t *list_next(const link_t *link, const list_t *list)
+{
+	return (link->next == &list->head) ? NULL : link->next;
+}
+
+/** Get previous item in list.
+ *
+ * @param link Current item link
+ * @param list List containing @a link
+ *
+ * @return Previous item or NULL if @a link is the first item.
+ */
+static inline link_t *list_prev(const link_t *link, const list_t *list)
+{
+	return (link->prev == &list->head) ? NULL : link->prev;
+}
+
+/** Split or concatenate headless doubly-linked circular list
+ *
+ * Split or concatenate headless doubly-linked circular list.
+ *
+ * Note that the algorithm works both directions:
+ * concatenates splitted lists and splits concatenated lists.
+ *
+ * @param part1 Pointer to link_t structure leading the first
+ *              (half of the headless) list.
+ * @param part2 Pointer to link_t structure leading the second
+ *              (half of the headless) list.
+ *
+ */
+_NO_TRACE static inline void headless_list_split_or_concat(link_t *part1, link_t *part2)
+{
+	if (part1 == NULL || part2 == NULL)
+		return;
+
+	part1->prev->next = part2;
+	part2->prev->next = part1;
+
+	link_t *hlp = part1->prev;
+
+	part1->prev = part2->prev;
+	part2->prev = hlp;
+}
+
+/** Split headless doubly-linked circular list
+ *
+ * Split headless doubly-linked circular list.
+ *
+ * @param part1 Pointer to link_t structure leading
+ *              the first half of the headless list.
+ * @param part2 Pointer to link_t structure leading
+ *              the second half of the headless list.
+ *
+ */
+_NO_TRACE static inline void headless_list_split(link_t *part1, link_t *part2)
+{
+	headless_list_split_or_concat(part1, part2);
+}
+
+/** Concatenate two headless doubly-linked circular lists
+ *
+ * Concatenate two headless doubly-linked circular lists.
+ *
+ * @param part1 Pointer to link_t structure leading
+ *              the first headless list.
+ * @param part2 Pointer to link_t structure leading
+ *              the second headless list.
+ *
+ */
+_NO_TRACE static inline void headless_list_concat(link_t *part1, link_t *part2)
+{
+	headless_list_split_or_concat(part1, part2);
+}
+
+/** Swap the contents of two lists.
+ *
+ * @param list1
+ * @param list2
+ */
+static inline void list_swap(list_t *list1, list_t *list2)
+{
+	link_t *first1 = list_first(list1);
+	link_t *first2 = list_first(list2);
+
+	/* Detach both lists from their heads. */
+	headless_list_split(&list1->head, first1);
+	headless_list_split(&list2->head, first2);
+
+	/* Attach both lists to their new heads. */
+	headless_list_concat(&list1->head, first2);
+	headless_list_concat(&list2->head, first1);
+}
+
+/** Concatenate two lists
+ *
+ * Concatenate lists @a list1 and @a list2, producing a single
+ * list @a list1 containing items from both (in @a list1, @a list2
+ * order) and empty list @a list2.
+ *
+ * @param list1		First list and concatenated output
+ * @param list2 	Second list and empty output.
+ *
+ */
+_NO_TRACE static inline void list_concat(list_t *list1, list_t *list2)
+{
+	list_splice(list2, list1->head.prev);
+}
+
+/** Get n-th item in a list.
+ *
+ * @param list Pointer to link_t structure representing the list.
+ * @param n    Item number (indexed from zero).
+ *
+ * @return n-th item of the list.
+ * @return NULL if no n-th item found.
+ *
+ */
+static inline link_t *list_nth(const list_t *list, size_t n)
+{
+	size_t cnt = 0;
+
+	link_t *link = list_first(list);
+	while (link != NULL) {
+		if (cnt == n)
+			return link;
+
+		cnt++;
+		link = list_next(link, list);
+	}
+
+	return NULL;
+}
+
+/** Verify that argument type is a pointer to link_t (at compile time).
+ *
+ * This can be used to check argument type in a macro.
+ */
+static inline const void *list_link_to_void(const link_t *link)
+{
+	return link;
+}
+
+/** Determine if link is used.
+ *
+ * @param link Link
+ * @return @c true if link is used, @c false if not.
+ */
+static inline bool link_used(link_t *link)
+{
+	if (link->prev == NULL && link->next == NULL)
+		return false;
+
+	assert(link->prev != NULL && link->next != NULL);
+	return true;
+}
+
+static inline void *list_pop_internal(list_t *list, ptrdiff_t offset)
+{
+	link_t *tmp = list_first(list);
+	if (tmp == NULL)
+		return NULL;
+
+	list_remove(tmp);
+	return (void *) (((uint8_t *) tmp) - offset);
+}
+
+__HELENOS_DECLS_END;
+
+#endif
+
+/** @}
+ */
Index: common/include/adt/odict.h
===================================================================
--- common/include/adt/odict.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/include/adt/odict.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2016 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 kernel_generic_adt
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBC_ODICT_H_
+#define _LIBC_ODICT_H_
+
+#include <errno.h>
+#include <member.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <types/adt/odict.h>
+
+#define odict_get_instance(odlink, type, member) \
+	member_to_inst(odlink, type, member)
+
+extern void odict_initialize(odict_t *, odgetkey_t, odcmp_t);
+extern void odict_finalize(odict_t *);
+extern void odlink_initialize(odlink_t *);
+extern void odict_insert(odlink_t *, odict_t *, odlink_t *);
+extern void odict_remove(odlink_t *);
+extern void odict_key_update(odlink_t *, odict_t *);
+extern bool odlink_used(odlink_t *);
+extern bool odict_empty(odict_t *);
+extern unsigned long odict_count(odict_t *);
+extern odlink_t *odict_first(odict_t *);
+extern odlink_t *odict_last(odict_t *);
+extern odlink_t *odict_prev(odlink_t *, odict_t *);
+extern odlink_t *odict_next(odlink_t *, odict_t *);
+extern odlink_t *odict_find_eq(odict_t *, void *, odlink_t *);
+extern odlink_t *odict_find_eq_last(odict_t *, void *, odlink_t *);
+extern odlink_t *odict_find_geq(odict_t *, void *, odlink_t *);
+extern odlink_t *odict_find_gt(odict_t *, void *, odlink_t *);
+extern odlink_t *odict_find_leq(odict_t *, void *, odlink_t *);
+extern odlink_t *odict_find_lt(odict_t *, void *, odlink_t *);
+extern errno_t odict_validate(odict_t *);
+
+#endif
+
+/** @}
+ */
Index: common/include/bsearch.h
===================================================================
--- common/include/bsearch.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/include/bsearch.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBC_BSEARCH_H_
+#define _LIBC_BSEARCH_H_
+
+#include <stddef.h>
+#include <_bits/decls.h>
+
+__C_DECLS_BEGIN;
+
+extern void *bsearch(const void *, const void *, size_t, size_t,
+    int (*)(const void *, const void *));
+
+__C_DECLS_END;
+
+#endif
+
+/** @}
+ */
Index: common/include/ctype.h
===================================================================
--- common/include/ctype.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/include/ctype.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2006 Josef Cejka
+ * 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.
+ */
+
+#ifndef _LIBC_CTYPE_H_
+#define _LIBC_CTYPE_H_
+
+#include <_bits/decls.h>
+
+__C_DECLS_BEGIN;
+int islower(int);
+int isupper(int);
+int isalpha(int);
+int isdigit(int);
+int isalnum(int);
+int isblank(int);
+int iscntrl(int);
+int isprint(int);
+int isgraph(int);
+int isspace(int);
+int ispunct(int);
+int isxdigit(int);
+int tolower(int);
+int toupper(int);
+__C_DECLS_END;
+
+#endif
Index: common/include/gsort.h
===================================================================
--- common/include/gsort.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/include/gsort.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2005 Sergey Bondari
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBC_SORT_H_
+#define _LIBC_SORT_H_
+
+#include <stddef.h>
+#include <stdbool.h>
+
+typedef int (*sort_cmp_t)(void *, void *, void *);
+
+extern bool gsort(void *, size_t, size_t, sort_cmp_t, void *);
+
+#endif
+
+/** @}
+ */
Index: common/include/mem.h
===================================================================
--- common/include/mem.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/include/mem.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2005 Martin Decky
+ * Copyright (c) 2018 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBC_MEM_H_
+#define _LIBC_MEM_H_
+
+#include <stddef.h>
+#include <_bits/decls.h>
+
+__C_DECLS_BEGIN;
+
+extern void *memset(void *, int, size_t)
+    __attribute__((nonnull(1)));
+extern void *memcpy(void *, const void *, size_t)
+    __attribute__((nonnull(1, 2)));
+extern void *memmove(void *, const void *, size_t)
+    __attribute__((nonnull(1, 2)));
+extern int memcmp(const void *, const void *, size_t)
+    __attribute__((nonnull(1, 2)));
+extern void *memchr(const void *, int, size_t)
+    __attribute__((nonnull(1)));
+
+__C_DECLS_END;
+
+#if !__STDC_HOSTED__
+#define memset(dst, val, cnt)  __builtin_memset((dst), (val), (cnt))
+#define memcpy(dst, src, cnt)  __builtin_memcpy((dst), (src), (cnt))
+#define memcmp(s1, s2, cnt)    __builtin_memcmp((s1), (s2), (cnt))
+#define memmove(dst, src, cnt)  __builtin_memmove((dst), (src), (cnt))
+#define memchr(s, c, cnt)  __builtin_memchr((s), (c), (cnt))
+#endif
+
+#endif
+
+/** @}
+ */
Index: common/include/printf_core.h
===================================================================
--- common/include/printf_core.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/include/printf_core.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2006 Josef Cejka
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBC_PRINTF_CORE_H_
+#define _LIBC_PRINTF_CORE_H_
+
+#include <stddef.h>
+#include <stdarg.h>
+#include <uchar.h>
+
+/** Structure for specifying output methods for different printf clones. */
+typedef struct {
+	/* String output function, returns number of printed characters or EOF */
+	int (*str_write)(const char *, size_t, void *);
+
+	/* Wide string output function, returns number of printed characters or EOF */
+	int (*wstr_write)(const char32_t *, size_t, void *);
+
+	/* User data - output stream specification, state, locks, etc. */
+	void *data;
+} printf_spec_t;
+
+extern int printf_core(const char *, printf_spec_t *, va_list);
+
+#endif
+
+/** @}
+ */
Index: common/include/qsort.h
===================================================================
--- common/include/qsort.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/include/qsort.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2017 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBC_QSORT_H_
+#define _LIBC_QSORT_H_
+
+#include <stddef.h>
+#include <_bits/decls.h>
+
+__C_DECLS_BEGIN;
+extern void qsort(void *, size_t, size_t, int (*)(const void *,
+    const void *));
+__C_DECLS_END;
+
+#ifdef _HELENOS_SOURCE
+__HELENOS_DECLS_BEGIN;
+extern void qsort_r(void *, size_t, size_t, int (*)(const void *,
+    const void *, void *), void *);
+__HELENOS_DECLS_END;
+#endif
+
+#endif
+
+/** @}
+ */
Index: common/include/stdlib.h
===================================================================
--- common/include/stdlib.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/include/stdlib.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2005 Martin Decky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBC_STDLIB_H_
+#define _LIBC_STDLIB_H_
+
+#include <_bits/size_t.h>
+#include <_bits/wchar_t.h>
+#include <_bits/uchar.h>
+#include <_bits/decls.h>
+#include <bsearch.h>
+#include <qsort.h>
+
+#define EXIT_SUCCESS 0
+#define EXIT_FAILURE 1
+
+#define RAND_MAX  714025
+
+#define MB_CUR_MAX 4
+
+__C_DECLS_BEGIN;
+
+/** Type returned by the div function */
+typedef struct {
+	/** Quotient */
+	int quot;
+	/** Remainder */
+	int rem;
+} div_t;
+
+/** Type returned by the ldiv function */
+typedef struct {
+	/** Quotient */
+	long quot;
+	/** Remainder */
+	long rem;
+} ldiv_t;
+
+/** Type returned by the lldiv function */
+typedef struct {
+	/** Quotient */
+	long long quot;
+	/** Remainder */
+	long long rem;
+} lldiv_t;
+
+extern long double strtold(const char *, char **);
+
+extern int rand(void);
+extern void srand(unsigned int);
+
+extern void abort(void) __attribute__((noreturn));
+extern int atexit(void (*)(void));
+extern void exit(int) __attribute__((noreturn));
+extern void _Exit(int) __attribute__((noreturn));
+extern int at_quick_exit(void (*)(void));
+extern void quick_exit(int);
+
+extern char *getenv(const char *);
+extern int system(const char *);
+
+extern int abs(int);
+extern long labs(long);
+extern long long llabs(long long);
+
+extern int atoi(const char *);
+extern long atol(const char *);
+extern long long atoll(const char *);
+
+extern long strtol(const char *__restrict__, char **__restrict__, int);
+extern long long strtoll(const char *__restrict__, char **__restrict__, int);
+extern unsigned long strtoul(const char *__restrict__, char **__restrict__, int);
+extern unsigned long long strtoull(const char *__restrict__, char **__restrict__, int);
+
+extern div_t div(int, int);
+extern ldiv_t ldiv(long, long);
+extern lldiv_t lldiv(long long, long long);
+
+extern void *malloc(size_t size)
+    __attribute__((malloc));
+extern void *calloc(size_t nmemb, size_t size)
+    __attribute__((malloc));
+extern void *realloc(void *addr, size_t size)
+    __attribute__((warn_unused_result));
+extern void free(void *addr);
+
+#ifdef _HELENOS_SOURCE
+__HELENOS_DECLS_BEGIN;
+
+extern void *memalign(size_t align, size_t size)
+    __attribute__((malloc));
+
+__HELENOS_DECLS_END;
+#endif
+
+__C_DECLS_END;
+
+#endif
+
+/** @}
+ */
Index: common/include/str.h
===================================================================
--- common/include/str.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/include/str.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2001-2004 Jakub Jermar
+ * Copyright (c) 2005 Martin Decky
+ * Copyright (c) 2011 Oleg Romanenko
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBC_STR_H_
+#define _LIBC_STR_H_
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <mem.h>
+#include <_bits/decls.h>
+#include <_bits/uchar.h>
+
+#ifndef __cplusplus
+
+/* Common Unicode characters */
+#define U_SPECIAL      '?'
+
+#define U_LEFT_ARROW   0x2190
+#define U_UP_ARROW     0x2191
+#define U_RIGHT_ARROW  0x2192
+#define U_DOWN_ARROW   0x2193
+
+#define U_PAGE_UP      0x21de
+#define U_PAGE_DOWN    0x21df
+
+#define U_HOME_ARROW   0x21f1
+#define U_END_ARROW    0x21f2
+
+#define U_NULL         0x2400
+#define U_ESCAPE       0x241b
+#define U_DELETE       0x2421
+
+#define U_CURSOR       0x2588
+
+/** No size limit constant */
+#define STR_NO_LIMIT  ((size_t) -1)
+
+/** Maximum size of a string containing @c length characters */
+#define STR_BOUNDS(length)  ((length) << 2)
+
+/**
+ * Maximum size of a buffer needed to a string converted from space-padded
+ * ASCII of size @a spa_size using spascii_to_str().
+ */
+#define SPASCII_STR_BUFSIZE(spa_size) ((spa_size) + 1)
+
+#endif
+
+__HELENOS_DECLS_BEGIN;
+
+extern char32_t str_decode(const char *str, size_t *offset, size_t sz);
+extern char32_t str_decode_reverse(const char *str, size_t *offset, size_t sz);
+extern errno_t chr_encode(char32_t ch, char *str, size_t *offset, size_t sz);
+
+extern size_t str_size(const char *str);
+extern size_t wstr_size(const char32_t *str);
+
+extern size_t str_nsize(const char *str, size_t max_size);
+extern size_t wstr_nsize(const char32_t *str, size_t max_size);
+
+extern size_t str_lsize(const char *str, size_t max_len);
+extern size_t wstr_lsize(const char32_t *str, size_t max_len);
+
+extern size_t str_length(const char *str);
+extern size_t wstr_length(const char32_t *wstr);
+
+extern size_t str_nlength(const char *str, size_t size);
+extern size_t wstr_nlength(const char32_t *str, size_t size);
+
+extern size_t chr_width(char32_t ch);
+extern size_t str_width(const char *str);
+
+extern bool ascii_check(char32_t ch);
+extern bool chr_check(char32_t ch);
+
+extern int str_cmp(const char *s1, const char *s2);
+extern int str_lcmp(const char *s1, const char *s2, size_t max_len);
+extern int str_casecmp(const char *s1, const char *s2);
+extern int str_lcasecmp(const char *s1, const char *s2, size_t max_len);
+
+extern bool str_test_prefix(const char *s, const char *p);
+extern const char *str_suffix(const char *s, size_t prefix_length);
+
+extern void str_cpy(char *dest, size_t size, const char *src);
+extern void str_ncpy(char *dest, size_t size, const char *src, size_t n);
+extern void str_append(char *dest, size_t size, const char *src);
+
+extern errno_t spascii_to_str(char *dest, size_t size, const uint8_t *src, size_t n);
+extern void wstr_to_str(char *dest, size_t size, const char32_t *src);
+extern char *wstr_to_astr(const char32_t *src);
+extern void str_to_wstr(char32_t *dest, size_t dlen, const char *src);
+extern char32_t *str_to_awstr(const char *src);
+extern errno_t utf16_to_str(char *dest, size_t size, const uint16_t *src);
+extern errno_t str_to_utf16(uint16_t *dest, size_t dlen, const char *src);
+extern size_t utf16_wsize(const uint16_t *ustr);
+
+extern char *str_chr(const char *str, char32_t ch);
+extern char *str_rchr(const char *str, char32_t ch);
+extern char *str_str(const char *hs, const char *n);
+
+extern void str_rtrim(char *str, char32_t ch);
+extern void str_ltrim(char *str, char32_t ch);
+
+extern bool wstr_linsert(char32_t *str, char32_t ch, size_t pos, size_t max_pos);
+extern bool wstr_remove(char32_t *str, size_t pos);
+
+extern char *str_dup(const char *src);
+extern char *str_ndup(const char *src, size_t n);
+
+extern char *str_tok(char *, const char *, char **);
+
+extern errno_t str_uint8_t(const char *, const char **, unsigned int, bool,
+    uint8_t *);
+extern errno_t str_uint16_t(const char *, const char **, unsigned int, bool,
+    uint16_t *);
+extern errno_t str_uint32_t(const char *, const char **, unsigned int, bool,
+    uint32_t *);
+extern errno_t str_uint64_t(const char *, const char **, unsigned int, bool,
+    uint64_t *);
+extern errno_t str_size_t(const char *, const char **, unsigned int, bool,
+    size_t *);
+extern errno_t str_int64_t(const char *, const char **, unsigned int, bool,
+    int64_t *);
+
+extern void order_suffix(const uint64_t, uint64_t *, char *);
+extern void bin_order_suffix(const uint64_t, uint64_t *, const char **, bool);
+
+/*
+ * TODO: Get rid of this.
+ */
+
+extern long int strtol(const char *, char **, int);
+extern unsigned long strtoul(const char *, char **, int);
+
+__HELENOS_DECLS_END;
+
+#endif
+
+/** @}
+ */
Index: common/include/str_error.h
===================================================================
--- common/include/str_error.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/include/str_error.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2010 Martin Decky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBC_STRERROR_H_
+#define _LIBC_STRERROR_H_
+
+#include <errno.h>
+
+const char *str_error(errno_t);
+const char *str_error_name(errno_t);
+
+#endif
+
+/** @}
+ */
Index: common/printf/printf_core.c
===================================================================
--- common/printf/printf_core.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/printf/printf_core.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,1674 @@
+/*
+ * Copyright (c) 2001-2004 Jakub Jermar
+ * Copyright (c) 2006 Josef Cejka
+ * Copyright (c) 2009 Martin Decky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/**
+ * @file
+ * @brief Printing functions.
+ */
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <printf_core.h>
+#include <ctype.h>
+#include <str.h>
+#include <assert.h>
+#include <macros.h>
+#include <uchar.h>
+
+/* Disable float support in kernel, because we usually disable floating operations there. */
+#if __STDC_HOSTED__
+#define HAS_FLOAT
+#endif
+
+#ifdef HAS_FLOAT
+#include <double_to_str.h>
+#include <ieee_double.h>
+#endif
+
+/** show prefixes 0x or 0 */
+#define __PRINTF_FLAG_PREFIX       0x00000001
+
+/** show the decimal point even if no fractional digits present */
+#define __PRINTF_FLAG_DECIMALPT    0x00000001
+
+/** signed / unsigned number */
+#define __PRINTF_FLAG_SIGNED       0x00000002
+
+/** print leading zeroes */
+#define __PRINTF_FLAG_ZEROPADDED   0x00000004
+
+/** align to left */
+#define __PRINTF_FLAG_LEFTALIGNED  0x00000010
+
+/** always show + sign */
+#define __PRINTF_FLAG_SHOWPLUS     0x00000020
+
+/** print space instead of plus */
+#define __PRINTF_FLAG_SPACESIGN    0x00000040
+
+/** show big characters */
+#define __PRINTF_FLAG_BIGCHARS     0x00000080
+
+/** number has - sign */
+#define __PRINTF_FLAG_NEGATIVE     0x00000100
+
+/** don't print trailing zeros in the fractional part */
+#define __PRINTF_FLAG_NOFRACZEROS  0x00000200
+
+/**
+ * Buffer big enough for 64-bit number printed in base 2, sign, prefix and 0
+ * to terminate string... (last one is only for better testing end of buffer by
+ * zero-filling subroutine)
+ */
+#define PRINT_NUMBER_BUFFER_SIZE  (64 + 5)
+
+/** Get signed or unsigned integer argument */
+#define PRINTF_GET_INT_ARGUMENT(type, ap, flags) \
+	({ \
+		unsigned type res; \
+		\
+		if ((flags) & __PRINTF_FLAG_SIGNED) { \
+			signed type arg = va_arg((ap), signed type); \
+			\
+			if (arg < 0) { \
+				res = -arg; \
+				(flags) |= __PRINTF_FLAG_NEGATIVE; \
+			} else \
+				res = arg; \
+		} else \
+			res = va_arg((ap), unsigned type); \
+		\
+		res; \
+	})
+
+/** Enumeration of possible arguments types.
+ */
+typedef enum {
+	PrintfQualifierByte = 0,
+	PrintfQualifierShort,
+	PrintfQualifierInt,
+	PrintfQualifierLong,
+	PrintfQualifierLongLong,
+	PrintfQualifierPointer,
+	PrintfQualifierSize,
+	PrintfQualifierMax
+} qualifier_t;
+
+static const char *nullstr = "(NULL)";
+static const char *digits_small = "0123456789abcdef";
+static const char *digits_big = "0123456789ABCDEF";
+static const char invalch = U_SPECIAL;
+
+/** Print one or more characters without adding newline.
+ *
+ * @param buf  Buffer holding characters with size of
+ *             at least size bytes. NULL is not allowed!
+ * @param size Size of the buffer in bytes.
+ * @param ps   Output method and its data.
+ *
+ * @return Number of characters printed.
+ *
+ */
+static int printf_putnchars(const char *buf, size_t size,
+    printf_spec_t *ps)
+{
+	return ps->str_write((void *) buf, size, ps->data);
+}
+
+/** Print one or more wide characters without adding newline.
+ *
+ * @param buf  Buffer holding wide characters with size of
+ *             at least size bytes. NULL is not allowed!
+ * @param size Size of the buffer in bytes.
+ * @param ps   Output method and its data.
+ *
+ * @return Number of wide characters printed.
+ *
+ */
+static int printf_wputnchars(const char32_t *buf, size_t size,
+    printf_spec_t *ps)
+{
+	return ps->wstr_write((void *) buf, size, ps->data);
+}
+
+/** Print string without adding a newline.
+ *
+ * @param str String to print.
+ * @param ps  Write function specification and support data.
+ *
+ * @return Number of characters printed.
+ *
+ */
+static int printf_putstr(const char *str, printf_spec_t *ps)
+{
+	if (str == NULL)
+		return printf_putnchars(nullstr, str_size(nullstr), ps);
+
+	return ps->str_write((void *) str, str_size(str), ps->data);
+}
+
+/** Print one ASCII character.
+ *
+ * @param c  ASCII character to be printed.
+ * @param ps Output method.
+ *
+ * @return Number of characters printed.
+ *
+ */
+static int printf_putchar(const char ch, printf_spec_t *ps)
+{
+	if (!ascii_check(ch))
+		return ps->str_write((void *) &invalch, 1, ps->data);
+
+	return ps->str_write(&ch, 1, ps->data);
+}
+
+/** Print one wide character.
+ *
+ * @param c  Wide character to be printed.
+ * @param ps Output method.
+ *
+ * @return Number of characters printed.
+ *
+ */
+static int printf_putuchar(const char32_t ch, printf_spec_t *ps)
+{
+	if (!chr_check(ch))
+		return ps->str_write((void *) &invalch, 1, ps->data);
+
+	return ps->wstr_write(&ch, sizeof(char32_t), ps->data);
+}
+
+/** Print one formatted ASCII character.
+ *
+ * @param ch    Character to print.
+ * @param width Width modifier.
+ * @param flags Flags that change the way the character is printed.
+ *
+ * @return Number of characters printed, negative value on failure.
+ *
+ */
+static int print_char(const char ch, int width, uint32_t flags, printf_spec_t *ps)
+{
+	size_t counter = 0;
+	if (!(flags & __PRINTF_FLAG_LEFTALIGNED)) {
+		while (--width > 0) {
+			/*
+			 * One space is consumed by the character itself, hence
+			 * the predecrement.
+			 */
+			if (printf_putchar(' ', ps) > 0)
+				counter++;
+		}
+	}
+
+	if (printf_putchar(ch, ps) > 0)
+		counter++;
+
+	while (--width > 0) {
+		/*
+		 * One space is consumed by the character itself, hence
+		 * the predecrement.
+		 */
+		if (printf_putchar(' ', ps) > 0)
+			counter++;
+	}
+
+	return (int) (counter);
+}
+
+/** Print one formatted wide character.
+ *
+ * @param ch    Character to print.
+ * @param width Width modifier.
+ * @param flags Flags that change the way the character is printed.
+ *
+ * @return Number of characters printed, negative value on failure.
+ *
+ */
+static int print_wchar(const char32_t ch, int width, uint32_t flags, printf_spec_t *ps)
+{
+	size_t counter = 0;
+	if (!(flags & __PRINTF_FLAG_LEFTALIGNED)) {
+		while (--width > 0) {
+			/*
+			 * One space is consumed by the character itself, hence
+			 * the predecrement.
+			 */
+			if (printf_putchar(' ', ps) > 0)
+				counter++;
+		}
+	}
+
+	if (printf_putuchar(ch, ps) > 0)
+		counter++;
+
+	while (--width > 0) {
+		/*
+		 * One space is consumed by the character itself, hence
+		 * the predecrement.
+		 */
+		if (printf_putchar(' ', ps) > 0)
+			counter++;
+	}
+
+	return (int) (counter);
+}
+
+/** Print string.
+ *
+ * @param str       String to be printed.
+ * @param width     Width modifier.
+ * @param precision Precision modifier.
+ * @param flags     Flags that modify the way the string is printed.
+ *
+ * @return Number of characters printed, negative value on failure.
+ */
+static int print_str(char *str, int width, unsigned int precision,
+    uint32_t flags, printf_spec_t *ps)
+{
+	if (str == NULL)
+		return printf_putstr(nullstr, ps);
+
+	size_t strw = str_length(str);
+
+	/* Precision unspecified - print everything. */
+	if ((precision == 0) || (precision > strw))
+		precision = strw;
+
+	/* Left padding */
+	size_t counter = 0;
+	width -= precision;
+	if (!(flags & __PRINTF_FLAG_LEFTALIGNED)) {
+		while (width-- > 0) {
+			if (printf_putchar(' ', ps) == 1)
+				counter++;
+		}
+	}
+
+	/* Part of @a str fitting into the alloted space. */
+	int retval;
+	size_t size = str_lsize(str, precision);
+	if ((retval = printf_putnchars(str, size, ps)) < 0)
+		return -counter;
+
+	counter += retval;
+
+	/* Right padding */
+	while (width-- > 0) {
+		if (printf_putchar(' ', ps) == 1)
+			counter++;
+	}
+
+	return ((int) counter);
+
+}
+
+/** Print wide string.
+ *
+ * @param str       Wide string to be printed.
+ * @param width     Width modifier.
+ * @param precision Precision modifier.
+ * @param flags     Flags that modify the way the string is printed.
+ *
+ * @return Number of wide characters printed, negative value on failure.
+ */
+static int print_wstr(char32_t *str, int width, unsigned int precision,
+    uint32_t flags, printf_spec_t *ps)
+{
+	if (str == NULL)
+		return printf_putstr(nullstr, ps);
+
+	size_t strw = wstr_length(str);
+
+	/* Precision not specified - print everything. */
+	if ((precision == 0) || (precision > strw))
+		precision = strw;
+
+	/* Left padding */
+	size_t counter = 0;
+	width -= precision;
+	if (!(flags & __PRINTF_FLAG_LEFTALIGNED)) {
+		while (width-- > 0) {
+			if (printf_putchar(' ', ps) == 1)
+				counter++;
+		}
+	}
+
+	/* Part of @a wstr fitting into the alloted space. */
+	int retval;
+	size_t size = wstr_lsize(str, precision);
+	if ((retval = printf_wputnchars(str, size, ps)) < 0)
+		return -counter;
+
+	counter += retval;
+
+	/* Right padding */
+	while (width-- > 0) {
+		if (printf_putchar(' ', ps) == 1)
+			counter++;
+	}
+
+	return ((int) counter);
+}
+
+/** Print a number in a given base.
+ *
+ * Print significant digits of a number in given base.
+ *
+ * @param num       Number to print.
+ * @param width     Width modifier.
+ * @param precision Precision modifier.
+ * @param base      Base to print the number in (must be between 2 and 16).
+ * @param flags     Flags that modify the way the number is printed.
+ *
+ * @return Number of characters printed.
+ *
+ */
+static int print_number(uint64_t num, int width, int precision, int base,
+    uint32_t flags, printf_spec_t *ps)
+{
+	/* Precision not specified. */
+	if (precision < 0) {
+		precision = 0;
+	}
+
+	const char *digits;
+	if (flags & __PRINTF_FLAG_BIGCHARS)
+		digits = digits_big;
+	else
+		digits = digits_small;
+
+	char data[PRINT_NUMBER_BUFFER_SIZE];
+	char *ptr = &data[PRINT_NUMBER_BUFFER_SIZE - 1];
+
+	/* Size of number with all prefixes and signs */
+	int size = 0;
+
+	/* Put zero at end of string */
+	*ptr-- = 0;
+
+	if (num == 0) {
+		*ptr-- = '0';
+		size++;
+	} else {
+		do {
+			*ptr-- = digits[num % base];
+			size++;
+		} while (num /= base);
+	}
+
+	/* Size of plain number */
+	int number_size = size;
+
+	/*
+	 * Collect the sum of all prefixes/signs/etc. to calculate padding and
+	 * leading zeroes.
+	 */
+	if (flags & __PRINTF_FLAG_PREFIX) {
+		switch (base) {
+		case 2:
+			/* Binary formating is not standard, but usefull */
+			size += 2;
+			break;
+		case 8:
+			size++;
+			break;
+		case 16:
+			size += 2;
+			break;
+		}
+	}
+
+	char sgn = 0;
+	if (flags & __PRINTF_FLAG_SIGNED) {
+		if (flags & __PRINTF_FLAG_NEGATIVE) {
+			sgn = '-';
+			size++;
+		} else if (flags & __PRINTF_FLAG_SHOWPLUS) {
+			sgn = '+';
+			size++;
+		} else if (flags & __PRINTF_FLAG_SPACESIGN) {
+			sgn = ' ';
+			size++;
+		}
+	}
+
+	if (flags & __PRINTF_FLAG_LEFTALIGNED)
+		flags &= ~__PRINTF_FLAG_ZEROPADDED;
+
+	/*
+	 * If the number is left-aligned or precision is specified then
+	 * padding with zeros is ignored.
+	 */
+	if (flags & __PRINTF_FLAG_ZEROPADDED) {
+		if ((precision == 0) && (width > size))
+			precision = width - size + number_size;
+	}
+
+	/* Print leading spaces */
+	if (number_size > precision) {
+		/* Print the whole number, not only a part */
+		precision = number_size;
+	}
+
+	width -= precision + size - number_size;
+	size_t counter = 0;
+
+	if (!(flags & __PRINTF_FLAG_LEFTALIGNED)) {
+		while (width-- > 0) {
+			if (printf_putchar(' ', ps) == 1)
+				counter++;
+		}
+	}
+
+	/* Print sign */
+	if (sgn) {
+		if (printf_putchar(sgn, ps) == 1)
+			counter++;
+	}
+
+	/* Print prefix */
+	if (flags & __PRINTF_FLAG_PREFIX) {
+		switch (base) {
+		case 2:
+			/* Binary formating is not standard, but usefull */
+			if (printf_putchar('0', ps) == 1)
+				counter++;
+			if (flags & __PRINTF_FLAG_BIGCHARS) {
+				if (printf_putchar('B', ps) == 1)
+					counter++;
+			} else {
+				if (printf_putchar('b', ps) == 1)
+					counter++;
+			}
+			break;
+		case 8:
+			if (printf_putchar('o', ps) == 1)
+				counter++;
+			break;
+		case 16:
+			if (printf_putchar('0', ps) == 1)
+				counter++;
+			if (flags & __PRINTF_FLAG_BIGCHARS) {
+				if (printf_putchar('X', ps) == 1)
+					counter++;
+			} else {
+				if (printf_putchar('x', ps) == 1)
+					counter++;
+			}
+			break;
+		}
+	}
+
+	/* Print leading zeroes */
+	precision -= number_size;
+	while (precision-- > 0) {
+		if (printf_putchar('0', ps) == 1)
+			counter++;
+	}
+
+	/* Print the number itself */
+	int retval;
+	if ((retval = printf_putstr(++ptr, ps)) > 0)
+		counter += retval;
+
+	/* Print trailing spaces */
+
+	while (width-- > 0) {
+		if (printf_putchar(' ', ps) == 1)
+			counter++;
+	}
+
+	return ((int) counter);
+}
+
+#ifdef HAS_FLOAT
+
+/** Unformatted double number string representation. */
+typedef struct {
+	/** Buffer with len digits, no sign or leading zeros. */
+	char *str;
+	/** Number of digits in str. */
+	int len;
+	/** Decimal exponent, ie number = str * 10^dec_exp */
+	int dec_exp;
+	/** True if negative. */
+	bool neg;
+} double_str_t;
+
+/** Returns the sign character or 0 if no sign should be printed. */
+static int get_sign_char(bool negative, uint32_t flags)
+{
+	if (negative) {
+		return '-';
+	} else if (flags & __PRINTF_FLAG_SHOWPLUS) {
+		return '+';
+	} else if (flags & __PRINTF_FLAG_SPACESIGN) {
+		return ' ';
+	} else {
+		return 0;
+	}
+}
+
+/** Prints count times character ch. */
+static int print_padding(char ch, int count, printf_spec_t *ps)
+{
+	for (int i = 0; i < count; ++i) {
+		if (ps->str_write(&ch, 1, ps->data) < 0) {
+			return -1;
+		}
+	}
+
+	return count;
+}
+
+/** Prints a special double (ie NaN, infinity) padded to width characters. */
+static int print_special(ieee_double_t val, int width, uint32_t flags,
+    printf_spec_t *ps)
+{
+	assert(val.is_special);
+
+	char sign = get_sign_char(val.is_negative, flags);
+
+	const int str_len = 3;
+	const char *str;
+
+	if (flags & __PRINTF_FLAG_BIGCHARS) {
+		str = val.is_infinity ? "INF" : "NAN";
+	} else {
+		str = val.is_infinity ? "inf" : "nan";
+	}
+
+	int padding_len = max(0, width - ((sign ? 1 : 0) + str_len));
+
+	int counter = 0;
+	int ret;
+
+	/* Leading padding. */
+	if (!(flags & __PRINTF_FLAG_LEFTALIGNED)) {
+		if ((ret = print_padding(' ', padding_len, ps)) < 0)
+			return -1;
+
+		counter += ret;
+	}
+
+	if (sign) {
+		if ((ret = ps->str_write(&sign, 1, ps->data)) < 0)
+			return -1;
+
+		counter += ret;
+	}
+
+	if ((ret = ps->str_write(str, str_len, ps->data)) < 0)
+		return -1;
+
+	counter += ret;
+
+	/* Trailing padding. */
+	if (flags & __PRINTF_FLAG_LEFTALIGNED) {
+		if ((ret = print_padding(' ', padding_len, ps)) < 0)
+			return -1;
+
+		counter += ret;
+	}
+
+	return counter;
+}
+
+/** Trims trailing zeros but leaves a single "0" intact. */
+static void fp_trim_trailing_zeros(char *buf, int *len, int *dec_exp)
+{
+	/* Cut the zero off by adjusting the exponent. */
+	while (2 <= *len && '0' == buf[*len - 1]) {
+		--*len;
+		++*dec_exp;
+	}
+}
+
+/** Textually round up the last digit thereby eliminating it. */
+static void fp_round_up(char *buf, int *len, int *dec_exp)
+{
+	assert(1 <= *len);
+
+	char *last_digit = &buf[*len - 1];
+
+	int carry = ('5' <= *last_digit);
+
+	/* Cut the digit off by adjusting the exponent. */
+	--*len;
+	++*dec_exp;
+	--last_digit;
+
+	if (carry) {
+		/* Skip all the digits to cut off/round to zero. */
+		while (buf <= last_digit && '9' == *last_digit) {
+			--last_digit;
+		}
+
+		/* last_digit points to the last digit to round but not '9' */
+		if (buf <= last_digit) {
+			*last_digit += 1;
+			int new_len = last_digit - buf + 1;
+			*dec_exp += *len - new_len;
+			*len = new_len;
+		} else {
+			/* All len digits rounded to 0. */
+			buf[0] = '1';
+			*dec_exp += *len;
+			*len = 1;
+		}
+	} else {
+		/* The only digit was rounded to 0. */
+		if (last_digit < buf) {
+			buf[0] = '0';
+			*dec_exp = 0;
+			*len = 1;
+		}
+	}
+}
+
+/** Format and print the double string repressentation according
+ *  to the %f specifier.
+ */
+static int print_double_str_fixed(double_str_t *val_str, int precision, int width,
+    uint32_t flags, printf_spec_t *ps)
+{
+	int len = val_str->len;
+	char *buf = val_str->str;
+	int dec_exp = val_str->dec_exp;
+
+	assert(0 < len);
+	assert(0 <= precision);
+	assert(0 <= dec_exp || -dec_exp <= precision);
+
+	/* Number of integral digits to print (at least leading zero). */
+	int int_len = max(1, len + dec_exp);
+
+	char sign = get_sign_char(val_str->neg, flags);
+
+	/* Fractional portion lengths. */
+	int last_frac_signif_pos = max(0, -dec_exp);
+	int leading_frac_zeros = max(0, last_frac_signif_pos - len);
+	int signif_frac_figs = min(last_frac_signif_pos, len);
+	int trailing_frac_zeros = precision - last_frac_signif_pos;
+	char *buf_frac = buf + len - signif_frac_figs;
+
+	if (flags & __PRINTF_FLAG_NOFRACZEROS) {
+		trailing_frac_zeros = 0;
+	}
+
+	int frac_len = leading_frac_zeros + signif_frac_figs + trailing_frac_zeros;
+
+	bool has_decimal_pt = (0 < frac_len) || (flags & __PRINTF_FLAG_DECIMALPT);
+
+	/* Number of non-padding chars to print. */
+	int num_len = (sign ? 1 : 0) + int_len + (has_decimal_pt ? 1 : 0) + frac_len;
+
+	int padding_len = max(0, width - num_len);
+	int ret = 0;
+	int counter = 0;
+
+	/* Leading padding and sign. */
+
+	if (!(flags & (__PRINTF_FLAG_LEFTALIGNED | __PRINTF_FLAG_ZEROPADDED))) {
+		if ((ret = print_padding(' ', padding_len, ps)) < 0)
+			return -1;
+
+		counter += ret;
+	}
+
+	if (sign) {
+		if ((ret = ps->str_write(&sign, 1, ps->data)) < 0)
+			return -1;
+
+		counter += ret;
+	}
+
+	if (flags & __PRINTF_FLAG_ZEROPADDED) {
+		if ((ret = print_padding('0', padding_len, ps)) < 0)
+			return -1;
+
+		counter += ret;
+	}
+
+	/* Print the intergral part of the buffer. */
+
+	int buf_int_len = min(len, len + dec_exp);
+
+	if (0 < buf_int_len) {
+		if ((ret = ps->str_write(buf, buf_int_len, ps->data)) < 0)
+			return -1;
+
+		counter += ret;
+
+		/* Print trailing zeros of the integral part of the number. */
+		if ((ret = print_padding('0', int_len - buf_int_len, ps)) < 0)
+			return -1;
+	} else {
+		/* Single leading integer 0. */
+		char ch = '0';
+		if ((ret = ps->str_write(&ch, 1, ps->data)) < 0)
+			return -1;
+	}
+
+	counter += ret;
+
+	/* Print the decimal point and the fractional part. */
+	if (has_decimal_pt) {
+		char ch = '.';
+
+		if ((ret = ps->str_write(&ch, 1, ps->data)) < 0)
+			return -1;
+
+		counter += ret;
+
+		/* Print leading zeros of the fractional part of the number. */
+		if ((ret = print_padding('0', leading_frac_zeros, ps)) < 0)
+			return -1;
+
+		counter += ret;
+
+		/* Print significant digits of the fractional part of the number. */
+		if (0 < signif_frac_figs) {
+			if ((ret = ps->str_write(buf_frac, signif_frac_figs, ps->data)) < 0)
+				return -1;
+
+			counter += ret;
+		}
+
+		/* Print trailing zeros of the fractional part of the number. */
+		if ((ret = print_padding('0', trailing_frac_zeros, ps)) < 0)
+			return -1;
+
+		counter += ret;
+	}
+
+	/* Trailing padding. */
+	if (flags & __PRINTF_FLAG_LEFTALIGNED) {
+		if ((ret = print_padding(' ', padding_len, ps)) < 0)
+			return -1;
+
+		counter += ret;
+	}
+
+	return counter;
+}
+
+/** Convert, format and print a double according to the %f specifier.
+ *
+ * @param g     Double to print.
+ * @param precision Number of fractional digits to print. If 0 no
+ *              decimal point will be printed unless the flag
+ *              __PRINTF_FLAG_DECIMALPT is specified.
+ * @param width Minimum number of characters to display. Pads
+ *              with '0' or ' ' depending on the set flags;
+ * @param flags Printf flags.
+ * @param ps    Printing functions.
+ *
+ * @return The number of characters printed; negative on failure.
+ */
+static int print_double_fixed(double g, int precision, int width, uint32_t flags,
+    printf_spec_t *ps)
+{
+	if (flags & __PRINTF_FLAG_LEFTALIGNED) {
+		flags &= ~__PRINTF_FLAG_ZEROPADDED;
+	}
+
+	if (flags & __PRINTF_FLAG_DECIMALPT) {
+		flags &= ~__PRINTF_FLAG_NOFRACZEROS;
+	}
+
+	ieee_double_t val = extract_ieee_double(g);
+
+	if (val.is_special) {
+		return print_special(val, width, flags, ps);
+	}
+
+	char buf[MAX_DOUBLE_STR_BUF_SIZE];
+	const size_t buf_size = MAX_DOUBLE_STR_BUF_SIZE;
+	double_str_t val_str;
+
+	val_str.str = buf;
+	val_str.neg = val.is_negative;
+
+	if (0 <= precision) {
+		/*
+		 * Request one more digit so we can round the result. The last
+		 * digit it returns may have an error of at most +/- 1.
+		 */
+		val_str.len = double_to_fixed_str(val, -1, precision + 1, buf, buf_size,
+		    &val_str.dec_exp);
+
+		/*
+		 * Round using the last digit to produce precision fractional digits.
+		 * If less than precision+1 fractional digits were output the last
+		 * digit is definitely inaccurate so also round to get rid of it.
+		 */
+		fp_round_up(buf, &val_str.len, &val_str.dec_exp);
+
+		/* Rounding could have introduced trailing zeros. */
+		if (flags & __PRINTF_FLAG_NOFRACZEROS) {
+			fp_trim_trailing_zeros(buf, &val_str.len, &val_str.dec_exp);
+		}
+	} else {
+		/* Let the implementation figure out the proper precision. */
+		val_str.len = double_to_short_str(val, buf, buf_size, &val_str.dec_exp);
+
+		/* Precision needed for the last significant digit. */
+		precision = max(0, -val_str.dec_exp);
+	}
+
+	return print_double_str_fixed(&val_str, precision, width, flags, ps);
+}
+
+/** Prints the decimal exponent part of a %e specifier formatted number. */
+static int print_exponent(int exp_val, uint32_t flags, printf_spec_t *ps)
+{
+	int counter = 0;
+	int ret;
+
+	char exp_ch = (flags & __PRINTF_FLAG_BIGCHARS) ? 'E' : 'e';
+
+	if ((ret = ps->str_write(&exp_ch, 1, ps->data)) < 0)
+		return -1;
+
+	counter += ret;
+
+	char exp_sign = (exp_val < 0) ? '-' : '+';
+
+	if ((ret = ps->str_write(&exp_sign, 1, ps->data)) < 0)
+		return -1;
+
+	counter += ret;
+
+	/* Print the exponent. */
+	exp_val = abs(exp_val);
+
+	char exp_str[4] = { 0 };
+
+	exp_str[0] = '0' + exp_val / 100;
+	exp_str[1] = '0' + (exp_val % 100) / 10;
+	exp_str[2] = '0' + (exp_val % 10);
+
+	int exp_len = (exp_str[0] == '0') ? 2 : 3;
+	const char *exp_str_start = &exp_str[3] - exp_len;
+
+	if ((ret = ps->str_write(exp_str_start, exp_len, ps->data)) < 0)
+		return -1;
+
+	counter += ret;
+
+	return counter;
+}
+
+/** Format and print the double string repressentation according
+ *  to the %e specifier.
+ */
+static int print_double_str_scient(double_str_t *val_str, int precision,
+    int width, uint32_t flags, printf_spec_t *ps)
+{
+	int len = val_str->len;
+	int dec_exp = val_str->dec_exp;
+	char *buf  = val_str->str;
+
+	assert(0 < len);
+
+	char sign = get_sign_char(val_str->neg, flags);
+	bool has_decimal_pt = (0 < precision) || (flags & __PRINTF_FLAG_DECIMALPT);
+	int dec_pt_len = has_decimal_pt ? 1 : 0;
+
+	/* Fractional part lengths. */
+	int signif_frac_figs = len - 1;
+	int trailing_frac_zeros = precision - signif_frac_figs;
+
+	if (flags & __PRINTF_FLAG_NOFRACZEROS) {
+		trailing_frac_zeros = 0;
+	}
+
+	int frac_len = signif_frac_figs + trailing_frac_zeros;
+
+	int exp_val = dec_exp + len - 1;
+	/* Account for exponent sign and 'e'; minimum 2 digits. */
+	int exp_len = 2 + (abs(exp_val) >= 100 ? 3 : 2);
+
+	/* Number of non-padding chars to print. */
+	int num_len = (sign ? 1 : 0) + 1 + dec_pt_len + frac_len + exp_len;
+
+	int padding_len = max(0, width - num_len);
+	int ret = 0;
+	int counter = 0;
+
+	if (!(flags & (__PRINTF_FLAG_LEFTALIGNED | __PRINTF_FLAG_ZEROPADDED))) {
+		if ((ret = print_padding(' ', padding_len, ps)) < 0)
+			return -1;
+
+		counter += ret;
+	}
+
+	if (sign) {
+		if ((ret = ps->str_write(&sign, 1, ps->data)) < 0)
+			return -1;
+
+		counter += ret;
+	}
+
+	if (flags & __PRINTF_FLAG_ZEROPADDED) {
+		if ((ret = print_padding('0', padding_len, ps)) < 0)
+			return -1;
+
+		counter += ret;
+	}
+
+	/* Single leading integer. */
+	if ((ret = ps->str_write(buf, 1, ps->data)) < 0)
+		return -1;
+
+	counter += ret;
+
+	/* Print the decimal point and the fractional part. */
+	if (has_decimal_pt) {
+		char ch = '.';
+
+		if ((ret = ps->str_write(&ch, 1, ps->data)) < 0)
+			return -1;
+
+		counter += ret;
+
+		/* Print significant digits of the fractional part of the number. */
+		if (0 < signif_frac_figs) {
+			if ((ret = ps->str_write(buf + 1, signif_frac_figs, ps->data)) < 0)
+				return -1;
+
+			counter += ret;
+		}
+
+		/* Print trailing zeros of the fractional part of the number. */
+		if ((ret = print_padding('0', trailing_frac_zeros, ps)) < 0)
+			return -1;
+
+		counter += ret;
+	}
+
+	/* Print the exponent. */
+	if ((ret = print_exponent(exp_val, flags, ps)) < 0)
+		return -1;
+
+	counter += ret;
+
+	if (flags & __PRINTF_FLAG_LEFTALIGNED) {
+		if ((ret = print_padding(' ', padding_len, ps)) < 0)
+			return -1;
+
+		counter += ret;
+	}
+
+	return counter;
+}
+
+/** Convert, format and print a double according to the %e specifier.
+ *
+ * Note that if g is large, the output may be huge (3e100 prints
+ * with at least 100 digits).
+ *
+ * %e style: [-]d.dddde+dd
+ *  left-justified:  [-]d.dddde+dd[space_pad]
+ *  right-justified: [space_pad][-][zero_pad]d.dddde+dd
+ *
+ * @param g     Double to print.
+ * @param precision Number of fractional digits to print, ie
+ *              precision + 1 significant digits to display. If 0 no
+ *              decimal point will be printed unless the flag
+ *              __PRINTF_FLAG_DECIMALPT is specified. If negative
+ *              the shortest accurate number will be printed.
+ * @param width Minimum number of characters to display. Pads
+ *              with '0' or ' ' depending on the set flags;
+ * @param flags Printf flags.
+ * @param ps    Printing functions.
+ *
+ * @return The number of characters printed; negative on failure.
+ */
+static int print_double_scientific(double g, int precision, int width,
+    uint32_t flags, printf_spec_t *ps)
+{
+	if (flags & __PRINTF_FLAG_LEFTALIGNED) {
+		flags &= ~__PRINTF_FLAG_ZEROPADDED;
+	}
+
+	ieee_double_t val = extract_ieee_double(g);
+
+	if (val.is_special) {
+		return print_special(val, width, flags, ps);
+	}
+
+	char buf[MAX_DOUBLE_STR_BUF_SIZE];
+	const size_t buf_size = MAX_DOUBLE_STR_BUF_SIZE;
+	double_str_t val_str;
+
+	val_str.str = buf;
+	val_str.neg = val.is_negative;
+
+	if (0 <= precision) {
+		/*
+		 * Request one more digit (in addition to the leading integer)
+		 * so we can round the result. The last digit it returns may
+		 * have an error of at most +/- 1.
+		 */
+		val_str.len = double_to_fixed_str(val, precision + 2, -1, buf, buf_size,
+		    &val_str.dec_exp);
+
+		/*
+		 * Round the extra digit to produce precision+1 significant digits.
+		 * If less than precision+2 significant digits were returned the last
+		 * digit is definitely inaccurate so also round to get rid of it.
+		 */
+		fp_round_up(buf, &val_str.len, &val_str.dec_exp);
+
+		/* Rounding could have introduced trailing zeros. */
+		if (flags & __PRINTF_FLAG_NOFRACZEROS) {
+			fp_trim_trailing_zeros(buf, &val_str.len, &val_str.dec_exp);
+		}
+	} else {
+		/* Let the implementation figure out the proper precision. */
+		val_str.len = double_to_short_str(val, buf, buf_size, &val_str.dec_exp);
+
+		/* Use all produced digits. */
+		precision = val_str.len - 1;
+	}
+
+	return print_double_str_scient(&val_str, precision, width, flags, ps);
+}
+
+/** Convert, format and print a double according to the %g specifier.
+ *
+ * %g style chooses between %f and %e.
+ *
+ * @param g     Double to print.
+ * @param precision Number of significant digits to display; excluding
+ *              any leading zeros from this count. If negative
+ *              the shortest accurate number will be printed.
+ * @param width Minimum number of characters to display. Pads
+ *              with '0' or ' ' depending on the set flags;
+ * @param flags Printf flags.
+ * @param ps    Printing functions.
+ *
+ * @return The number of characters printed; negative on failure.
+ */
+static int print_double_generic(double g, int precision, int width,
+    uint32_t flags, printf_spec_t *ps)
+{
+	ieee_double_t val = extract_ieee_double(g);
+
+	if (val.is_special) {
+		return print_special(val, width, flags, ps);
+	}
+
+	char buf[MAX_DOUBLE_STR_BUF_SIZE];
+	const size_t buf_size = MAX_DOUBLE_STR_BUF_SIZE;
+	int dec_exp;
+	int len;
+
+	/* Honor the user requested number of significant digits. */
+	if (0 <= precision) {
+		/*
+		 * Do a quick and dirty conversion of a single digit to determine
+		 * the decimal exponent.
+		 */
+		len = double_to_fixed_str(val, 1, -1, buf, buf_size, &dec_exp);
+		assert(0 < len);
+
+		precision = max(1, precision);
+
+		if (-4 <= dec_exp && dec_exp < precision) {
+			precision = precision - (dec_exp + 1);
+			return print_double_fixed(g, precision, width,
+			    flags | __PRINTF_FLAG_NOFRACZEROS, ps);
+		} else {
+			--precision;
+			return print_double_scientific(g, precision, width,
+			    flags | __PRINTF_FLAG_NOFRACZEROS, ps);
+		}
+	} else {
+		/* Convert to get the decimal exponent and digit count. */
+		len = double_to_short_str(val, buf, buf_size, &dec_exp);
+		assert(0 < len);
+
+		if (flags & __PRINTF_FLAG_LEFTALIGNED) {
+			flags &= ~__PRINTF_FLAG_ZEROPADDED;
+		}
+
+		double_str_t val_str;
+		val_str.str = buf;
+		val_str.len = len;
+		val_str.neg = val.is_negative;
+		val_str.dec_exp = dec_exp;
+
+		int first_digit_pos = len + dec_exp;
+		int last_digit_pos = dec_exp;
+
+		/* The whole number (15 digits max) fits between dec places 15 .. -6 */
+		if (len <= 15 && -6 <= last_digit_pos && first_digit_pos <= 15) {
+			/* Precision needed for the last significant digit. */
+			precision = max(0, -val_str.dec_exp);
+			return print_double_str_fixed(&val_str, precision, width, flags, ps);
+		} else {
+			/* Use all produced digits. */
+			precision = val_str.len - 1;
+			return print_double_str_scient(&val_str, precision, width, flags, ps);
+		}
+	}
+}
+
+/** Convert, format and print a double according to the specifier.
+ *
+ * Depending on the specifier it prints the double using the styles
+ * %g, %f or %e by means of print_double_generic(), print_double_fixed(),
+ * print_double_scientific().
+ *
+ * @param g     Double to print.
+ * @param spec  Specifier of the style to print in; one of: 'g','G','f','F',
+ *              'e','E'.
+ * @param precision Number of fractional digits to display. If negative
+ *              the shortest accurate number will be printed for style %g;
+ *              negative precision defaults to 6 for styles %f, %e.
+ * @param width Minimum number of characters to display. Pads
+ *              with '0' or ' ' depending on the set flags;
+ * @param flags Printf flags.
+ * @param ps    Printing functions.
+ *
+ * @return The number of characters printed; negative on failure.
+ */
+static int print_double(double g, char spec, int precision, int width,
+    uint32_t flags, printf_spec_t *ps)
+{
+	switch (spec) {
+	case 'F':
+		flags |= __PRINTF_FLAG_BIGCHARS;
+		/* Fallthrough */
+	case 'f':
+		precision = (precision < 0) ? 6 : precision;
+		return print_double_fixed(g, precision, width, flags, ps);
+
+	case 'E':
+		flags |= __PRINTF_FLAG_BIGCHARS;
+		/* Fallthrough */
+	case 'e':
+		precision = (precision < 0) ? 6 : precision;
+		return print_double_scientific(g, precision, width, flags, ps);
+
+	case 'G':
+		flags |= __PRINTF_FLAG_BIGCHARS;
+		/* Fallthrough */
+	case 'g':
+		return print_double_generic(g, precision, width, flags, ps);
+
+	default:
+		assert(false);
+		return -1;
+	}
+}
+
+#endif
+
+/** Print formatted string.
+ *
+ * Print string formatted according to the fmt parameter and variadic arguments.
+ * Each formatting directive must have the following form:
+ *
+ *  \% [ FLAGS ] [ WIDTH ] [ .PRECISION ] [ TYPE ] CONVERSION
+ *
+ * FLAGS:@n
+ *  - "#" Force to print prefix. For \%o conversion, the prefix is 0, for
+ *        \%x and \%X prefixes are 0x and 0X and for conversion \%b the
+ *        prefix is 0b.
+ *
+ *  - "-" Align to left.
+ *
+ *  - "+" Print positive sign just as negative.
+ *
+ *  - " " If the printed number is positive and "+" flag is not set,
+ *        print space in place of sign.
+ *
+ *  - "0" Print 0 as padding instead of spaces. Zeroes are placed between
+ *        sign and the rest of the number. This flag is ignored if "-"
+ *        flag is specified.
+ *
+ * WIDTH:@n
+ *  - Specify the minimal width of a printed argument. If it is bigger,
+ *    width is ignored. If width is specified with a "*" character instead of
+ *    number, width is taken from parameter list. And integer parameter is
+ *    expected before parameter for processed conversion specification. If
+ *    this value is negative its absolute value is taken and the "-" flag is
+ *    set.
+ *
+ * PRECISION:@n
+ *  - Value precision. For numbers it specifies minimum valid numbers.
+ *    Smaller numbers are printed with leading zeroes. Bigger numbers are not
+ *    affected. Strings with more than precision characters are cut off. Just
+ *    as with width, an "*" can be used used instead of a number. An integer
+ *    value is then expected in parameters. When both width and precision are
+ *    specified using "*", the first parameter is used for width and the
+ *    second one for precision.
+ *
+ * TYPE:@n
+ *  - "hh" Signed or unsigned char.@n
+ *  - "h"  Signed or unsigned short.@n
+ *  - ""   Signed or unsigned int (default value).@n
+ *  - "l"  Signed or unsigned long int.@n
+ *         If conversion is "c", the character is wint_t (wide character).@n
+ *         If conversion is "s", the string is char32_t * (wide string).@n
+ *  - "ll" Signed or unsigned long long int.@n
+ *  - "z"  Signed or unsigned ssize_t or site_t.@n
+ *
+ * CONVERSION:@n
+ *  - % Print percentile character itself.
+ *
+ *  - c Print single character. The character is expected to be plain
+ *      ASCII (e.g. only values 0 .. 127 are valid).@n
+ *      If type is "l", then the character is expected to be wide character
+ *      (e.g. values 0 .. 0x10ffff are valid).
+ *
+ *  - s Print zero terminated string. If a NULL value is passed as
+ *      value, "(NULL)" is printed instead.@n
+ *      If type is "l", then the string is expected to be wide string.
+ *
+ *  - P, p Print value of a pointer. Void * value is expected and it is
+ *         printed in hexadecimal notation with prefix (as with
+ *         \%#0.8X / \%#0.8x for 32-bit or \%#0.16lX / \%#0.16lx for 64-bit
+ *         long pointers).
+ *
+ *  - b Print value as unsigned binary number. Prefix is not printed by
+ *      default. (Nonstandard extension.)
+ *
+ *  - o Print value as unsigned octal number. Prefix is not printed by
+ *      default.
+ *
+ *  - d, i Print signed decimal number. There is no difference between d
+ *         and i conversion.
+ *
+ *  - u Print unsigned decimal number.
+ *
+ *  - X, x Print hexadecimal number with upper- or lower-case. Prefix is
+ *         not printed by default.
+ *
+ * All other characters from fmt except the formatting directives are printed
+ * verbatim.
+ *
+ * @param fmt Format NULL-terminated string.
+ *
+ * @return Number of characters printed, negative value on failure.
+ *
+ */
+int printf_core(const char *fmt, printf_spec_t *ps, va_list ap)
+{
+	size_t i;        /* Index of the currently processed character from fmt */
+	size_t nxt = 0;  /* Index of the next character from fmt */
+	size_t j = 0;    /* Index to the first not printed nonformating character */
+
+	size_t counter = 0;   /* Number of characters printed */
+	int retval;           /* Return values from nested functions */
+
+	while (true) {
+		i = nxt;
+		char32_t uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
+
+		if (uc == 0)
+			break;
+
+		/* Control character */
+		if (uc == '%') {
+			/* Print common characters if any processed */
+			if (i > j) {
+				if ((retval = printf_putnchars(&fmt[j], i - j, ps)) < 0) {
+					/* Error */
+					counter = -counter;
+					goto out;
+				}
+				counter += retval;
+			}
+
+			j = i;
+
+			/* Parse modifiers */
+			uint32_t flags = 0;
+			bool end = false;
+
+			do {
+				i = nxt;
+				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
+				switch (uc) {
+				case '#':
+					flags |= __PRINTF_FLAG_PREFIX;
+					flags |= __PRINTF_FLAG_DECIMALPT;
+					break;
+				case '-':
+					flags |= __PRINTF_FLAG_LEFTALIGNED;
+					break;
+				case '+':
+					flags |= __PRINTF_FLAG_SHOWPLUS;
+					break;
+				case ' ':
+					flags |= __PRINTF_FLAG_SPACESIGN;
+					break;
+				case '0':
+					flags |= __PRINTF_FLAG_ZEROPADDED;
+					break;
+				default:
+					end = true;
+				}
+			} while (!end);
+
+			/* Width & '*' operator */
+			int width = 0;
+			if (isdigit(uc)) {
+				while (true) {
+					width *= 10;
+					width += uc - '0';
+
+					i = nxt;
+					uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
+					if (uc == 0)
+						break;
+					if (!isdigit(uc))
+						break;
+				}
+			} else if (uc == '*') {
+				/* Get width value from argument list */
+				i = nxt;
+				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
+				width = (int) va_arg(ap, int);
+				if (width < 0) {
+					/* Negative width sets '-' flag */
+					width *= -1;
+					flags |= __PRINTF_FLAG_LEFTALIGNED;
+				}
+			}
+
+			/* Precision and '*' operator */
+			int precision = -1;
+			if (uc == '.') {
+				i = nxt;
+				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
+				if (isdigit(uc)) {
+					precision = 0;
+					while (true) {
+						precision *= 10;
+						precision += uc - '0';
+
+						i = nxt;
+						uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
+						if (uc == 0)
+							break;
+						if (!isdigit(uc))
+							break;
+					}
+				} else if (uc == '*') {
+					/* Get precision value from the argument list */
+					i = nxt;
+					uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
+					precision = (int) va_arg(ap, int);
+					if (precision < 0) {
+						/* Ignore negative precision - use default instead */
+						precision = -1;
+					}
+				}
+			}
+
+			qualifier_t qualifier;
+
+			switch (uc) {
+			case 't':
+				/* ptrdiff_t */
+				if (sizeof(ptrdiff_t) == sizeof(int32_t))
+					qualifier = PrintfQualifierInt;
+				else
+					qualifier = PrintfQualifierLongLong;
+				i = nxt;
+				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
+				break;
+			case 'h':
+				/* Char or short */
+				qualifier = PrintfQualifierShort;
+				i = nxt;
+				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
+				if (uc == 'h') {
+					i = nxt;
+					uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
+					qualifier = PrintfQualifierByte;
+				}
+				break;
+			case 'l':
+				/* Long or long long */
+				qualifier = PrintfQualifierLong;
+				i = nxt;
+				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
+				if (uc == 'l') {
+					i = nxt;
+					uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
+					qualifier = PrintfQualifierLongLong;
+				}
+				break;
+			case 'z':
+				qualifier = PrintfQualifierSize;
+				i = nxt;
+				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
+				break;
+			case 'j':
+				qualifier = PrintfQualifierMax;
+				i = nxt;
+				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
+				break;
+			default:
+				/* Default type */
+				qualifier = PrintfQualifierInt;
+			}
+
+			unsigned int base = 10;
+
+			switch (uc) {
+				/*
+				 * String and character conversions.
+				 */
+			case 's':
+				precision = max(0,  precision);
+
+				if (qualifier == PrintfQualifierLong)
+					retval = print_wstr(va_arg(ap, char32_t *), width, precision, flags, ps);
+				else
+					retval = print_str(va_arg(ap, char *), width, precision, flags, ps);
+
+				if (retval < 0) {
+					counter = -counter;
+					goto out;
+				}
+
+				counter += retval;
+				j = nxt;
+				continue;
+			case 'c':
+				if (qualifier == PrintfQualifierLong)
+					retval = print_wchar(va_arg(ap, wint_t), width, flags, ps);
+				else
+					retval = print_char(va_arg(ap, unsigned int), width, flags, ps);
+
+				if (retval < 0) {
+					counter = -counter;
+					goto out;
+				}
+
+				counter += retval;
+				j = nxt;
+				continue;
+
+#ifdef HAS_FLOAT
+				/*
+				 * Floating point values
+				 */
+			case 'G':
+			case 'g':
+			case 'F':
+			case 'f':
+			case 'E':
+			case 'e':
+				retval = print_double(va_arg(ap, double), uc, precision,
+				    width, flags, ps);
+
+				if (retval < 0) {
+					counter = -counter;
+					goto out;
+				}
+
+				counter += retval;
+				j = nxt;
+				continue;
+#endif
+
+				/*
+				 * Integer values
+				 */
+			case 'P':
+				/* Pointer */
+				flags |= __PRINTF_FLAG_BIGCHARS;
+				/* Fallthrough */
+			case 'p':
+				flags |= __PRINTF_FLAG_PREFIX;
+				flags |= __PRINTF_FLAG_ZEROPADDED;
+				base = 16;
+				qualifier = PrintfQualifierPointer;
+				break;
+			case 'b':
+				base = 2;
+				break;
+			case 'o':
+				base = 8;
+				break;
+			case 'd':
+			case 'i':
+				flags |= __PRINTF_FLAG_SIGNED;
+				/* Fallthrough */
+			case 'u':
+				break;
+			case 'X':
+				flags |= __PRINTF_FLAG_BIGCHARS;
+				/* Fallthrough */
+			case 'x':
+				base = 16;
+				break;
+
+			case '%':
+				/* Percentile itself */
+				j = i;
+				continue;
+
+				/*
+				 * Bad formatting.
+				 */
+			default:
+				/*
+				 * Unknown format. Now, j is the index of '%'
+				 * so we will print whole bad format sequence.
+				 */
+				continue;
+			}
+
+			/* Print integers */
+			size_t size;
+			uint64_t number;
+
+			switch (qualifier) {
+			case PrintfQualifierByte:
+				size = sizeof(unsigned char);
+				number = PRINTF_GET_INT_ARGUMENT(int, ap, flags);
+				break;
+			case PrintfQualifierShort:
+				size = sizeof(unsigned short);
+				number = PRINTF_GET_INT_ARGUMENT(int, ap, flags);
+				break;
+			case PrintfQualifierInt:
+				size = sizeof(unsigned int);
+				number = PRINTF_GET_INT_ARGUMENT(int, ap, flags);
+				break;
+			case PrintfQualifierLong:
+				size = sizeof(unsigned long);
+				number = PRINTF_GET_INT_ARGUMENT(long, ap, flags);
+				break;
+			case PrintfQualifierLongLong:
+				size = sizeof(unsigned long long);
+				number = PRINTF_GET_INT_ARGUMENT(long long, ap, flags);
+				break;
+			case PrintfQualifierPointer:
+				size = sizeof(void *);
+				precision = size << 1;
+				number = (uint64_t) (uintptr_t) va_arg(ap, void *);
+				break;
+			case PrintfQualifierSize:
+				size = sizeof(size_t);
+				number = (uint64_t) va_arg(ap, size_t);
+				break;
+			case PrintfQualifierMax:
+				size = sizeof(uintmax_t);
+				number = (uint64_t) va_arg(ap, uintmax_t);
+				break;
+			default:
+				/* Unknown qualifier */
+				counter = -counter;
+				goto out;
+			}
+
+			if ((retval = print_number(number, width, precision,
+			    base, flags, ps)) < 0) {
+				counter = -counter;
+				goto out;
+			}
+
+			counter += retval;
+			j = nxt;
+		}
+	}
+
+	if (i > j) {
+		if ((retval = printf_putnchars(&fmt[j], i - j, ps)) < 0) {
+			/* Error */
+			counter = -counter;
+			goto out;
+		}
+		counter += retval;
+	}
+
+out:
+	return ((int) counter);
+}
+
+/** @}
+ */
Index: common/stdc/bsearch.c
===================================================================
--- common/stdc/bsearch.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/stdc/bsearch.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2018 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+
+/**
+ * @file
+ * @brief Binary search.
+ */
+
+#include <bsearch.h>
+#include <stddef.h>
+
+/** Binary search.
+ *
+ * @param key Key to search for
+ * @param base Array of objects
+ * @param nmemb Number of objects in array
+ * @param size Size of each object
+ * @param compar Comparison function
+ */
+void *bsearch(const void *key, const void *base, size_t nmemb, size_t size,
+    int (*compar)(const void *, const void *))
+{
+	size_t pividx;
+	const void *pivot;
+	int r;
+
+	while (nmemb != 0) {
+		pividx = nmemb / 2;
+		pivot = base + size * pividx;
+
+		r = compar(key, pivot);
+		if (r == 0)
+			return (void *)pivot;
+
+		if (r < 0) {
+			/* Now only look at members preceding pivot */
+			nmemb = pividx;
+		} else {
+			/* Now only look at members following pivot */
+			nmemb = nmemb - pividx - 1;
+			base += size * (pividx + 1);
+		}
+	}
+
+	return NULL;
+}
+
+/** @}
+ */
Index: common/stdc/calloc.c
===================================================================
--- common/stdc/calloc.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/stdc/calloc.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009 Martin Decky
+ * Copyright (c) 2009 Petr Tuma
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+
+#include <mem.h>
+
+/** Allocate memory by number of elements
+ *
+ * @param nmemb Number of members to allocate.
+ * @param size  Size of one member in bytes.
+ *
+ * @return Allocated memory or NULL.
+ *
+ */
+void *calloc(const size_t nmemb, const size_t size)
+{
+	// Check for overflow
+	if (SIZE_MAX / size < nmemb)
+		return NULL;
+
+	void *block = malloc(nmemb * size);
+	if (block == NULL)
+		return NULL;
+
+	memset(block, 0, nmemb * size);
+	return block;
+}
Index: common/stdc/cc.h
===================================================================
--- common/stdc/cc.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/stdc/cc.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2013 Vojtech Horky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ * Macros related to used compiler (such as GCC-specific attributes).
+ */
+
+#ifndef _LIBC_CC_H_
+#define _LIBC_CC_H_
+
+#ifndef __clang__
+#define ATTRIBUTE_OPTIMIZE(opt) \
+    __attribute__ ((optimize(opt)))
+#else
+#define ATTRIBUTE_OPTIMIZE(opt)
+#endif
+
+#define ATTRIBUTE_OPTIMIZE_NO_TLDP \
+    ATTRIBUTE_OPTIMIZE("-fno-tree-loop-distribute-patterns")
+
+#ifdef CONFIG_LTO
+#define DO_NOT_DISCARD __attribute__ ((used))
+#else
+#define DO_NOT_DISCARD
+#endif
+
+#endif
+
+/** @}
+ */
Index: common/stdc/ctype.c
===================================================================
--- common/stdc/ctype.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/stdc/ctype.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2006 Josef Cejka
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+#include <ctype.h>
+
+int islower(int c)
+{
+	return ((c >= 'a') && (c <= 'z'));
+}
+
+int isupper(int c)
+{
+	return ((c >= 'A') && (c <= 'Z'));
+}
+
+int isalpha(int c)
+{
+	return (islower(c) || isupper(c));
+}
+
+int isdigit(int c)
+{
+	return ((c >= '0') && (c <= '9'));
+}
+
+int isalnum(int c)
+{
+	return (isalpha(c) || isdigit(c));
+}
+
+int isblank(int c)
+{
+	return c == ' ' || c == '\t';
+}
+
+int iscntrl(int c)
+{
+	return (c >= 0 && c < 0x20) || c == 0x7E;
+}
+
+int isprint(int c)
+{
+	return c >= 0 && c < 0x80 && !iscntrl(c);
+}
+
+int isgraph(int c)
+{
+	return isprint(c) && c != ' ';
+}
+
+int isspace(int c)
+{
+	switch (c) {
+	case ' ':
+	case '\n':
+	case '\t':
+	case '\f':
+	case '\r':
+	case '\v':
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+int ispunct(int c)
+{
+	return !isspace(c) && !isalnum(c) && isprint(c);
+}
+
+int isxdigit(int c)
+{
+	return isdigit(c) ||
+	    (c >= 'a' && c <= 'f') ||
+	    (c >= 'A' && c <= 'F');
+}
+
+int tolower(int c)
+{
+	if (isupper(c))
+		return (c + ('a' - 'A'));
+	else
+		return c;
+}
+
+int toupper(int c)
+{
+	if (islower(c))
+		return (c + ('A' - 'a'));
+	else
+		return c;
+}
+
+/** @}
+ */
Index: common/stdc/mem.c
===================================================================
--- common/stdc/mem.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/stdc/mem.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,297 @@
+/*
+ * Copyright (c) 2005 Martin Decky
+ * Copyright (c) 2018 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+#include "../include/mem.h"
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdint.h>
+#include "cc.h"
+
+#undef memset
+#undef memcpy
+#undef memcmp
+#undef memmove
+#undef memchr
+
+/** Fill memory block with a constant value. */
+DO_NOT_DISCARD
+ATTRIBUTE_OPTIMIZE_NO_TLDP
+    void *memset(void *dest, int b, size_t n)
+{
+	char *pb;
+	unsigned long *pw;
+	size_t word_size;
+	size_t n_words;
+
+	unsigned long pattern;
+	size_t i;
+	size_t fill;
+
+	/* Fill initial segment. */
+	word_size = sizeof(unsigned long);
+	fill = word_size - ((uintptr_t) dest & (word_size - 1));
+	if (fill > n)
+		fill = n;
+
+	pb = dest;
+
+	i = fill;
+	while (i-- != 0)
+		*pb++ = b;
+
+	/* Compute remaining size. */
+	n -= fill;
+	if (n == 0)
+		return dest;
+
+	n_words = n / word_size;
+	n = n % word_size;
+	pw = (unsigned long *) pb;
+
+	/* Create word-sized pattern for aligned segment. */
+	pattern = 0;
+	i = word_size;
+	while (i-- != 0)
+		pattern = (pattern << 8) | (uint8_t) b;
+
+	/* Fill aligned segment. */
+	i = n_words;
+	while (i-- != 0)
+		*pw++ = pattern;
+
+	pb = (char *) pw;
+
+	/* Fill final segment. */
+	i = n;
+	while (i-- != 0)
+		*pb++ = b;
+
+	return dest;
+}
+
+struct along {
+	unsigned long n;
+} __attribute__((packed));
+
+ATTRIBUTE_OPTIMIZE_NO_TLDP
+    static void *unaligned_memcpy(void *dst, const void *src, size_t n)
+{
+	size_t i, j;
+	struct along *adst = dst;
+	const struct along *asrc = src;
+
+	for (i = 0; i < n / sizeof(unsigned long); i++)
+		adst[i].n = asrc[i].n;
+
+	for (j = 0; j < n % sizeof(unsigned long); j++)
+		((unsigned char *) (((unsigned long *) dst) + i))[j] =
+		    ((unsigned char *) (((unsigned long *) src) + i))[j];
+
+	return (char *) dst;
+}
+
+/** Copy memory block. */
+DO_NOT_DISCARD
+ATTRIBUTE_OPTIMIZE_NO_TLDP
+    void *memcpy(void *dst, const void *src, size_t n)
+{
+	size_t i;
+	size_t mod, fill;
+	size_t word_size;
+	size_t n_words;
+
+	const unsigned long *srcw;
+	unsigned long *dstw;
+	const uint8_t *srcb;
+	uint8_t *dstb;
+
+	word_size = sizeof(unsigned long);
+
+	/*
+	 * Are source and destination addresses congruent modulo word_size?
+	 * If not, use unaligned_memcpy().
+	 */
+
+	if (((uintptr_t) dst & (word_size - 1)) !=
+	    ((uintptr_t) src & (word_size - 1)))
+		return unaligned_memcpy(dst, src, n);
+
+	/*
+	 * mod is the address modulo word size. fill is the length of the
+	 * initial buffer segment before the first word boundary.
+	 * If the buffer is very short, use unaligned_memcpy(), too.
+	 */
+
+	mod = (uintptr_t) dst & (word_size - 1);
+	fill = word_size - mod;
+	if (fill > n)
+		fill = n;
+
+	/* Copy the initial segment. */
+
+	srcb = src;
+	dstb = dst;
+
+	i = fill;
+	while (i-- != 0)
+		*dstb++ = *srcb++;
+
+	/* Compute remaining length. */
+
+	n -= fill;
+	if (n == 0)
+		return dst;
+
+	/* Pointers to aligned segment. */
+
+	dstw = (unsigned long *) dstb;
+	srcw = (const unsigned long *) srcb;
+
+	n_words = n / word_size;	/* Number of whole words to copy. */
+	n -= n_words * word_size;	/* Remaining bytes at the end. */
+
+	/* "Fast" copy. */
+	i = n_words;
+	while (i-- != 0)
+		*dstw++ = *srcw++;
+
+	/*
+	 * Copy the rest.
+	 */
+
+	srcb = (const uint8_t *) srcw;
+	dstb = (uint8_t *) dstw;
+
+	i = n;
+	while (i-- != 0)
+		*dstb++ = *srcb++;
+
+	return dst;
+}
+
+/** Move memory block with possible overlapping. */
+DO_NOT_DISCARD
+ATTRIBUTE_OPTIMIZE_NO_TLDP
+void *memmove(void *dst, const void *src, size_t n)
+{
+	const uint8_t *sp;
+	uint8_t *dp;
+
+	/* Nothing to do? */
+	if (src == dst)
+		return dst;
+
+	/* Non-overlapping? */
+	if (dst >= src + n || src >= dst + n) {
+		return memcpy(dst, src, n);
+	}
+
+	/* Which direction? */
+	if (src > dst) {
+		/* Forwards. */
+		sp = src;
+		dp = dst;
+
+		while (n-- != 0)
+			*dp++ = *sp++;
+	} else {
+		/* Backwards. */
+		sp = src + (n - 1);
+		dp = dst + (n - 1);
+
+		while (n-- != 0)
+			*dp-- = *sp--;
+	}
+
+	return dst;
+}
+
+/** Compare two memory areas.
+ *
+ * @param s1  Pointer to the first area to compare.
+ * @param s2  Pointer to the second area to compare.
+ * @param len Size of the areas in bytes.
+ *
+ * @return Zero if areas have the same contents. If they differ,
+ *	   the sign of the result is the same as the sign of the
+ *	   difference of the first pair of different bytes.
+ *
+ */
+DO_NOT_DISCARD
+ATTRIBUTE_OPTIMIZE_NO_TLDP
+    int memcmp(const void *s1, const void *s2, size_t len)
+{
+	uint8_t *u1 = (uint8_t *) s1;
+	uint8_t *u2 = (uint8_t *) s2;
+	size_t i;
+
+	for (i = 0; i < len; i++) {
+		if (*u1 != *u2)
+			return (int)(*u1) - (int)(*u2);
+		++u1;
+		++u2;
+	}
+
+	return 0;
+}
+
+/** Search memory area.
+ *
+ * @param s Memory area
+ * @param c Character (byte) to search for
+ * @param n Size of memory area in bytes
+ *
+ * @return Pointer to the first occurrence of @a c in the first @a n
+ *         bytes of @a s or @c NULL if not found.
+ */
+DO_NOT_DISCARD
+ATTRIBUTE_OPTIMIZE_NO_TLDP
+void *memchr(const void *s, int c, size_t n)
+{
+	uint8_t *u = (uint8_t *) s;
+	unsigned char uc = (unsigned char) c;
+	size_t i;
+
+	for (i = 0; i < n; i++) {
+		if (u[i] == uc)
+			return (void *) &u[i];
+	}
+
+	return NULL;
+}
+
+/** @}
+ */
Index: common/stdc/qsort.c
===================================================================
--- common/stdc/qsort.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/stdc/qsort.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2017 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+
+/**
+ * @file
+ * @brief Quicksort.
+ */
+
+#include <qsort.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+/** Quicksort spec */
+typedef struct {
+	void *base;
+	size_t nmemb;
+	size_t size;
+	int (*compar)(const void *, const void *, void *);
+	void *arg;
+} qs_spec_t;
+
+/** Comparison function wrapper.
+ *
+ * Performs qsort_r comparison using qsort comparison function
+ *
+ * @param a First element
+ * @param b Second element
+ * @param arg qsort comparison function
+ */
+static int compar_wrap(const void *a, const void *b, void *arg)
+{
+	int (*compar)(const void *, const void *) =
+	    (int (*)(const void *, const void *))arg;
+
+	return compar(a, b);
+}
+
+/** Determine if one element is less-than another element.
+ *
+ * @param qs Quicksort spec
+ * @param i First element index
+ * @param j Second element index
+ */
+static bool elem_lt(qs_spec_t *qs, size_t i, size_t j)
+{
+	const void *a;
+	const void *b;
+	int r;
+
+	a = qs->base + i * qs->size;
+	b = qs->base + j * qs->size;
+
+	r = qs->compar(a, b, qs->arg);
+
+	return r < 0;
+}
+
+/** Swap two elements.
+ *
+ * @param qs Quicksort spec
+ * @param i First element index
+ * @param j Second element index
+ */
+static void elem_swap(qs_spec_t *qs, size_t i, size_t j)
+{
+	char *a;
+	char *b;
+	char t;
+	size_t k;
+
+	a = qs->base + i * qs->size;
+	b = qs->base + j * qs->size;
+
+	for (k = 0; k < qs->size; k++) {
+		t = a[k];
+		a[k] = b[k];
+		b[k] = t;
+	}
+}
+
+/** Partition a range of indices.
+ *
+ * @param qs Quicksort spec
+ * @param lo Lower bound (inclusive)
+ * @param hi Upper bound (inclusive)
+ * @return Pivot index
+ */
+static size_t partition(qs_spec_t *qs, size_t lo, size_t hi)
+{
+	size_t pivot;
+	size_t i, j;
+
+	pivot = lo + (hi - lo) / 2;
+	i = lo;
+	j = hi;
+	while (true) {
+		while (elem_lt(qs, i, pivot))
+			++i;
+		while (elem_lt(qs, pivot, j))
+			--j;
+
+		if (i >= j)
+			return j;
+
+		elem_swap(qs, i, j);
+
+		/* Need to follow pivot movement */
+		if (i == pivot)
+			pivot = j;
+		else if (j == pivot)
+			pivot = i;
+
+		i++;
+		j--;
+	}
+}
+
+/** Sort a range of indices.
+ *
+ * @param qs Quicksort spec
+ * @param lo Lower bound (inclusive)
+ * @param hi Upper bound (inclusive)
+ */
+static void quicksort(qs_spec_t *qs, size_t lo, size_t hi)
+{
+	size_t p;
+
+	if (lo < hi) {
+		p = partition(qs, lo, hi);
+		quicksort(qs, lo, p);
+		quicksort(qs, p + 1, hi);
+	}
+}
+
+/** Quicksort.
+ *
+ * @param base Array to sort
+ * @param nmemb Number of array members
+ * @param size Size of member in bytes
+ * @param compar Comparison function
+ */
+void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *,
+    const void *))
+{
+	qs_spec_t qs;
+
+	if (nmemb == 0)
+		return;
+
+	qs.base = base;
+	qs.nmemb = nmemb;
+	qs.size = size;
+	qs.compar = compar_wrap;
+	qs.arg = compar;
+
+	quicksort(&qs, 0, nmemb - 1);
+}
+
+/** Quicksort with extra argument to comparison function.
+ *
+ * @param base Array to sort
+ * @param nmemb Number of array members
+ * @param size Size of member in bytes
+ * @param compar Comparison function
+ * @param arg Argument to comparison function
+ */
+void qsort_r(void *base, size_t nmemb, size_t size, int (*compar)(const void *,
+    const void *, void *), void *arg)
+{
+	qs_spec_t qs;
+
+	if (nmemb == 0)
+		return;
+
+	qs.base = base;
+	qs.nmemb = nmemb;
+	qs.size = size;
+	qs.compar = compar;
+	qs.arg = arg;
+
+	quicksort(&qs, 0, nmemb - 1);
+}
+
+/** @}
+ */
Index: common/str.c
===================================================================
--- common/str.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/str.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,1548 @@
+/*
+ * Copyright (c) 2001-2004 Jakub Jermar
+ * Copyright (c) 2005 Martin Decky
+ * Copyright (c) 2008 Jiri Svoboda
+ * Copyright (c) 2011 Martin Sucha
+ * Copyright (c) 2011 Oleg Romanenko
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+
+/**
+ * @file
+ * @brief String functions.
+ *
+ * Strings and characters use the Universal Character Set (UCS). The standard
+ * strings, called just strings are encoded in UTF-8. Wide strings (encoded
+ * in UTF-32) are supported to a limited degree. A single character is
+ * represented as char32_t.@n
+ *
+ * Overview of the terminology:@n
+ *
+ *  Term                  Meaning
+ *  --------------------  ----------------------------------------------------
+ *  byte                  8 bits stored in uint8_t (unsigned 8 bit integer)
+ *
+ *  character             UTF-32 encoded Unicode character, stored in char32_t
+ *                        (unsigned 32 bit integer), code points 0 .. 1114111
+ *                        are valid
+ *
+ *  ASCII character       7 bit encoded ASCII character, stored in char
+ *                        (usually signed 8 bit integer), code points 0 .. 127
+ *                        are valid
+ *
+ *  string                UTF-8 encoded NULL-terminated Unicode string, char *
+ *
+ *  wide string           UTF-32 encoded NULL-terminated Unicode string,
+ *                        char32_t *
+ *
+ *  [wide] string size    number of BYTES in a [wide] string (excluding
+ *                        the NULL-terminator), size_t
+ *
+ *  [wide] string length  number of CHARACTERS in a [wide] string (excluding
+ *                        the NULL-terminator), size_t
+ *
+ *  [wide] string width   number of display cells on a monospace display taken
+ *                        by a [wide] string, size_t
+ *
+ *
+ * Overview of string metrics:@n
+ *
+ *  Metric  Abbrev.  Type     Meaning
+ *  ------  ------   ------   -------------------------------------------------
+ *  size    n        size_t   number of BYTES in a string (excluding the
+ *                            NULL-terminator)
+ *
+ *  length  l        size_t   number of CHARACTERS in a string (excluding the
+ *                            null terminator)
+ *
+ *  width  w         size_t   number of display cells on a monospace display
+ *                            taken by a string
+ *
+ *
+ * Function naming prefixes:@n
+ *
+ *  chr_    operate on characters
+ *  ascii_  operate on ASCII characters
+ *  str_    operate on strings
+ *  wstr_   operate on wide strings
+ *
+ *  [w]str_[n|l|w]  operate on a prefix limited by size, length
+ *                  or width
+ *
+ *
+ * A specific character inside a [wide] string can be referred to by:@n
+ *
+ *  pointer (char *, char32_t *)
+ *  byte offset (size_t)
+ *  character index (size_t)
+ *
+ */
+
+#include <str.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <align.h>
+#include <mem.h>
+
+/** Byte mask consisting of lowest @n bits (out of 8) */
+#define LO_MASK_8(n)  ((uint8_t) ((1 << (n)) - 1))
+
+/** Byte mask consisting of lowest @n bits (out of 32) */
+#define LO_MASK_32(n)  ((uint32_t) ((1 << (n)) - 1))
+
+/** Byte mask consisting of highest @n bits (out of 8) */
+#define HI_MASK_8(n)  (~LO_MASK_8(8 - (n)))
+
+/** Number of data bits in a UTF-8 continuation byte */
+#define CONT_BITS  6
+
+/** Decode a single character from a string.
+ *
+ * Decode a single character from a string of size @a size. Decoding starts
+ * at @a offset and this offset is moved to the beginning of the next
+ * character. In case of decoding error, offset generally advances at least
+ * by one. However, offset is never moved beyond size.
+ *
+ * @param str    String (not necessarily NULL-terminated).
+ * @param offset Byte offset in string where to start decoding.
+ * @param size   Size of the string (in bytes).
+ *
+ * @return Value of decoded character, U_SPECIAL on decoding error or
+ *         NULL if attempt to decode beyond @a size.
+ *
+ */
+char32_t str_decode(const char *str, size_t *offset, size_t size)
+{
+	if (*offset + 1 > size)
+		return 0;
+
+	/* First byte read from string */
+	uint8_t b0 = (uint8_t) str[(*offset)++];
+
+	/* Determine code length */
+
+	unsigned int b0_bits;  /* Data bits in first byte */
+	unsigned int cbytes;   /* Number of continuation bytes */
+
+	if ((b0 & 0x80) == 0) {
+		/* 0xxxxxxx (Plain ASCII) */
+		b0_bits = 7;
+		cbytes = 0;
+	} else if ((b0 & 0xe0) == 0xc0) {
+		/* 110xxxxx 10xxxxxx */
+		b0_bits = 5;
+		cbytes = 1;
+	} else if ((b0 & 0xf0) == 0xe0) {
+		/* 1110xxxx 10xxxxxx 10xxxxxx */
+		b0_bits = 4;
+		cbytes = 2;
+	} else if ((b0 & 0xf8) == 0xf0) {
+		/* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
+		b0_bits = 3;
+		cbytes = 3;
+	} else {
+		/* 10xxxxxx -- unexpected continuation byte */
+		return U_SPECIAL;
+	}
+
+	if (*offset + cbytes > size)
+		return U_SPECIAL;
+
+	char32_t ch = b0 & LO_MASK_8(b0_bits);
+
+	/* Decode continuation bytes */
+	while (cbytes > 0) {
+		uint8_t b = (uint8_t) str[(*offset)++];
+
+		/* Must be 10xxxxxx */
+		if ((b & 0xc0) != 0x80)
+			return U_SPECIAL;
+
+		/* Shift data bits to ch */
+		ch = (ch << CONT_BITS) | (char32_t) (b & LO_MASK_8(CONT_BITS));
+		cbytes--;
+	}
+
+	return ch;
+}
+
+/** Decode a single character from a string to the left.
+ *
+ * Decode a single character from a string of size @a size. Decoding starts
+ * at @a offset and this offset is moved to the beginning of the previous
+ * character. In case of decoding error, offset generally decreases at least
+ * by one. However, offset is never moved before 0.
+ *
+ * @param str    String (not necessarily NULL-terminated).
+ * @param offset Byte offset in string where to start decoding.
+ * @param size   Size of the string (in bytes).
+ *
+ * @return Value of decoded character, U_SPECIAL on decoding error or
+ *         NULL if attempt to decode beyond @a start of str.
+ *
+ */
+char32_t str_decode_reverse(const char *str, size_t *offset, size_t size)
+{
+	if (*offset == 0)
+		return 0;
+
+	size_t processed = 0;
+	/* Continue while continuation bytes found */
+	while (*offset > 0 && processed < 4) {
+		uint8_t b = (uint8_t) str[--(*offset)];
+
+		if (processed == 0 && (b & 0x80) == 0) {
+			/* 0xxxxxxx (Plain ASCII) */
+			return b & 0x7f;
+		} else if ((b & 0xe0) == 0xc0 || (b & 0xf0) == 0xe0 ||
+		    (b & 0xf8) == 0xf0) {
+			/* Start byte */
+			size_t start_offset = *offset;
+			return str_decode(str, &start_offset, size);
+		} else if ((b & 0xc0) != 0x80) {
+			/* Not a continuation byte */
+			return U_SPECIAL;
+		}
+		processed++;
+	}
+	/* Too many continuation bytes */
+	return U_SPECIAL;
+}
+
+/** Encode a single character to string representation.
+ *
+ * Encode a single character to string representation (i.e. UTF-8) and store
+ * it into a buffer at @a offset. Encoding starts at @a offset and this offset
+ * is moved to the position where the next character can be written to.
+ *
+ * @param ch     Input character.
+ * @param str    Output buffer.
+ * @param offset Byte offset where to start writing.
+ * @param size   Size of the output buffer (in bytes).
+ *
+ * @return EOK if the character was encoded successfully, EOVERFLOW if there
+ *         was not enough space in the output buffer or EINVAL if the character
+ *         code was invalid.
+ */
+errno_t chr_encode(const char32_t ch, char *str, size_t *offset, size_t size)
+{
+	if (*offset >= size)
+		return EOVERFLOW;
+
+	if (!chr_check(ch))
+		return EINVAL;
+
+	/*
+	 * Unsigned version of ch (bit operations should only be done
+	 * on unsigned types).
+	 */
+	uint32_t cc = (uint32_t) ch;
+
+	/* Determine how many continuation bytes are needed */
+
+	unsigned int b0_bits;  /* Data bits in first byte */
+	unsigned int cbytes;   /* Number of continuation bytes */
+
+	if ((cc & ~LO_MASK_32(7)) == 0) {
+		b0_bits = 7;
+		cbytes = 0;
+	} else if ((cc & ~LO_MASK_32(11)) == 0) {
+		b0_bits = 5;
+		cbytes = 1;
+	} else if ((cc & ~LO_MASK_32(16)) == 0) {
+		b0_bits = 4;
+		cbytes = 2;
+	} else if ((cc & ~LO_MASK_32(21)) == 0) {
+		b0_bits = 3;
+		cbytes = 3;
+	} else {
+		/* Codes longer than 21 bits are not supported */
+		return EINVAL;
+	}
+
+	/* Check for available space in buffer */
+	if (*offset + cbytes >= size)
+		return EOVERFLOW;
+
+	/* Encode continuation bytes */
+	unsigned int i;
+	for (i = cbytes; i > 0; i--) {
+		str[*offset + i] = 0x80 | (cc & LO_MASK_32(CONT_BITS));
+		cc = cc >> CONT_BITS;
+	}
+
+	/* Encode first byte */
+	str[*offset] = (cc & LO_MASK_32(b0_bits)) | HI_MASK_8(8 - b0_bits - 1);
+
+	/* Advance offset */
+	*offset += cbytes + 1;
+
+	return EOK;
+}
+
+/** Get size of string.
+ *
+ * Get the number of bytes which are used by the string @a str (excluding the
+ * NULL-terminator).
+ *
+ * @param str String to consider.
+ *
+ * @return Number of bytes used by the string
+ *
+ */
+size_t str_size(const char *str)
+{
+	size_t size = 0;
+
+	while (*str++ != 0)
+		size++;
+
+	return size;
+}
+
+/** Get size of wide string.
+ *
+ * Get the number of bytes which are used by the wide string @a str (excluding the
+ * NULL-terminator).
+ *
+ * @param str Wide string to consider.
+ *
+ * @return Number of bytes used by the wide string
+ *
+ */
+size_t wstr_size(const char32_t *str)
+{
+	return (wstr_length(str) * sizeof(char32_t));
+}
+
+/** Get size of string with length limit.
+ *
+ * Get the number of bytes which are used by up to @a max_len first
+ * characters in the string @a str. If @a max_len is greater than
+ * the length of @a str, the entire string is measured (excluding the
+ * NULL-terminator).
+ *
+ * @param str     String to consider.
+ * @param max_len Maximum number of characters to measure.
+ *
+ * @return Number of bytes used by the characters.
+ *
+ */
+size_t str_lsize(const char *str, size_t max_len)
+{
+	size_t len = 0;
+	size_t offset = 0;
+
+	while (len < max_len) {
+		if (str_decode(str, &offset, STR_NO_LIMIT) == 0)
+			break;
+
+		len++;
+	}
+
+	return offset;
+}
+
+/** Get size of string with size limit.
+ *
+ * Get the number of bytes which are used by the string @a str
+ * (excluding the NULL-terminator), but no more than @max_size bytes.
+ *
+ * @param str      String to consider.
+ * @param max_size Maximum number of bytes to measure.
+ *
+ * @return Number of bytes used by the string
+ *
+ */
+size_t str_nsize(const char *str, size_t max_size)
+{
+	size_t size = 0;
+
+	while ((*str++ != 0) && (size < max_size))
+		size++;
+
+	return size;
+}
+
+/** Get size of wide string with size limit.
+ *
+ * Get the number of bytes which are used by the wide string @a str
+ * (excluding the NULL-terminator), but no more than @max_size bytes.
+ *
+ * @param str      Wide string to consider.
+ * @param max_size Maximum number of bytes to measure.
+ *
+ * @return Number of bytes used by the wide string
+ *
+ */
+size_t wstr_nsize(const char32_t *str, size_t max_size)
+{
+	return (wstr_nlength(str, max_size) * sizeof(char32_t));
+}
+
+/** Get size of wide string with length limit.
+ *
+ * Get the number of bytes which are used by up to @a max_len first
+ * wide characters in the wide string @a str. If @a max_len is greater than
+ * the length of @a str, the entire wide string is measured (excluding the
+ * NULL-terminator).
+ *
+ * @param str     Wide string to consider.
+ * @param max_len Maximum number of wide characters to measure.
+ *
+ * @return Number of bytes used by the wide characters.
+ *
+ */
+size_t wstr_lsize(const char32_t *str, size_t max_len)
+{
+	return (wstr_nlength(str, max_len * sizeof(char32_t)) * sizeof(char32_t));
+}
+
+/** Get number of characters in a string.
+ *
+ * @param str NULL-terminated string.
+ *
+ * @return Number of characters in string.
+ *
+ */
+size_t str_length(const char *str)
+{
+	size_t len = 0;
+	size_t offset = 0;
+
+	while (str_decode(str, &offset, STR_NO_LIMIT) != 0)
+		len++;
+
+	return len;
+}
+
+/** Get number of characters in a wide string.
+ *
+ * @param str NULL-terminated wide string.
+ *
+ * @return Number of characters in @a str.
+ *
+ */
+size_t wstr_length(const char32_t *wstr)
+{
+	size_t len = 0;
+
+	while (*wstr++ != 0)
+		len++;
+
+	return len;
+}
+
+/** Get number of characters in a string with size limit.
+ *
+ * @param str  NULL-terminated string.
+ * @param size Maximum number of bytes to consider.
+ *
+ * @return Number of characters in string.
+ *
+ */
+size_t str_nlength(const char *str, size_t size)
+{
+	size_t len = 0;
+	size_t offset = 0;
+
+	while (str_decode(str, &offset, size) != 0)
+		len++;
+
+	return len;
+}
+
+/** Get number of characters in a string with size limit.
+ *
+ * @param str  NULL-terminated string.
+ * @param size Maximum number of bytes to consider.
+ *
+ * @return Number of characters in string.
+ *
+ */
+size_t wstr_nlength(const char32_t *str, size_t size)
+{
+	size_t len = 0;
+	size_t limit = ALIGN_DOWN(size, sizeof(char32_t));
+	size_t offset = 0;
+
+	while ((offset < limit) && (*str++ != 0)) {
+		len++;
+		offset += sizeof(char32_t);
+	}
+
+	return len;
+}
+
+/** Get character display width on a character cell display.
+ *
+ * @param ch	Character
+ * @return	Width of character in cells.
+ */
+size_t chr_width(char32_t ch)
+{
+	return 1;
+}
+
+/** Get string display width on a character cell display.
+ *
+ * @param str	String
+ * @return	Width of string in cells.
+ */
+size_t str_width(const char *str)
+{
+	size_t width = 0;
+	size_t offset = 0;
+	char32_t ch;
+
+	while ((ch = str_decode(str, &offset, STR_NO_LIMIT)) != 0)
+		width += chr_width(ch);
+
+	return width;
+}
+
+/** Check whether character is plain ASCII.
+ *
+ * @return True if character is plain ASCII.
+ *
+ */
+bool ascii_check(char32_t ch)
+{
+	if (ch <= 127)
+		return true;
+
+	return false;
+}
+
+/** Check whether character is valid
+ *
+ * @return True if character is a valid Unicode code point.
+ *
+ */
+bool chr_check(char32_t ch)
+{
+	if (ch <= 1114111)
+		return true;
+
+	return false;
+}
+
+/** Compare two NULL terminated strings.
+ *
+ * Do a char-by-char comparison of two NULL-terminated strings.
+ * The strings are considered equal iff their length is equal
+ * and both strings consist of the same sequence of characters.
+ *
+ * A string S1 is less than another string S2 if it has a character with
+ * lower value at the first character position where the strings differ.
+ * If the strings differ in length, the shorter one is treated as if
+ * padded by characters with a value of zero.
+ *
+ * @param s1 First string to compare.
+ * @param s2 Second string to compare.
+ *
+ * @return 0 if the strings are equal, -1 if the first is less than the second,
+ *         1 if the second is less than the first.
+ *
+ */
+int str_cmp(const char *s1, const char *s2)
+{
+	char32_t c1 = 0;
+	char32_t c2 = 0;
+
+	size_t off1 = 0;
+	size_t off2 = 0;
+
+	while (true) {
+		c1 = str_decode(s1, &off1, STR_NO_LIMIT);
+		c2 = str_decode(s2, &off2, STR_NO_LIMIT);
+
+		if (c1 < c2)
+			return -1;
+
+		if (c1 > c2)
+			return 1;
+
+		if (c1 == 0 || c2 == 0)
+			break;
+	}
+
+	return 0;
+}
+
+/** Compare two NULL terminated strings with length limit.
+ *
+ * Do a char-by-char comparison of two NULL-terminated strings.
+ * The strings are considered equal iff
+ * min(str_length(s1), max_len) == min(str_length(s2), max_len)
+ * and both strings consist of the same sequence of characters,
+ * up to max_len characters.
+ *
+ * A string S1 is less than another string S2 if it has a character with
+ * lower value at the first character position where the strings differ.
+ * If the strings differ in length, the shorter one is treated as if
+ * padded by characters with a value of zero. Only the first max_len
+ * characters are considered.
+ *
+ * @param s1      First string to compare.
+ * @param s2      Second string to compare.
+ * @param max_len Maximum number of characters to consider.
+ *
+ * @return 0 if the strings are equal, -1 if the first is less than the second,
+ *         1 if the second is less than the first.
+ *
+ */
+int str_lcmp(const char *s1, const char *s2, size_t max_len)
+{
+	char32_t c1 = 0;
+	char32_t c2 = 0;
+
+	size_t off1 = 0;
+	size_t off2 = 0;
+
+	size_t len = 0;
+
+	while (true) {
+		if (len >= max_len)
+			break;
+
+		c1 = str_decode(s1, &off1, STR_NO_LIMIT);
+		c2 = str_decode(s2, &off2, STR_NO_LIMIT);
+
+		if (c1 < c2)
+			return -1;
+
+		if (c1 > c2)
+			return 1;
+
+		if (c1 == 0 || c2 == 0)
+			break;
+
+		++len;
+	}
+
+	return 0;
+
+}
+
+/** Compare two NULL terminated strings in case-insensitive manner.
+ *
+ * Do a char-by-char comparison of two NULL-terminated strings.
+ * The strings are considered equal iff their length is equal
+ * and both strings consist of the same sequence of characters
+ * when converted to lower case.
+ *
+ * A string S1 is less than another string S2 if it has a character with
+ * lower value at the first character position where the strings differ.
+ * If the strings differ in length, the shorter one is treated as if
+ * padded by characters with a value of zero.
+ *
+ * @param s1 First string to compare.
+ * @param s2 Second string to compare.
+ *
+ * @return 0 if the strings are equal, -1 if the first is less than the second,
+ *         1 if the second is less than the first.
+ *
+ */
+int str_casecmp(const char *s1, const char *s2)
+{
+	char32_t c1 = 0;
+	char32_t c2 = 0;
+
+	size_t off1 = 0;
+	size_t off2 = 0;
+
+	while (true) {
+		c1 = tolower(str_decode(s1, &off1, STR_NO_LIMIT));
+		c2 = tolower(str_decode(s2, &off2, STR_NO_LIMIT));
+
+		if (c1 < c2)
+			return -1;
+
+		if (c1 > c2)
+			return 1;
+
+		if (c1 == 0 || c2 == 0)
+			break;
+	}
+
+	return 0;
+}
+
+/** Compare two NULL terminated strings with length limit in case-insensitive
+ * manner.
+ *
+ * Do a char-by-char comparison of two NULL-terminated strings.
+ * The strings are considered equal iff
+ * min(str_length(s1), max_len) == min(str_length(s2), max_len)
+ * and both strings consist of the same sequence of characters,
+ * up to max_len characters.
+ *
+ * A string S1 is less than another string S2 if it has a character with
+ * lower value at the first character position where the strings differ.
+ * If the strings differ in length, the shorter one is treated as if
+ * padded by characters with a value of zero. Only the first max_len
+ * characters are considered.
+ *
+ * @param s1      First string to compare.
+ * @param s2      Second string to compare.
+ * @param max_len Maximum number of characters to consider.
+ *
+ * @return 0 if the strings are equal, -1 if the first is less than the second,
+ *         1 if the second is less than the first.
+ *
+ */
+int str_lcasecmp(const char *s1, const char *s2, size_t max_len)
+{
+	char32_t c1 = 0;
+	char32_t c2 = 0;
+
+	size_t off1 = 0;
+	size_t off2 = 0;
+
+	size_t len = 0;
+
+	while (true) {
+		if (len >= max_len)
+			break;
+
+		c1 = tolower(str_decode(s1, &off1, STR_NO_LIMIT));
+		c2 = tolower(str_decode(s2, &off2, STR_NO_LIMIT));
+
+		if (c1 < c2)
+			return -1;
+
+		if (c1 > c2)
+			return 1;
+
+		if (c1 == 0 || c2 == 0)
+			break;
+
+		++len;
+	}
+
+	return 0;
+
+}
+
+/** Test whether p is a prefix of s.
+ *
+ * Do a char-by-char comparison of two NULL-terminated strings
+ * and determine if p is a prefix of s.
+ *
+ * @param s The string in which to look
+ * @param p The string to check if it is a prefix of s
+ *
+ * @return true iff p is prefix of s else false
+ *
+ */
+bool str_test_prefix(const char *s, const char *p)
+{
+	char32_t c1 = 0;
+	char32_t c2 = 0;
+
+	size_t off1 = 0;
+	size_t off2 = 0;
+
+	while (true) {
+		c1 = str_decode(s, &off1, STR_NO_LIMIT);
+		c2 = str_decode(p, &off2, STR_NO_LIMIT);
+
+		if (c2 == 0)
+			return true;
+
+		if (c1 != c2)
+			return false;
+
+		if (c1 == 0)
+			break;
+	}
+
+	return false;
+}
+
+/** Get a string suffix.
+ *
+ * Return a string suffix defined by the prefix length.
+ *
+ * @param s             The string to get the suffix from.
+ * @param prefix_length Number of prefix characters to ignore.
+ *
+ * @return String suffix.
+ *
+ */
+const char *str_suffix(const char *s, size_t prefix_length)
+{
+	size_t off = 0;
+	size_t i = 0;
+
+	while (true) {
+		str_decode(s, &off, STR_NO_LIMIT);
+		i++;
+
+		if (i >= prefix_length)
+			break;
+	}
+
+	return s + off;
+}
+
+/** Copy string.
+ *
+ * Copy source string @a src to destination buffer @a dest.
+ * No more than @a size bytes are written. If the size of the output buffer
+ * is at least one byte, the output string will always be well-formed, i.e.
+ * null-terminated and containing only complete characters.
+ *
+ * @param dest  Destination buffer.
+ * @param count Size of the destination buffer (must be > 0).
+ * @param src   Source string.
+ *
+ */
+void str_cpy(char *dest, size_t size, const char *src)
+{
+	/* There must be space for a null terminator in the buffer. */
+	assert(size > 0);
+	assert(src != NULL);
+
+	size_t src_off = 0;
+	size_t dest_off = 0;
+
+	char32_t ch;
+	while ((ch = str_decode(src, &src_off, STR_NO_LIMIT)) != 0) {
+		if (chr_encode(ch, dest, &dest_off, size - 1) != EOK)
+			break;
+	}
+
+	dest[dest_off] = '\0';
+}
+
+/** Copy size-limited substring.
+ *
+ * Copy prefix of string @a src of max. size @a size to destination buffer
+ * @a dest. No more than @a size bytes are written. The output string will
+ * always be well-formed, i.e. null-terminated and containing only complete
+ * characters.
+ *
+ * No more than @a n bytes are read from the input string, so it does not
+ * have to be null-terminated.
+ *
+ * @param dest  Destination buffer.
+ * @param count Size of the destination buffer (must be > 0).
+ * @param src   Source string.
+ * @param n     Maximum number of bytes to read from @a src.
+ *
+ */
+void str_ncpy(char *dest, size_t size, const char *src, size_t n)
+{
+	/* There must be space for a null terminator in the buffer. */
+	assert(size > 0);
+
+	size_t src_off = 0;
+	size_t dest_off = 0;
+
+	char32_t ch;
+	while ((ch = str_decode(src, &src_off, n)) != 0) {
+		if (chr_encode(ch, dest, &dest_off, size - 1) != EOK)
+			break;
+	}
+
+	dest[dest_off] = '\0';
+}
+
+/** Append one string to another.
+ *
+ * Append source string @a src to string in destination buffer @a dest.
+ * Size of the destination buffer is @a dest. If the size of the output buffer
+ * is at least one byte, the output string will always be well-formed, i.e.
+ * null-terminated and containing only complete characters.
+ *
+ * @param dest   Destination buffer.
+ * @param count Size of the destination buffer.
+ * @param src   Source string.
+ */
+void str_append(char *dest, size_t size, const char *src)
+{
+	size_t dstr_size;
+
+	dstr_size = str_size(dest);
+	if (dstr_size >= size)
+		return;
+
+	str_cpy(dest + dstr_size, size - dstr_size, src);
+}
+
+/** Convert space-padded ASCII to string.
+ *
+ * Common legacy text encoding in hardware is 7-bit ASCII fitted into
+ * a fixed-width byte buffer (bit 7 always zero), right-padded with spaces
+ * (ASCII 0x20). Convert space-padded ascii to string representation.
+ *
+ * If the text does not fit into the destination buffer, the function converts
+ * as many characters as possible and returns EOVERFLOW.
+ *
+ * If the text contains non-ASCII bytes (with bit 7 set), the whole string is
+ * converted anyway and invalid characters are replaced with question marks
+ * (U_SPECIAL) and the function returns EIO.
+ *
+ * Regardless of return value upon return @a dest will always be well-formed.
+ *
+ * @param dest		Destination buffer
+ * @param size		Size of destination buffer
+ * @param src		Space-padded ASCII.
+ * @param n		Size of the source buffer in bytes.
+ *
+ * @return		EOK on success, EOVERFLOW if the text does not fit
+ *			destination buffer, EIO if the text contains
+ *			non-ASCII bytes.
+ */
+errno_t spascii_to_str(char *dest, size_t size, const uint8_t *src, size_t n)
+{
+	size_t sidx;
+	size_t didx;
+	size_t dlast;
+	uint8_t byte;
+	errno_t rc;
+	errno_t result;
+
+	/* There must be space for a null terminator in the buffer. */
+	assert(size > 0);
+	result = EOK;
+
+	didx = 0;
+	dlast = 0;
+	for (sidx = 0; sidx < n; ++sidx) {
+		byte = src[sidx];
+		if (!ascii_check(byte)) {
+			byte = U_SPECIAL;
+			result = EIO;
+		}
+
+		rc = chr_encode(byte, dest, &didx, size - 1);
+		if (rc != EOK) {
+			assert(rc == EOVERFLOW);
+			dest[didx] = '\0';
+			return rc;
+		}
+
+		/* Remember dest index after last non-empty character */
+		if (byte != 0x20)
+			dlast = didx;
+	}
+
+	/* Terminate string after last non-empty character */
+	dest[dlast] = '\0';
+	return result;
+}
+
+/** Convert wide string to string.
+ *
+ * Convert wide string @a src to string. The output is written to the buffer
+ * specified by @a dest and @a size. @a size must be non-zero and the string
+ * written will always be well-formed.
+ *
+ * @param dest	Destination buffer.
+ * @param size	Size of the destination buffer.
+ * @param src	Source wide string.
+ */
+void wstr_to_str(char *dest, size_t size, const char32_t *src)
+{
+	char32_t ch;
+	size_t src_idx;
+	size_t dest_off;
+
+	/* There must be space for a null terminator in the buffer. */
+	assert(size > 0);
+
+	src_idx = 0;
+	dest_off = 0;
+
+	while ((ch = src[src_idx++]) != 0) {
+		if (chr_encode(ch, dest, &dest_off, size - 1) != EOK)
+			break;
+	}
+
+	dest[dest_off] = '\0';
+}
+
+/** Convert UTF16 string to string.
+ *
+ * Convert utf16 string @a src to string. The output is written to the buffer
+ * specified by @a dest and @a size. @a size must be non-zero and the string
+ * written will always be well-formed. Surrogate pairs also supported.
+ *
+ * @param dest	Destination buffer.
+ * @param size	Size of the destination buffer.
+ * @param src	Source utf16 string.
+ *
+ * @return EOK, if success, an error code otherwise.
+ */
+errno_t utf16_to_str(char *dest, size_t size, const uint16_t *src)
+{
+	size_t idx = 0, dest_off = 0;
+	char32_t ch;
+	errno_t rc = EOK;
+
+	/* There must be space for a null terminator in the buffer. */
+	assert(size > 0);
+
+	while (src[idx]) {
+		if ((src[idx] & 0xfc00) == 0xd800) {
+			if (src[idx + 1] && (src[idx + 1] & 0xfc00) == 0xdc00) {
+				ch = 0x10000;
+				ch += (src[idx] & 0x03FF) << 10;
+				ch += (src[idx + 1] & 0x03FF);
+				idx += 2;
+			} else
+				break;
+		} else {
+			ch = src[idx];
+			idx++;
+		}
+		rc = chr_encode(ch, dest, &dest_off, size - 1);
+		if (rc != EOK)
+			break;
+	}
+	dest[dest_off] = '\0';
+	return rc;
+}
+
+/** Convert string to UTF16 string.
+ *
+ * Convert string @a src to utf16 string. The output is written to the buffer
+ * specified by @a dest and @a dlen. @a dlen must be non-zero and the string
+ * written will always be well-formed. Surrogate pairs also supported.
+ *
+ * @param dest	Destination buffer.
+ * @param dlen	Number of utf16 characters that fit in the destination buffer.
+ * @param src	Source string.
+ *
+ * @return EOK, if success, an error code otherwise.
+ */
+errno_t str_to_utf16(uint16_t *dest, size_t dlen, const char *src)
+{
+	errno_t rc = EOK;
+	size_t offset = 0;
+	size_t idx = 0;
+	char32_t c;
+
+	assert(dlen > 0);
+
+	while ((c = str_decode(src, &offset, STR_NO_LIMIT)) != 0) {
+		if (c > 0x10000) {
+			if (idx + 2 >= dlen - 1) {
+				rc = EOVERFLOW;
+				break;
+			}
+			c = (c - 0x10000);
+			dest[idx] = 0xD800 | (c >> 10);
+			dest[idx + 1] = 0xDC00 | (c & 0x3FF);
+			idx++;
+		} else {
+			dest[idx] = c;
+		}
+
+		idx++;
+		if (idx >= dlen - 1) {
+			rc = EOVERFLOW;
+			break;
+		}
+	}
+
+	dest[idx] = '\0';
+	return rc;
+}
+
+/** Get size of UTF-16 string.
+ *
+ * Get the number of words which are used by the UTF-16 string @a ustr
+ * (excluding the NULL-terminator).
+ *
+ * @param ustr UTF-16 string to consider.
+ *
+ * @return Number of words used by the UTF-16 string
+ *
+ */
+size_t utf16_wsize(const uint16_t *ustr)
+{
+	size_t wsize = 0;
+
+	while (*ustr++ != 0)
+		wsize++;
+
+	return wsize;
+}
+
+/** Convert wide string to new string.
+ *
+ * Convert wide string @a src to string. Space for the new string is allocated
+ * on the heap.
+ *
+ * @param src	Source wide string.
+ * @return	New string.
+ */
+char *wstr_to_astr(const char32_t *src)
+{
+	char dbuf[STR_BOUNDS(1)];
+	char *str;
+	char32_t ch;
+
+	size_t src_idx;
+	size_t dest_off;
+	size_t dest_size;
+
+	/* Compute size of encoded string. */
+
+	src_idx = 0;
+	dest_size = 0;
+
+	while ((ch = src[src_idx++]) != 0) {
+		dest_off = 0;
+		if (chr_encode(ch, dbuf, &dest_off, STR_BOUNDS(1)) != EOK)
+			break;
+		dest_size += dest_off;
+	}
+
+	str = malloc(dest_size + 1);
+	if (str == NULL)
+		return NULL;
+
+	/* Encode string. */
+
+	src_idx = 0;
+	dest_off = 0;
+
+	while ((ch = src[src_idx++]) != 0) {
+		if (chr_encode(ch, str, &dest_off, dest_size) != EOK)
+			break;
+	}
+
+	str[dest_size] = '\0';
+	return str;
+}
+
+/** Convert string to wide string.
+ *
+ * Convert string @a src to wide string. The output is written to the
+ * buffer specified by @a dest and @a dlen. @a dlen must be non-zero
+ * and the wide string written will always be null-terminated.
+ *
+ * @param dest	Destination buffer.
+ * @param dlen	Length of destination buffer (number of wchars).
+ * @param src	Source string.
+ */
+void str_to_wstr(char32_t *dest, size_t dlen, const char *src)
+{
+	size_t offset;
+	size_t di;
+	char32_t c;
+
+	assert(dlen > 0);
+
+	offset = 0;
+	di = 0;
+
+	do {
+		if (di >= dlen - 1)
+			break;
+
+		c = str_decode(src, &offset, STR_NO_LIMIT);
+		dest[di++] = c;
+	} while (c != '\0');
+
+	dest[dlen - 1] = '\0';
+}
+
+/** Convert string to wide string.
+ *
+ * Convert string @a src to wide string. A new wide NULL-terminated
+ * string will be allocated on the heap.
+ *
+ * @param src	Source string.
+ */
+char32_t *str_to_awstr(const char *str)
+{
+	size_t len = str_length(str);
+
+	char32_t *wstr = calloc(len + 1, sizeof(char32_t));
+	if (wstr == NULL)
+		return NULL;
+
+	str_to_wstr(wstr, len + 1, str);
+	return wstr;
+}
+
+/** Find first occurence of character in string.
+ *
+ * @param str String to search.
+ * @param ch  Character to look for.
+ *
+ * @return Pointer to character in @a str or NULL if not found.
+ */
+char *str_chr(const char *str, char32_t ch)
+{
+	char32_t acc;
+	size_t off = 0;
+	size_t last = 0;
+
+	while ((acc = str_decode(str, &off, STR_NO_LIMIT)) != 0) {
+		if (acc == ch)
+			return (char *) (str + last);
+		last = off;
+	}
+
+	return NULL;
+}
+
+/** Find first occurence of substring in string.
+ *
+ * @param hs  Haystack (string)
+ * @param n   Needle (substring to look for)
+ *
+ * @return Pointer to character in @a hs or @c NULL if not found.
+ */
+char *str_str(const char *hs, const char *n)
+{
+	size_t off = 0;
+
+	if (str_lcmp(hs, n, str_length(n)) == 0)
+		return (char *)hs;
+
+	while (str_decode(hs, &off, STR_NO_LIMIT) != 0) {
+		if (str_lcmp(hs + off, n, str_length(n)) == 0)
+			return (char *)(hs + off);
+	}
+
+	return NULL;
+}
+
+/** Removes specified trailing characters from a string.
+ *
+ * @param str String to remove from.
+ * @param ch  Character to remove.
+ */
+void str_rtrim(char *str, char32_t ch)
+{
+	size_t off = 0;
+	size_t pos = 0;
+	char32_t c;
+	bool update_last_chunk = true;
+	char *last_chunk = NULL;
+
+	while ((c = str_decode(str, &off, STR_NO_LIMIT))) {
+		if (c != ch) {
+			update_last_chunk = true;
+			last_chunk = NULL;
+		} else if (update_last_chunk) {
+			update_last_chunk = false;
+			last_chunk = (str + pos);
+		}
+		pos = off;
+	}
+
+	if (last_chunk)
+		*last_chunk = '\0';
+}
+
+/** Removes specified leading characters from a string.
+ *
+ * @param str String to remove from.
+ * @param ch  Character to remove.
+ */
+void str_ltrim(char *str, char32_t ch)
+{
+	char32_t acc;
+	size_t off = 0;
+	size_t pos = 0;
+	size_t str_sz = str_size(str);
+
+	while ((acc = str_decode(str, &off, STR_NO_LIMIT)) != 0) {
+		if (acc != ch)
+			break;
+		else
+			pos = off;
+	}
+
+	if (pos > 0) {
+		memmove(str, &str[pos], str_sz - pos);
+		pos = str_sz - pos;
+		str[pos] = '\0';
+	}
+}
+
+/** Find last occurence of character in string.
+ *
+ * @param str String to search.
+ * @param ch  Character to look for.
+ *
+ * @return Pointer to character in @a str or NULL if not found.
+ */
+char *str_rchr(const char *str, char32_t ch)
+{
+	char32_t acc;
+	size_t off = 0;
+	size_t last = 0;
+	const char *res = NULL;
+
+	while ((acc = str_decode(str, &off, STR_NO_LIMIT)) != 0) {
+		if (acc == ch)
+			res = (str + last);
+		last = off;
+	}
+
+	return (char *) res;
+}
+
+/** Insert a wide character into a wide string.
+ *
+ * Insert a wide character into a wide string at position
+ * @a pos. The characters after the position are shifted.
+ *
+ * @param str     String to insert to.
+ * @param ch      Character to insert to.
+ * @param pos     Character index where to insert.
+ * @param max_pos Characters in the buffer.
+ *
+ * @return True if the insertion was sucessful, false if the position
+ *         is out of bounds.
+ *
+ */
+bool wstr_linsert(char32_t *str, char32_t ch, size_t pos, size_t max_pos)
+{
+	size_t len = wstr_length(str);
+
+	if ((pos > len) || (pos + 1 > max_pos))
+		return false;
+
+	size_t i;
+	for (i = len; i + 1 > pos; i--)
+		str[i + 1] = str[i];
+
+	str[pos] = ch;
+
+	return true;
+}
+
+/** Remove a wide character from a wide string.
+ *
+ * Remove a wide character from a wide string at position
+ * @a pos. The characters after the position are shifted.
+ *
+ * @param str String to remove from.
+ * @param pos Character index to remove.
+ *
+ * @return True if the removal was sucessful, false if the position
+ *         is out of bounds.
+ *
+ */
+bool wstr_remove(char32_t *str, size_t pos)
+{
+	size_t len = wstr_length(str);
+
+	if (pos >= len)
+		return false;
+
+	size_t i;
+	for (i = pos + 1; i <= len; i++)
+		str[i - 1] = str[i];
+
+	return true;
+}
+
+/** Duplicate string.
+ *
+ * Allocate a new string and copy characters from the source
+ * string into it. The duplicate string is allocated via sleeping
+ * malloc(), thus this function can sleep in no memory conditions.
+ *
+ * The allocation cannot fail and the return value is always
+ * a valid pointer. The duplicate string is always a well-formed
+ * null-terminated UTF-8 string, but it can differ from the source
+ * string on the byte level.
+ *
+ * @param src Source string.
+ *
+ * @return Duplicate string.
+ *
+ */
+char *str_dup(const char *src)
+{
+	size_t size = str_size(src) + 1;
+	char *dest = malloc(size);
+	if (!dest)
+		return NULL;
+
+	str_cpy(dest, size, src);
+	return dest;
+}
+
+/** Duplicate string with size limit.
+ *
+ * Allocate a new string and copy up to @max_size bytes from the source
+ * string into it. The duplicate string is allocated via sleeping
+ * malloc(), thus this function can sleep in no memory conditions.
+ * No more than @max_size + 1 bytes is allocated, but if the size
+ * occupied by the source string is smaller than @max_size + 1,
+ * less is allocated.
+ *
+ * The allocation cannot fail and the return value is always
+ * a valid pointer. The duplicate string is always a well-formed
+ * null-terminated UTF-8 string, but it can differ from the source
+ * string on the byte level.
+ *
+ * @param src Source string.
+ * @param n   Maximum number of bytes to duplicate.
+ *
+ * @return Duplicate string.
+ *
+ */
+char *str_ndup(const char *src, size_t n)
+{
+	size_t size = str_size(src);
+	if (size > n)
+		size = n;
+
+	char *dest = malloc(size + 1);
+	if (!dest)
+		return NULL;
+
+	str_ncpy(dest, size + 1, src, size);
+	return dest;
+}
+
+/** Split string by delimiters.
+ *
+ * @param s             String to be tokenized. May not be NULL.
+ * @param delim		String with the delimiters.
+ * @param next		Variable which will receive the pointer to the
+ *                      continuation of the string following the first
+ *                      occurrence of any of the delimiter characters.
+ *                      May be NULL.
+ * @return              Pointer to the prefix of @a s before the first
+ *                      delimiter character. NULL if no such prefix
+ *                      exists.
+ */
+char *str_tok(char *s, const char *delim, char **next)
+{
+	char *start, *end;
+
+	if (!s)
+		return NULL;
+
+	size_t len = str_size(s);
+	size_t cur;
+	size_t tmp;
+	char32_t ch;
+
+	/* Skip over leading delimiters. */
+	tmp = 0;
+	cur = 0;
+	while ((ch = str_decode(s, &tmp, len)) && str_chr(delim, ch))
+		cur = tmp;
+	start = &s[cur];
+
+	/* Skip over token characters. */
+	tmp = cur;
+	while ((ch = str_decode(s, &tmp, len)) && !str_chr(delim, ch))
+		cur = tmp;
+	end = &s[cur];
+	if (next)
+		*next = (ch ? &s[tmp] : &s[cur]);
+
+	if (start == end)
+		return NULL;	/* No more tokens. */
+
+	/* Overwrite delimiter with NULL terminator. */
+	*end = '\0';
+	return start;
+}
+
+void order_suffix(const uint64_t val, uint64_t *rv, char *suffix)
+{
+	if (val > UINT64_C(10000000000000000000)) {
+		*rv = val / UINT64_C(1000000000000000000);
+		*suffix = 'Z';
+	} else if (val > UINT64_C(1000000000000000000)) {
+		*rv = val / UINT64_C(1000000000000000);
+		*suffix = 'E';
+	} else if (val > UINT64_C(1000000000000000)) {
+		*rv = val / UINT64_C(1000000000000);
+		*suffix = 'T';
+	} else if (val > UINT64_C(1000000000000)) {
+		*rv = val / UINT64_C(1000000000);
+		*suffix = 'G';
+	} else if (val > UINT64_C(1000000000)) {
+		*rv = val / UINT64_C(1000000);
+		*suffix = 'M';
+	} else if (val > UINT64_C(1000000)) {
+		*rv = val / UINT64_C(1000);
+		*suffix = 'k';
+	} else {
+		*rv = val;
+		*suffix = ' ';
+	}
+}
+
+void bin_order_suffix(const uint64_t val, uint64_t *rv, const char **suffix,
+    bool fixed)
+{
+	if (val > UINT64_C(1152921504606846976)) {
+		*rv = val / UINT64_C(1125899906842624);
+		*suffix = "EiB";
+	} else if (val > UINT64_C(1125899906842624)) {
+		*rv = val / UINT64_C(1099511627776);
+		*suffix = "TiB";
+	} else if (val > UINT64_C(1099511627776)) {
+		*rv = val / UINT64_C(1073741824);
+		*suffix = "GiB";
+	} else if (val > UINT64_C(1073741824)) {
+		*rv = val / UINT64_C(1048576);
+		*suffix = "MiB";
+	} else if (val > UINT64_C(1048576)) {
+		*rv = val / UINT64_C(1024);
+		*suffix = "KiB";
+	} else {
+		*rv = val;
+		if (fixed)
+			*suffix = "B  ";
+		else
+			*suffix = "B";
+	}
+}
+
+/** @}
+ */
Index: common/str_error.c
===================================================================
--- common/str_error.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/str_error.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2010 Martin Decky
+ * Copyright (c) 2017 CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <str_error.h>
+
+#include <errno.h>
+#include <stddef.h>
+
+
+/*
+ * The arrays below are automatically generated from the same file that
+ * errno.h constants are generated from. Triple-include of the same list
+ * with redefinitions of __errno() macro are used to ensure that the
+ * information cannot get out of synch. This is inpired by musl libc.
+ */
+
+#undef __errno_entry
+#define __errno_entry(name, num, desc) name,
+
+static const errno_t err_num[] = {
+#include <abi/errno.in>
+};
+
+#undef __errno_entry
+#define __errno_entry(name, num, desc) #name,
+
+static const char *err_name[] = {
+#include <abi/errno.in>
+};
+
+#undef __errno_entry
+#define __errno_entry(name, num, desc) "[" #name "] " desc,
+
+static const char *err_desc[] = {
+#include <abi/errno.in>
+};
+
+/* Returns index corresponding to the given errno, or -1 if not found. */
+static int find_errno(errno_t e)
+{
+	/*
+	 * Just a dumb linear search.
+	 * There are too few entries to warrant anything smarter.
+	 */
+
+	int len = sizeof(err_num) / sizeof(errno_t);
+
+	for (int i = 0; i < len; i++) {
+		if (err_num[i] == e) {
+			return i;
+		}
+	}
+
+	return -1;
+}
+
+const char *str_error_name(errno_t e)
+{
+	int i = find_errno(e);
+
+	if (i >= 0) {
+		return err_name[i];
+	}
+
+	return NULL;
+}
+
+const char *str_error(errno_t e)
+{
+	int i = find_errno(e);
+
+	if (i >= 0) {
+		return err_desc[i];
+	}
+
+	return NULL;
+}
Index: common/strtol.c
===================================================================
--- common/strtol.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ common/strtol.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,562 @@
+/*
+ * Copyright (c) 2005 Martin Decky
+ * Copyright (c) 2008 Jiri Svoboda
+ * Copyright (c) 2011 Martin Sucha
+ * Copyright (c) 2011 Oleg Romanenko
+ * Copyright (c) 2011 Jiri Zarevucky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <str.h>
+
+// FIXME: The original HelenOS functions return EOVERFLOW instead
+//        of ERANGE. It's a pointless distinction from standard functions,
+//        so we should change that. Beware the callers though.
+
+// TODO: more unit tests
+
+static inline int _digit_value(int c)
+{
+	if (isdigit(c)) {
+		return c - '0';
+	} else if (islower(c)) {
+		return c - 'a' + 10;
+	} else if (isupper(c)) {
+		return c - 'A' + 10;
+	}
+	return INT_MAX;
+}
+
+/*
+ * FIXME: workaround for GCC "optimizing" the overflow check
+ * into soft-emulated 128b multiplication using `__multi3`,
+ * which we don't currently implement.
+ */
+__attribute__((noinline)) static uintmax_t _max_value(int base)
+{
+	return UINTMAX_MAX / base;
+}
+
+static inline int _prefixbase(const char *restrict *nptrptr, bool nonstd)
+{
+	const char *nptr = *nptrptr;
+
+	if (nptr[0] != '0')
+		return 10;
+
+	if (nptr[1] == 'x' || nptr[1] == 'X') {
+		if (_digit_value(nptr[2]) < 16) {
+			*nptrptr += 2;
+			return 16;
+		}
+	}
+
+	if (nonstd) {
+		switch (nptr[1]) {
+		case 'b':
+		case 'B':
+			if (_digit_value(nptr[2]) < 2) {
+				*nptrptr += 2;
+				return 2;
+			}
+			break;
+		case 'o':
+		case 'O':
+			if (_digit_value(nptr[2]) < 8) {
+				*nptrptr += 2;
+				return 8;
+			}
+			break;
+		case 'd':
+		case 'D':
+		case 't':
+		case 'T':
+			if (_digit_value(nptr[2]) < 10) {
+				*nptrptr += 2;
+				return 10;
+			}
+			break;
+		}
+	}
+
+	return 8;
+}
+
+static inline uintmax_t _strtoumax(
+    const char *restrict nptr, char **restrict endptr, int base,
+    bool *restrict sgn, errno_t *err, bool nonstd)
+{
+	assert(nptr != NULL);
+	assert(sgn != NULL);
+
+	const char *first = nptr;
+
+	/* Skip leading whitespace. */
+
+	while (isspace(*nptr)) {
+		nptr++;
+	}
+
+	/* Parse sign, if any. */
+
+	switch (*nptr) {
+	case '-':
+		*sgn = true;
+		nptr++;
+		break;
+	case '+':
+		nptr++;
+		break;
+	}
+
+	/* Figure out the base. */
+
+	if (base == 0)
+		base = _prefixbase(&nptr, nonstd);
+
+	if (base == 16 && !nonstd) {
+		/*
+		 * Standard strto* functions allow hexadecimal prefix to be
+		 * present when base is explicitly set to 16.
+		 * Our nonstandard str_* functions don't allow it.
+		 * I don't know if that is intended, just matching the original
+		 * functionality here.
+		 */
+
+		if (nptr[0] == '0' && (nptr[1] == 'x' || nptr[1] == 'X') &&
+		    _digit_value(nptr[2]) < base)
+			nptr += 2;
+	}
+
+	if (base < 2 || base > 36) {
+		*err = EINVAL;
+		return 0;
+	}
+
+	/* Must be at least one digit. */
+
+	if (_digit_value(*nptr) >= base) {
+		/* No digits on input. */
+		if (endptr != NULL)
+			*endptr = (char *) first;
+		return 0;
+	}
+
+	/* Read the value.  */
+
+	uintmax_t result = 0;
+	uintmax_t max = _max_value(base);
+	int digit;
+
+	while (digit = _digit_value(*nptr), digit < base) {
+		if (result > max ||
+		    __builtin_add_overflow(result * base, digit, &result)) {
+
+			*err = nonstd ? EOVERFLOW : ERANGE;
+			result = UINTMAX_MAX;
+			break;
+		}
+
+		nptr++;
+	}
+
+	/* Set endptr. */
+
+	if (endptr != NULL) {
+		/*
+		 * Move the pointer to the end of the number,
+		 * in case it isn't there already.
+		 * This can happen when the number has legal formatting,
+		 * but is out of range of the target type.
+		 */
+		while (_digit_value(*nptr) < base) {
+			nptr++;
+		}
+
+		*endptr = (char *) nptr;
+	}
+
+	return result;
+}
+
+static inline intmax_t _strtosigned(const char *nptr, char **endptr, int base,
+    intmax_t min, intmax_t max, errno_t *err, bool nonstd)
+{
+	bool sgn = false;
+	uintmax_t number = _strtoumax(nptr, endptr, base, &sgn, err, nonstd);
+
+	if (number > (uintmax_t) max) {
+		if (sgn && (number - 1 == (uintmax_t) max)) {
+			return min;
+		}
+
+		*err = nonstd ? EOVERFLOW : ERANGE;
+		return (sgn ? min : max);
+	}
+
+	return (sgn ? -number : number);
+}
+
+static inline uintmax_t _strtounsigned(const char *nptr, char **endptr, int base,
+    uintmax_t max, errno_t *err, bool nonstd)
+{
+	bool sgn = false;
+	uintmax_t number = _strtoumax(nptr, endptr, base, &sgn, err, nonstd);
+
+	if (nonstd && sgn) {
+		/* Do not allow negative values */
+		*err = EINVAL;
+		return 0;
+	}
+
+	if (number > max) {
+		*err = nonstd ? EOVERFLOW : ERANGE;
+		return max;
+	}
+
+	return (sgn ? -number : number);
+}
+
+/** Convert initial part of string to long int according to given base.
+ * The number may begin with an arbitrary number of whitespaces followed by
+ * optional sign (`+' or `-'). If the base is 0 or 16, the prefix `0x' may be
+ * inserted and the number will be taken as hexadecimal one. If the base is 0
+ * and the number begin with a zero, number will be taken as octal one (as with
+ * base 8). Otherwise the base 0 is taken as decimal.
+ *
+ * @param nptr		Pointer to string.
+ * @param[out] endptr	If not NULL, function stores here pointer to the first
+ * 			invalid character.
+ * @param base		Zero or number between 2 and 36 inclusive.
+ * @return		Result of conversion.
+ */
+long strtol(const char *nptr, char **endptr, int base)
+{
+#if !__STDC_HOSTED__
+	errno_t errno;
+#endif
+
+	return _strtosigned(nptr, endptr, base, LONG_MIN, LONG_MAX, &errno, false);
+}
+
+/** Convert initial part of string to unsigned long according to given base.
+ * The number may begin with an arbitrary number of whitespaces followed by
+ * optional sign (`+' or `-'). If the base is 0 or 16, the prefix `0x' may be
+ * inserted and the number will be taken as hexadecimal one. If the base is 0
+ * and the number begin with a zero, number will be taken as octal one (as with
+ * base 8). Otherwise the base 0 is taken as decimal.
+ *
+ * @param nptr		Pointer to string.
+ * @param[out] endptr	If not NULL, function stores here pointer to the first
+ * 			invalid character
+ * @param base		Zero or number between 2 and 36 inclusive.
+ * @return		Result of conversion.
+ */
+unsigned long strtoul(const char *nptr, char **endptr, int base)
+{
+#if !__STDC_HOSTED__
+	errno_t errno;
+#endif
+
+	return _strtounsigned(nptr, endptr, base, ULONG_MAX, &errno, false);
+}
+
+long long strtoll(const char *nptr, char **endptr, int base)
+{
+#if !__STDC_HOSTED__
+	errno_t errno;
+#endif
+
+	return _strtosigned(nptr, endptr, base, LLONG_MIN, LLONG_MAX, &errno, false);
+}
+
+unsigned long long strtoull(const char *nptr, char **endptr, int base)
+{
+#if !__STDC_HOSTED__
+	errno_t errno;
+#endif
+
+	return _strtounsigned(nptr, endptr, base, ULLONG_MAX, &errno, false);
+}
+
+intmax_t strtoimax(const char *nptr, char **endptr, int base)
+{
+#if !__STDC_HOSTED__
+	errno_t errno;
+#endif
+
+	return _strtosigned(nptr, endptr, base, INTMAX_MIN, INTMAX_MAX, &errno, false);
+}
+
+uintmax_t strtoumax(const char *nptr, char **endptr, int base)
+{
+#if !__STDC_HOSTED__
+	errno_t errno;
+#endif
+
+	return _strtounsigned(nptr, endptr, base, UINTMAX_MAX, &errno, false);
+}
+
+int atoi(const char *nptr)
+{
+	return (int)strtol(nptr, NULL, 10);
+}
+
+long atol(const char *nptr)
+{
+	return strtol(nptr, NULL, 10);
+}
+
+long long atoll(const char *nptr)
+{
+	return strtoll(nptr, NULL, 10);
+}
+
+/** Convert string to uint8_t.
+ *
+ * @param nptr   Pointer to string.
+ * @param endptr If not NULL, pointer to the first invalid character
+ *               is stored here.
+ * @param base   Zero or number between 2 and 36 inclusive.
+ * @param strict Do not allow any trailing characters.
+ * @param result Result of the conversion.
+ *
+ * @return EOK if conversion was successful.
+ *
+ */
+errno_t str_uint8_t(const char *nptr, const char **endptr, unsigned int base,
+    bool strict, uint8_t *result)
+{
+	assert(result != NULL);
+
+	errno_t rc = EOK;
+	char *lendptr = (char *) nptr;
+
+	uintmax_t r = _strtounsigned(nptr, &lendptr, base, UINT8_MAX, &rc, true);
+
+	if (endptr)
+		*endptr = lendptr;
+
+	if (rc != EOK)
+		return rc;
+
+	if (strict && *lendptr != '\0')
+		return EINVAL;
+
+	*result = r;
+	return EOK;
+}
+
+/** Convert string to uint16_t.
+ *
+ * @param nptr   Pointer to string.
+ * @param endptr If not NULL, pointer to the first invalid character
+ *               is stored here.
+ * @param base   Zero or number between 2 and 36 inclusive.
+ * @param strict Do not allow any trailing characters.
+ * @param result Result of the conversion.
+ *
+ * @return EOK if conversion was successful.
+ *
+ */
+errno_t str_uint16_t(const char *nptr, const char **endptr, unsigned int base,
+    bool strict, uint16_t *result)
+{
+	assert(result != NULL);
+
+	errno_t rc = EOK;
+	char *lendptr = (char *) nptr;
+
+	uintmax_t r = _strtounsigned(nptr, &lendptr, base, UINT16_MAX, &rc, true);
+
+	if (endptr)
+		*endptr = lendptr;
+
+	if (rc != EOK)
+		return rc;
+
+	if (strict && *lendptr != '\0')
+		return EINVAL;
+
+	*result = r;
+	return EOK;
+}
+
+/** Convert string to uint32_t.
+ *
+ * @param nptr   Pointer to string.
+ * @param endptr If not NULL, pointer to the first invalid character
+ *               is stored here.
+ * @param base   Zero or number between 2 and 36 inclusive.
+ * @param strict Do not allow any trailing characters.
+ * @param result Result of the conversion.
+ *
+ * @return EOK if conversion was successful.
+ *
+ */
+errno_t str_uint32_t(const char *nptr, const char **endptr, unsigned int base,
+    bool strict, uint32_t *result)
+{
+	assert(result != NULL);
+
+	errno_t rc = EOK;
+	char *lendptr = (char *) nptr;
+
+	uintmax_t r = _strtounsigned(nptr, &lendptr, base, UINT32_MAX, &rc, true);
+
+	if (endptr)
+		*endptr = lendptr;
+
+	if (rc != EOK)
+		return rc;
+
+	if (strict && *lendptr != '\0')
+		return EINVAL;
+
+	*result = r;
+	return EOK;
+}
+
+/** Convert string to uint64_t.
+ *
+ * @param nptr   Pointer to string.
+ * @param endptr If not NULL, pointer to the first invalid character
+ *               is stored here.
+ * @param base   Zero or number between 2 and 36 inclusive.
+ * @param strict Do not allow any trailing characters.
+ * @param result Result of the conversion.
+ *
+ * @return EOK if conversion was successful.
+ *
+ */
+errno_t str_uint64_t(const char *nptr, const char **endptr, unsigned int base,
+    bool strict, uint64_t *result)
+{
+	assert(result != NULL);
+
+	errno_t rc = EOK;
+	char *lendptr = (char *) nptr;
+
+	uintmax_t r = _strtounsigned(nptr, &lendptr, base, UINT64_MAX, &rc, true);
+
+	if (endptr)
+		*endptr = lendptr;
+
+	if (rc != EOK)
+		return rc;
+
+	if (strict && *lendptr != '\0')
+		return EINVAL;
+
+	*result = r;
+	return EOK;
+}
+
+/** Convert string to int64_t.
+ *
+ * @param nptr   Pointer to string.
+ * @param endptr If not NULL, pointer to the first invalid character
+ *               is stored here.
+ * @param base   Zero or number between 2 and 36 inclusive.
+ * @param strict Do not allow any trailing characters.
+ * @param result Result of the conversion.
+ *
+ * @return EOK if conversion was successful.
+ *
+ */
+errno_t str_int64_t(const char *nptr, const char **endptr, unsigned int base,
+    bool strict, int64_t *result)
+{
+	assert(result != NULL);
+
+	errno_t rc = EOK;
+	char *lendptr = (char *) nptr;
+
+	intmax_t r = _strtosigned(nptr, &lendptr, base, INT64_MIN, INT64_MAX, &rc, true);
+
+	if (endptr)
+		*endptr = lendptr;
+
+	if (rc != EOK)
+		return rc;
+
+	if (strict && *lendptr != '\0')
+		return EINVAL;
+
+	*result = r;
+	return EOK;
+}
+
+/** Convert string to size_t.
+ *
+ * @param nptr   Pointer to string.
+ * @param endptr If not NULL, pointer to the first invalid character
+ *               is stored here.
+ * @param base   Zero or number between 2 and 36 inclusive.
+ * @param strict Do not allow any trailing characters.
+ * @param result Result of the conversion.
+ *
+ * @return EOK if conversion was successful.
+ *
+ */
+errno_t str_size_t(const char *nptr, const char **endptr, unsigned int base,
+    bool strict, size_t *result)
+{
+	assert(result != NULL);
+
+	errno_t rc = EOK;
+	char *lendptr = (char *) nptr;
+
+	uintmax_t r = _strtounsigned(nptr, &lendptr, base, SIZE_MAX, &rc, true);
+
+	if (endptr)
+		*endptr = lendptr;
+
+	if (rc != EOK)
+		return rc;
+
+	if (strict && *lendptr != '\0')
+		return EINVAL;
+
+	*result = r;
+	return EOK;
+}
+
+/** @}
+ */
Index: configure.sh
===================================================================
--- configure.sh	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ configure.sh	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -164,5 +164,5 @@
 sed "s:@COMPPREFIX@:$compprefix:g" "$cross_def" > crossfile || exit 1
 
-meson "${SOURCE_DIR}" '.' --cross-file crossfile || exit 1
+meson setup "${SOURCE_DIR}" '.' --cross-file crossfile || exit 1
 
 echo
Index: contrib/qemu/build-from-scratch.sh
===================================================================
--- contrib/qemu/build-from-scratch.sh	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ contrib/qemu/build-from-scratch.sh	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -29,5 +29,5 @@
 #
 
-VERSION=6.0.0
+VERSION=8.0.0
 BASENAME=qemu-${VERSION}
 BASENAME_MASTER=qemu-master
@@ -35,5 +35,5 @@
 SOURCEDIR=${BASENAME}
 URL=https://download.qemu.org/${TARBALL}
-REPO=git://git.qemu.org/qemu.git
+REPO=git@github.com:qemu/qemu.git
 
 OPENSPARC_TARBALL="OpenSPARCT1_Arch.1.5.tar.bz2"
@@ -42,4 +42,5 @@
 ARCHIVE_PREFIX="./S10image"
 BINARIES="1up-hv.bin 1up-md.bin nvram1 openboot.bin q.bin reset.bin"
+INSTALL_PREFIX="$HOME/.local"
 
 echo "==== Downloading OpenSPARC archive ===="
@@ -77,6 +78,6 @@
 echo "==== Installing OpenSPARC binaries ===="
 
-sudo install -d /usr/local/opensparc/image
-sudo install -m 0444 binaries/* /usr/local/opensparc/image
+install -d "$INSTALL_PREFIX/opensparc/image"
+install -m 0444 binaries/* "$INSTALL_PREFIX/opensparc/image"
 
 echo "==== Obtaining QEMU sources ===="
@@ -94,5 +95,5 @@
 	fi
 
-	gpg --verify ${TARBALL}.sig ${TARBALL}
+	gpg --auto-key-retrieve --verify ${TARBALL}.sig ${TARBALL}
 	if [ $? -ne 0 ]; then
 		echo Unable to verify the signature
@@ -100,5 +101,6 @@
 	fi
 
-	tar xvfj ${TARBALL}
+	echo "==== Decompressing QEMU sources ===="
+	tar xfj ${TARBALL}
 	cd ${SOURCEDIR}
 fi
@@ -106,12 +108,11 @@
 echo "==== Configuring QEMU ===="
 
-./configure --target-list=i386-softmmu,x86_64-softmmu,arm-softmmu,aarch64-softmmu,ppc-softmmu,sparc64-softmmu,mips-softmmu,mipsel-softmmu --audio-drv-list=pa
+./configure --target-list=i386-softmmu,x86_64-softmmu,arm-softmmu,aarch64-softmmu,ppc-softmmu,sparc64-softmmu,mips-softmmu,mipsel-softmmu --enable-gtk --enable-vte --enable-kvm --enable-curses --enable-opengl --enable-slirp --enable-pa --audio-drv-list=pa --prefix="$INSTALL_PREFIX" || exit 1
 
 echo "==== Building QEMU ===="
 
-make -j 4
+make -j`nproc` || exit 1
 
 echo "==== Installing QEMU ===="
 
-sudo make install
-
+make install
Index: defaults/ia64/Makefile.config
===================================================================
--- defaults/ia64/Makefile.config	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ defaults/ia64/Makefile.config	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -51,3 +51,3 @@
 
 # Optimization level
-OPTIMIZATION = 3
+OPTIMIZATION = 2
Index: kernel/arch/abs32le/_link.ld.in
===================================================================
--- kernel/arch/abs32le/_link.ld.in	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/abs32le/_link.ld.in	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -3,26 +3,55 @@
 		kernel_load_address = .;
 		ktext_start = .;
-		*(.text);
+		*(.text .text.*);
 		ktext_end = .;
 	}
 	.data : {
 		kdata_start = .;
-		*(.data);                       /* initialized data */
-		*(.bss);                        /* uninitialized static variables */
+		*(.data .data.*);                       /* initialized data */
+		*(.bss .bss.*);                        /* uninitialized static variables */
 		*(COMMON);                      /* global variables */
 
-		*(.rodata*);
-		*(.sdata);
-		*(.reginfo);
-		. = ALIGN(8);
-		symbol_table = .;
-		*(symtab.*);
+		*(.rodata .rodata.*);
+		*(.eh_frame .eh_frame.*);       /* stack unwinding data */
+		*(.eh_frame_hdr .eh_frame_hdr.*);
+		*(.sdata .sdata.*);
 	}
 	.sbss : {
-		*(.sbss);
-		*(.scommon);
+		*(.sbss .sbss.*);
+		*(.scommon .scommon.*);
 	}
 
 	kdata_end = .;
+
+	.comment 0 : { *(.comment); }
+	.debug_abbrev 0 : { *(.debug_abbrev); }
+	.debug_abbrev.dwo 0 : { *(	.debug_abbrev.dwo); }
+	.debug_addr 0 : { *(.debug_addr); }
+	.debug_aranges 0 : { *(.debug_aranges); }
+	.debug_cu_index 0 : { *(.debug_cu_index); }
+	.debug_frame 0 : { *(.debug_frame); }
+	.debug_frame_hdr 0 : { *(.debug_frame_hdr); }
+	.debug_info 0 : { *(.debug_info); }
+	.debug_info.dwo 0 : { *(.debug_info.dwo); }
+	.debug_line 0 : { *(.debug_line); }
+	.debug_line.dwo 0 : { *(.debug_line.dwo); }
+	.debug_line_str 0 : { *(.debug_line_str); }
+	.debug_loc 0 : { *(.debug_loc); }
+	.debug_loclists 0 : { *(.debug_loclists); }
+	.debug_loclists.dwo 0 : { *(.debug_loclists.dwo); }
+	.debug_macinfo 0 : { *(.debug_macinfo); }
+	.debug_macro 0 : { *(.debug_macro); }
+	.debug_macro.dwo 0 : { *(.debug_macro.dwo); }
+	.debug_names 0 : { *(.debug_names); }
+	.debug_pubnames 0 : { *(.debug_pubnames); }
+	.debug_pubtypes 0 : { *(.debug_pubtypes); }
+	.debug_ranges 0 : { *(.debug_ranges); }
+	.debug_rnglists 0 : { *(.debug_rnglists); }
+	.debug_str 0 : { *(.debug_str); }
+	.debug_str.dwo 0 : { *(.debug_str.dwo); }
+	.debug_str_offsets 0 : { *(.debug_str_offsets); }
+	.debug_str_offsets.dwo 0 : { *(.debug_str_offsets.dwo); }
+	.debug_tu_index 0 : { *(.debug_tu_index); }
+	.debug_types 0 : { *(.debug_types); }
 
 	/DISCARD/ : {
Index: kernel/arch/abs32le/include/arch/asm.h
===================================================================
--- kernel/arch/abs32le/include/arch/asm.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/abs32le/include/arch/asm.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -67,4 +67,13 @@
 }
 
+_NO_TRACE static inline void cpu_spin_hint(void)
+{
+	/*
+	 * Some ISAs have a special instruction for the body of a busy wait loop,
+	 * such as in spinlock and the like. Using it allows the CPU to optimize
+	 * its operation. For an example, see the "pause" instruction on x86.
+	 */
+}
+
 _NO_TRACE static inline void pio_write_8(ioport8_t *port, uint8_t val)
 {
@@ -188,4 +197,15 @@
 }
 
+/** Enables interrupts and blocks until an interrupt arrives,
+ * atomically if possible on target architecture.
+ * Disables interrupts again before returning to caller.
+ */
+_NO_TRACE static inline void cpu_interruptible_sleep(void)
+{
+	interrupts_enable();
+	cpu_sleep();
+	interrupts_disable();
+}
+
 #endif
 
Index: kernel/arch/abs32le/include/arch/context.h
===================================================================
--- kernel/arch/abs32le/include/arch/context.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/abs32le/include/arch/context.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -49,5 +49,4 @@
 	uintptr_t sp;
 	uintptr_t pc;
-	ipl_t ipl;
 } context_t;
 
Index: kernel/arch/abs32le/include/arch/context_offset.h
===================================================================
--- kernel/arch/abs32le/include/arch/context_offset.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/abs32le/include/arch/context_offset.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -37,5 +37,4 @@
 
 #define OFFSET_PC  0x00
-#define OFFSET_IPL 0x04
 
 #endif
Index: kernel/arch/abs32le/include/arch/fpu_context.h
===================================================================
--- kernel/arch/abs32le/include/arch/fpu_context.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/abs32le/include/arch/fpu_context.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,6 +36,4 @@
 #define KERN_abs32le_FPU_CONTEXT_H_
 
-#define FPU_CONTEXT_ALIGN  16
-
 /*
  * On real hardware this stores the FPU registers
Index: kernel/arch/amd64/_link.ld.in
===================================================================
--- kernel/arch/amd64/_link.ld.in	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/amd64/_link.ld.in	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -17,42 +17,75 @@
 	.unmapped (BOOT_OFFSET + SIZEOF_HEADERS): AT (BOOT_OFFSET + SIZEOF_HEADERS) {
 		unmapped_start = .;
-		*(K_TEXT_START);
-		*(K_DATA_START);
-		*(K_INI_PTLS);
+		KEEP(*(K_TEXT_START));
+		KEEP(*(K_DATA_START));
+		KEEP(*(K_INI_PTLS));
 		unmapped_end = .;
 	}
 
-	.mapped (PA2KA(BOOT_OFFSET) + SIZEOF_HEADERS + SIZEOF(.unmapped)) : AT (BOOT_OFFSET + SIZEOF_HEADERS + SIZEOF(.unmapped)) {
+	.text (PA2KA(BOOT_OFFSET) + SIZEOF_HEADERS + SIZEOF(.unmapped)) : AT (BOOT_OFFSET + SIZEOF_HEADERS + SIZEOF(.unmapped)) {
 		ktext_start = .;
 		*(.text .text.*);
 		ktext_end = .;
+	}
 
+	/* stack unwinding data */
+	.eh_frame_hdr : {
+		eh_frame_hdr_start = .;
+		*(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*);
+		eh_frame_hdr_end = .;
+	}
+	.eh_frame : {
+		eh_frame_start = .;
+		KEEP(*(.eh_frame .eh_frame.*));
+		eh_frame_end = .;
+	}
+
+	.data : {
 		kdata_start = .;
-		*(.data);              /* initialized data */
-		*(.rodata .rodata.*);  /* string literals */
-		*(COMMON);      /* global variables */
+		*(.rodata .rodata.*);  /* read-only global variables */
+		*(.data .data.*);      /* non-zero initialized global variables */
 
-		/* bss can't be omitted from the ELF image. */
-		*(.bss);        /* uninitialized static variables */
+		/*
+		 * When .bss is not physically present in the ELF file (MemSz > FileSz)
+		 * the kernel crashes during early boot. Not sure which part of the
+		 * boot process is to blame, for now just keep .bss packaged with .data
+		 * so that FileSz == MemSz.
+		 */
 
-		. = ALIGN(8);
-		symbol_table = .;
-		*(symtab.*);    /* Symbol table, must be LAST symbol!*/
-
+		 *(.bss .bss.*);        /* uninitialized global variables */
+		*(COMMON);             /* non-`static` global variables without an extern declaration */
 		kdata_end = .;
 	}
 
-#ifdef CONFIG_LINE_DEBUG
 	.comment 0 : { *(.comment); }
 	.debug_abbrev 0 : { *(.debug_abbrev); }
+	.debug_abbrev.dwo 0 : { *(	.debug_abbrev.dwo); }
+	.debug_addr 0 : { *(.debug_addr); }
 	.debug_aranges 0 : { *(.debug_aranges); }
+	.debug_cu_index 0 : { *(.debug_cu_index); }
+	.debug_frame 0 : { *(.debug_frame); }
+	.debug_frame_hdr 0 : { *(.debug_frame_hdr); }
 	.debug_info 0 : { *(.debug_info); }
+	.debug_info.dwo 0 : { *(.debug_info.dwo); }
 	.debug_line 0 : { *(.debug_line); }
+	.debug_line.dwo 0 : { *(.debug_line.dwo); }
+	.debug_line_str 0 : { *(.debug_line_str); }
 	.debug_loc 0 : { *(.debug_loc); }
+	.debug_loclists 0 : { *(.debug_loclists); }
+	.debug_loclists.dwo 0 : { *(.debug_loclists.dwo); }
+	.debug_macinfo 0 : { *(.debug_macinfo); }
+	.debug_macro 0 : { *(.debug_macro); }
+	.debug_macro.dwo 0 : { *(.debug_macro.dwo); }
+	.debug_names 0 : { *(.debug_names); }
 	.debug_pubnames 0 : { *(.debug_pubnames); }
 	.debug_pubtypes 0 : { *(.debug_pubtypes); }
 	.debug_ranges 0 : { *(.debug_ranges); }
+	.debug_rnglists 0 : { *(.debug_rnglists); }
 	.debug_str 0 : { *(.debug_str); }
-#endif
+	.debug_str.dwo 0 : { *(.debug_str.dwo); }
+	.debug_str_offsets 0 : { *(.debug_str_offsets); }
+	.debug_str_offsets.dwo 0 : { *(.debug_str_offsets.dwo); }
+	.debug_tu_index 0 : { *(.debug_tu_index); }
+	.debug_types 0 : { *(.debug_types); }
 
 	/DISCARD/ : {
Index: kernel/arch/amd64/include/arch/asm.h
===================================================================
--- kernel/arch/amd64/include/arch/asm.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/amd64/include/arch/asm.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -43,8 +43,18 @@
 #define IO_SPACE_BOUNDARY	((void *) (64 * 1024))
 
-_NO_TRACE static inline void cpu_sleep(void)
-{
-	asm volatile (
+/** Enables interrupts and blocks until an interrupt arrives,
+ * atomically if possible on target architecture.
+ * Disables interrupts again before returning to caller.
+ */
+_NO_TRACE static inline void cpu_interruptible_sleep()
+{
+	/*
+	 * On x86, "sti" enables interrupts after the next instruction.
+	 * Therefore, this sequence is guaranteed to be atomic.
+	 */
+	asm volatile (
+	    "sti\n"
 	    "hlt\n"
+	    "cli\n"
 	);
 }
@@ -57,4 +67,11 @@
 		);
 	}
+}
+
+_NO_TRACE static inline void cpu_spin_hint(void)
+{
+	asm volatile (
+	    "pause\n"
+	);
 }
 
Index: kernel/arch/amd64/include/arch/context_struct.h
===================================================================
--- kernel/arch/amd64/include/arch/context_struct.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/amd64/include/arch/context_struct.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -39,6 +39,5 @@
 #define CONTEXT_OFFSET_R15  0x38
 #define CONTEXT_OFFSET_TP   0x40
-#define CONTEXT_OFFSET_IPL  0x48
-#define CONTEXT_SIZE        0x50
+#define CONTEXT_SIZE        0x48
 
 #ifndef __ASSEMBLER__
@@ -60,5 +59,4 @@
 	uint64_t r15;
 	uint64_t tp;
-	ipl_t ipl;
 } context_t;
 
Index: kernel/arch/amd64/include/arch/mm/page.h
===================================================================
--- kernel/arch/amd64/include/arch/mm/page.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/amd64/include/arch/mm/page.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -93,16 +93,11 @@
 /* Get PTE address accessors for each level. */
 #define GET_PTL1_ADDRESS_ARCH(ptl0, i) \
-	((pte_t *) ((((uint64_t) ((pte_t *) (ptl0))[(i)].addr_12_31) << 12) | \
-	    (((uint64_t) ((pte_t *) (ptl0))[(i)].addr_32_51) << 32)))
+	((pte_t *) (((uint64_t) ((pte_t *) (ptl0))[(i)].addr_12_51) << 12))
 #define GET_PTL2_ADDRESS_ARCH(ptl1, i) \
-	((pte_t *) ((((uint64_t) ((pte_t *) (ptl1))[(i)].addr_12_31) << 12) | \
-	    (((uint64_t) ((pte_t *) (ptl1))[(i)].addr_32_51) << 32)))
+	((pte_t *) (((uint64_t) ((pte_t *) (ptl1))[(i)].addr_12_51) << 12))
 #define GET_PTL3_ADDRESS_ARCH(ptl2, i) \
-	((pte_t *) ((((uint64_t) ((pte_t *) (ptl2))[(i)].addr_12_31) << 12) | \
-	    (((uint64_t) ((pte_t *) (ptl2))[(i)].addr_32_51) << 32)))
+	((pte_t *) (((uint64_t) ((pte_t *) (ptl2))[(i)].addr_12_51) << 12))
 #define GET_FRAME_ADDRESS_ARCH(ptl3, i) \
-	((uintptr_t *) \
-	    ((((uint64_t) ((pte_t *) (ptl3))[(i)].addr_12_31) << 12) | \
-	    (((uint64_t) ((pte_t *) (ptl3))[(i)].addr_32_51) << 32)))
+	((uintptr_t *) (((uint64_t) ((pte_t *) (ptl3))[(i)].addr_12_51) << 12))
 
 /* Set PTE address accessors for each level. */
@@ -154,6 +149,5 @@
 	((p)->present != 0)
 #define PTE_GET_FRAME_ARCH(p) \
-	((((uintptr_t) (p)->addr_12_31) << 12) | \
-	    ((uintptr_t) (p)->addr_32_51 << 32))
+	(((uintptr_t) (p)->addr_12_51) << 12)
 #define PTE_WRITABLE_ARCH(p) \
 	((p)->writeable != 0)
@@ -198,10 +192,10 @@
 	unsigned int accessed : 1;
 	unsigned int dirty : 1;
-	unsigned int unused : 1;
+	unsigned int pat : 1;
 	unsigned int global : 1;
 	unsigned int soft_valid : 1;  /**< Valid content even if present bit is cleared. */
 	unsigned int avl : 2;
-	unsigned int addr_12_31 : 30;
-	unsigned int addr_32_51 : 21;
+	unsigned long addr_12_51 : 40;
+	unsigned int reserved : 11;
 	unsigned int no_execute : 1;
 } __attribute__((packed)) pte_t;
@@ -217,5 +211,6 @@
 	    p->writeable << PAGE_WRITE_SHIFT |
 	    (!p->no_execute) << PAGE_EXEC_SHIFT |
-	    p->global << PAGE_GLOBAL_SHIFT);
+	    p->global << PAGE_GLOBAL_SHIFT |
+	    p->page_write_through << PAGE_WRITE_COMBINE_SHIFT);
 }
 
@@ -224,6 +219,5 @@
 	pte_t *p = &pt[i];
 
-	p->addr_12_31 = (a >> 12) & UINT32_C(0xfffff);
-	p->addr_32_51 = a >> 32;
+	p->addr_12_51 = (a >> 12) & UINT64_C(0xffffffffff);
 }
 
@@ -232,5 +226,4 @@
 	pte_t *p = &pt[i];
 
-	p->page_cache_disable = !(flags & PAGE_CACHEABLE);
 	p->present = !(flags & PAGE_NOT_PRESENT);
 	p->uaccessible = (flags & PAGE_USER) != 0;
@@ -239,4 +232,14 @@
 	p->global = (flags & PAGE_GLOBAL) != 0;
 
+	if (flags & PAGE_WRITE_COMBINE) {
+		/* We have mapped PCD+PWT bits to write-combine mode via PAT MSR. */
+		/* (If PAT is unsupported, it will default to uncached.) */
+		p->page_cache_disable = 1;
+		p->page_write_through = 1;
+	} else {
+		p->page_cache_disable = !(flags & PAGE_CACHEABLE);
+		p->page_write_through = 0;
+	}
+
 	/*
 	 * Ensure that there is at least one bit set even if the present bit is cleared.
Index: kernel/arch/amd64/include/arch/mm/pat.h
===================================================================
--- kernel/arch/amd64/include/arch/mm/pat.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ kernel/arch/amd64/include/arch/mm/pat.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2024 Jiri Svoboda
+ * Copyright (c) 2024 Jiří Zárevúcky
+ * 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 kernel_amd64_mm
+ * @{
+ */
+/** @file
+ */
+
+#ifndef KERN_amd64_MM_PAT_H_
+#define KERN_amd64_MM_PAT_H_
+
+#include <arch/asm.h>
+#include <arch/cpuid.h>
+
+#define MSR_IA32_PAT  0x00000277
+
+typedef enum {
+	PAT_TYPE_UNCACHEABLE = 0,
+	PAT_TYPE_WRITE_COMBINING = 1,
+	PAT_TYPE_WRITE_THROUGH = 4,
+	PAT_TYPE_WRITE_PROTECTED = 5,
+	PAT_TYPE_WRITE_BACK = 6,
+	PAT_TYPE_UNCACHED  = 7,
+} pat_type_t;
+
+#ifndef PROCESSOR_i486
+/**
+ * Assign caching type for a particular combination of PAT,
+ * PCD and PWT bits in PTE.
+ */
+static inline void pat_set_mapping(bool pat, bool pcd, bool pwt,
+    pat_type_t type)
+{
+	int index = pat << 2 | pcd << 1 | pwt;
+	int shift = index * 8;
+
+	uint64_t r = read_msr(MSR_IA32_PAT);
+	r &= ~(0xffull << shift);
+	r |= ((uint64_t) type) << shift;
+	write_msr(MSR_IA32_PAT, r);
+}
+#endif
+
+static inline bool pat_supported(void)
+{
+	if (!has_cpuid())
+		return false;
+
+	cpu_info_t info;
+	cpuid(INTEL_CPUID_STANDARD, &info);
+
+	return (info.cpuid_edx & (1 << 16)) != 0;
+}
+
+#endif
+
+/** @}
+ */
Index: kernel/arch/amd64/src/amd64.c
===================================================================
--- kernel/arch/amd64/src/amd64.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/amd64/src/amd64.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -37,5 +37,5 @@
 #include <stdint.h>
 #include <errno.h>
-#include <mem.h>
+#include <memw.h>
 #include <interrupt.h>
 #include <console/console.h>
@@ -60,4 +60,5 @@
 #include <arch/vreg.h>
 #include <arch/kseg.h>
+#include <arch/mm/pat.h>
 #include <genarch/pic/pic_ops.h>
 
@@ -115,4 +116,8 @@
 	/* Disable alignment check */
 	write_cr0(read_cr0() & ~CR0_AM);
+
+	/* Use PCD+PWT bit combination in PTE to mean write-combining mode. */
+	if (pat_supported())
+		pat_set_mapping(false, true, true, PAT_TYPE_WRITE_COMBINING);
 
 	if (config.cpu_active == 1) {
Index: kernel/arch/amd64/src/cpu/cpu.c
===================================================================
--- kernel/arch/amd64/src/cpu/cpu.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/amd64/src/cpu/cpu.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -99,5 +99,4 @@
 	CPU->arch.tss->iomap_base = &CPU->arch.tss->iomap[0] -
 	    ((uint8_t *) CPU->arch.tss);
-	CPU->fpu_owner = NULL;
 }
 
Index: kernel/arch/amd64/src/pm.c
===================================================================
--- kernel/arch/amd64/src/pm.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/amd64/src/pm.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -39,5 +39,5 @@
 #include <mm/as.h>
 #include <mm/frame.h>
-#include <mem.h>
+#include <memw.h>
 #include <stdlib.h>
 
Index: kernel/arch/amd64/src/smp/ap.S
===================================================================
--- kernel/arch/amd64/src/smp/ap.S	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/amd64/src/smp/ap.S	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -98,7 +98,8 @@
 .code64
 start64:
-	movabsq $ctx, %rsp
-	movq CONTEXT_OFFSET_SP(%rsp), %rsp
+	movabsq $bootstrap_stack_top, %rsp
+	movq (%rsp), %rsp
 
+	pushq $0
 	pushq $0
 	movq %rsp, %rbp
Index: kernel/arch/arm32/_link.ld.in
===================================================================
--- kernel/arch/arm32/_link.ld.in	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/arm32/_link.ld.in	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -26,28 +26,67 @@
 	kernel_load_address = .;
 	. = . + SIZEOF_HEADERS;
+
 	.text : {
 		ktext_start = .;
-		*(.text);
+		*(.text .text.*);
 		ktext_end = .;
 	}
+
+	/* stack unwinding data */
+	.eh_frame_hdr : {
+		eh_frame_hdr_start = .;
+		*(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*);
+		eh_frame_hdr_end = .;
+	}
+
+	.eh_frame : {
+		eh_frame_start = .;
+		KEEP(*(.eh_frame .eh_frame.*));
+		eh_frame_end = .;
+	}
+
 	.data : {
 		kdata_start = .;
-		*(.data);                       /* initialized data */
-		*(.bss);                        /* uninitialized static variables */
+		*(.data .data.*);                       /* initialized data */
+		*(.bss .bss.*);                        /* uninitialized static variables */
 		*(COMMON);                      /* global variables */
 
-		*(.rodata*);
-		*(.sdata);
-		*(.reginfo);
-		. = ALIGN(8);
-		symbol_table = .;
-		*(symtab.*);
-	}
-	.sbss : {
-		*(.sbss);
-		*(.scommon);
+		*(.rodata .rodata.*);
+		*(.sdata .sdata.*);
+		*(.sbss .sbss.*);
+		*(.scommon .scommon.*);
+		kdata_end = .;
 	}
 
-	kdata_end = .;
+	.comment 0 : { *(.comment); }
+	.debug_abbrev 0 : { *(.debug_abbrev); }
+	.debug_abbrev.dwo 0 : { *(	.debug_abbrev.dwo); }
+	.debug_addr 0 : { *(.debug_addr); }
+	.debug_aranges 0 : { *(.debug_aranges); }
+	.debug_cu_index 0 : { *(.debug_cu_index); }
+	.debug_frame 0 : { *(.debug_frame); }
+	.debug_frame_hdr 0 : { *(.debug_frame_hdr); }
+	.debug_info 0 : { *(.debug_info); }
+	.debug_info.dwo 0 : { *(.debug_info.dwo); }
+	.debug_line 0 : { *(.debug_line); }
+	.debug_line.dwo 0 : { *(.debug_line.dwo); }
+	.debug_line_str 0 : { *(.debug_line_str); }
+	.debug_loc 0 : { *(.debug_loc); }
+	.debug_loclists 0 : { *(.debug_loclists); }
+	.debug_loclists.dwo 0 : { *(.debug_loclists.dwo); }
+	.debug_macinfo 0 : { *(.debug_macinfo); }
+	.debug_macro 0 : { *(.debug_macro); }
+	.debug_macro.dwo 0 : { *(.debug_macro.dwo); }
+	.debug_names 0 : { *(.debug_names); }
+	.debug_pubnames 0 : { *(.debug_pubnames); }
+	.debug_pubtypes 0 : { *(.debug_pubtypes); }
+	.debug_ranges 0 : { *(.debug_ranges); }
+	.debug_rnglists 0 : { *(.debug_rnglists); }
+	.debug_str 0 : { *(.debug_str); }
+	.debug_str.dwo 0 : { *(.debug_str.dwo); }
+	.debug_str_offsets 0 : { *(.debug_str_offsets); }
+	.debug_str_offsets.dwo 0 : { *(.debug_str_offsets.dwo); }
+	.debug_tu_index 0 : { *(.debug_tu_index); }
+	.debug_types 0 : { *(.debug_types); }
 
 	/DISCARD/ : {
Index: kernel/arch/arm32/include/arch/asm.h
===================================================================
--- kernel/arch/arm32/include/arch/asm.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/arm32/include/arch/asm.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -65,4 +65,23 @@
 }
 
+/** Enables interrupts and blocks until an interrupt arrives,
+ * atomically if possible on target architecture.
+ * Disables interrupts again before returning to caller.
+ */
+_NO_TRACE static inline void cpu_interruptible_sleep(void)
+{
+	// FIXME: do this atomically
+	interrupts_enable();
+	cpu_sleep();
+	interrupts_disable();
+}
+
+_NO_TRACE static inline void cpu_spin_hint(void)
+{
+#ifdef PROCESSOR_ARCH_armv7_a
+	asm volatile ("yield");
+#endif
+}
+
 _NO_TRACE static inline void pio_write_8(ioport8_t *port, uint8_t v)
 {
Index: kernel/arch/arm32/include/arch/fpu_context.h
===================================================================
--- kernel/arch/arm32/include/arch/fpu_context.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/arm32/include/arch/fpu_context.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -40,11 +40,9 @@
 #include <stdint.h>
 
-#define FPU_CONTEXT_ALIGN    8
-
 /*
  * ARM Architecture reference manual, p B-1529.
  */
 typedef struct {
-	uint32_t fpexc;
+	_Alignas(8) uint32_t fpexc;
 	uint32_t fpscr;
 	uint32_t s[64];
Index: kernel/arch/arm32/src/atomic.c
===================================================================
--- kernel/arch/arm32/src/atomic.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/arm32/src/atomic.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -38,6 +38,8 @@
 #include <arch/asm.h>
 
-unsigned __atomic_fetch_add_4(volatile unsigned *mem, unsigned val, int model)
+unsigned __atomic_fetch_add_4(volatile void *mem0, unsigned val, int model)
 {
+	volatile unsigned *mem = mem0;
+
 	/*
 	 * This implementation is for UP pre-ARMv6 systems where we do not have
@@ -51,6 +53,8 @@
 }
 
-unsigned __atomic_fetch_sub_4(volatile unsigned *mem, unsigned val, int model)
+unsigned __atomic_fetch_sub_4(volatile void *mem0, unsigned val, int model)
 {
+	volatile unsigned *mem = mem0;
+
 	ipl_t ipl = interrupts_disable();
 	unsigned ret = *mem;
@@ -67,6 +71,9 @@
  * returns the previous value of \a *ptr.
  */
-void *__sync_val_compare_and_swap_4(void **ptr, void *expected, void *new_val)
+unsigned __sync_val_compare_and_swap_4(volatile void *ptr0, unsigned expected,
+    unsigned new_val)
 {
+	volatile unsigned *ptr = ptr0;
+
 	/*
 	 * Using an interrupt disabling spinlock might still lead to deadlock
@@ -78,5 +85,5 @@
 	irq_spinlock_lock(&cas_lock, true);
 
-	void *cur_val = *ptr;
+	unsigned cur_val = *ptr;
 
 	if (cur_val == expected) {
@@ -96,12 +103,15 @@
 /* Naive implementations of the newer intrinsics. */
 
-_Bool __atomic_compare_exchange_4(void **mem, void **expected, void *desired, _Bool weak, int success, int failure)
+_Bool __atomic_compare_exchange_4(volatile void *mem, void *expected0,
+    unsigned desired, _Bool weak, int success, int failure)
 {
+	unsigned *expected = expected0;
+
 	(void) weak;
 	(void) success;
 	(void) failure;
 
-	void *old = *expected;
-	void *new = __sync_val_compare_and_swap_4(mem, old, desired);
+	unsigned old = *expected;
+	unsigned new = __sync_val_compare_and_swap_4(mem, old, desired);
 	if (old == new) {
 		return 1;
@@ -112,10 +122,12 @@
 }
 
-void *__atomic_exchange_4(void **mem, void *val, int model)
+unsigned __atomic_exchange_4(volatile void *mem0, unsigned val, int model)
 {
+	volatile unsigned *mem = mem0;
+
 	(void) model;
 
 	irq_spinlock_lock(&cas_lock, true);
-	void *old = *mem;
+	unsigned old = *mem;
 	*mem = val;
 	irq_spinlock_unlock(&cas_lock, true);
Index: kernel/arch/arm32/src/mach/beagleboardxm/beagleboardxm.c
===================================================================
--- kernel/arch/arm32/src/mach/beagleboardxm/beagleboardxm.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/arm32/src/mach/beagleboardxm/beagleboardxm.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -92,7 +92,7 @@
 	 * Release the lock, call clock() and reacquire the lock again.
 	 */
-	spinlock_unlock(&irq->lock);
+	irq_spinlock_unlock(&irq->lock, false);
 	clock();
-	spinlock_lock(&irq->lock);
+	irq_spinlock_lock(&irq->lock, false);
 }
 
@@ -156,5 +156,5 @@
 		/* The IRQ handler was found. */
 		irq->handler(irq);
-		spinlock_unlock(&irq->lock);
+		irq_spinlock_unlock(&irq->lock, false);
 	} else {
 		/* Spurious interrupt. */
Index: kernel/arch/arm32/src/mach/beaglebone/beaglebone.c
===================================================================
--- kernel/arch/arm32/src/mach/beaglebone/beaglebone.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/arm32/src/mach/beaglebone/beaglebone.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -117,7 +117,7 @@
 {
 	am335x_timer_intr_ack(&bbone.timer);
-	spinlock_unlock(&irq->lock);
+	irq_spinlock_unlock(&irq->lock, false);
 	clock();
-	spinlock_lock(&irq->lock);
+	irq_spinlock_lock(&irq->lock, false);
 }
 
@@ -184,5 +184,5 @@
 		/* The IRQ handler was found. */
 		irq->handler(irq);
-		spinlock_unlock(&irq->lock);
+		irq_spinlock_unlock(&irq->lock, false);
 	} else {
 		printf("Spurious interrupt\n");
Index: kernel/arch/arm32/src/mach/gta02/gta02.c
===================================================================
--- kernel/arch/arm32/src/mach/gta02/gta02.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/arm32/src/mach/gta02/gta02.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -146,5 +146,5 @@
 		/* The IRQ handler was found. */
 		irq->handler(irq);
-		spinlock_unlock(&irq->lock);
+		irq_spinlock_unlock(&irq->lock, false);
 	} else {
 		/* Spurious interrupt. */
@@ -258,7 +258,7 @@
 	 * Release the lock, call clock() and reacquire the lock again.
 	 */
-	spinlock_unlock(&irq->lock);
+	irq_spinlock_unlock(&irq->lock, false);
 	clock();
-	spinlock_lock(&irq->lock);
+	irq_spinlock_lock(&irq->lock, false);
 }
 
Index: kernel/arch/arm32/src/mach/integratorcp/integratorcp.c
===================================================================
--- kernel/arch/arm32/src/mach/integratorcp/integratorcp.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/arm32/src/mach/integratorcp/integratorcp.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -197,7 +197,7 @@
 	 */
 
-	spinlock_unlock(&irq->lock);
+	irq_spinlock_unlock(&irq->lock, false);
 	clock();
-	spinlock_lock(&irq->lock);
+	irq_spinlock_lock(&irq->lock, false);
 
 }
@@ -268,5 +268,5 @@
 				/* The IRQ handler was found. */
 				irq->handler(irq);
-				spinlock_unlock(&irq->lock);
+				irq_spinlock_unlock(&irq->lock, false);
 			} else {
 				/* Spurious interrupt. */
Index: kernel/arch/arm32/src/mach/raspberrypi/raspberrypi.c
===================================================================
--- kernel/arch/arm32/src/mach/raspberrypi/raspberrypi.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/arm32/src/mach/raspberrypi/raspberrypi.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -94,7 +94,7 @@
 {
 	bcm2835_timer_irq_ack(raspi.timer);
-	spinlock_unlock(&irq->lock);
+	irq_spinlock_unlock(&irq->lock, false);
 	clock();
-	spinlock_lock(&irq->lock);
+	irq_spinlock_lock(&irq->lock, false);
 }
 
@@ -159,5 +159,5 @@
 		/* The IRQ handler was found. */
 		irq->handler(irq);
-		spinlock_unlock(&irq->lock);
+		irq_spinlock_unlock(&irq->lock, false);
 	} else {
 		/* Spurious interrupt. */
Index: kernel/arch/arm32/src/ras.c
===================================================================
--- kernel/arch/arm32/src/ras.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/arm32/src/ras.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -44,5 +44,5 @@
 #include <arch/exception.h>
 #include <arch.h>
-#include <mem.h>
+#include <memw.h>
 #include <typedefs.h>
 
Index: kernel/arch/arm64/_link.ld.in
===================================================================
--- kernel/arch/arm64/_link.ld.in	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/arm64/_link.ld.in	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -19,39 +19,65 @@
 	kernel_load_address = LOAD_ADDRESS_V;
 
-	.image (LOAD_ADDRESS_V + SIZEOF_HEADERS) : AT (LOAD_ADDRESS_P + SIZEOF_HEADERS) {
+	.text (LOAD_ADDRESS_V + SIZEOF_HEADERS) : AT (LOAD_ADDRESS_P + SIZEOF_HEADERS) {
 		. = ALIGN(16);
 		ktext_start = .;
-		*(K_TEXT_START)
+		KEEP(*(K_TEXT_START))
 		*(.text .text.*);
 		ktext_end = .;
+	}
 
+	/* stack unwinding data */
+	.eh_frame_hdr : {
+		eh_frame_hdr_start = .;
+		*(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*);
+		eh_frame_hdr_end = .;
+	}
+
+	.eh_frame : {
+		eh_frame_start = .;
+		KEEP(*(.eh_frame .eh_frame.*));
+		eh_frame_end = .;
+	}
+
+	.data : {
 		kdata_start = .;
-		*(K_DATA_START)
-		*(.data);                       /* initialized data */
-		*(.bss);                        /* uninitialized static variables */
+		KEEP(*(K_DATA_START))
+		*(.rodata .rodata.*);
+		*(.data .data.*);                       /* initialized data */
+		*(.bss .bss.*);                        /* uninitialized static variables */
 		*(COMMON);                      /* global variables */
-
-		*(.rodata*);
-		. = ALIGN(8);
-		symbol_table = .;
-		*(symtab.*);
-
 		kdata_end = .;
 	}
 
-#ifdef CONFIG_LINE_DEBUG
 	.comment 0 : { *(.comment); }
 	.debug_abbrev 0 : { *(.debug_abbrev); }
+	.debug_abbrev.dwo 0 : { *(	.debug_abbrev.dwo); }
+	.debug_addr 0 : { *(.debug_addr); }
 	.debug_aranges 0 : { *(.debug_aranges); }
+	.debug_cu_index 0 : { *(.debug_cu_index); }
 	.debug_frame 0 : { *(.debug_frame); }
+	.debug_frame_hdr 0 : { *(.debug_frame_hdr); }
 	.debug_info 0 : { *(.debug_info); }
+	.debug_info.dwo 0 : { *(.debug_info.dwo); }
 	.debug_line 0 : { *(.debug_line); }
+	.debug_line.dwo 0 : { *(.debug_line.dwo); }
+	.debug_line_str 0 : { *(.debug_line_str); }
 	.debug_loc 0 : { *(.debug_loc); }
+	.debug_loclists 0 : { *(.debug_loclists); }
+	.debug_loclists.dwo 0 : { *(.debug_loclists.dwo); }
 	.debug_macinfo 0 : { *(.debug_macinfo); }
+	.debug_macro 0 : { *(.debug_macro); }
+	.debug_macro.dwo 0 : { *(.debug_macro.dwo); }
+	.debug_names 0 : { *(.debug_names); }
 	.debug_pubnames 0 : { *(.debug_pubnames); }
 	.debug_pubtypes 0 : { *(.debug_pubtypes); }
 	.debug_ranges 0 : { *(.debug_ranges); }
+	.debug_rnglists 0 : { *(.debug_rnglists); }
 	.debug_str 0 : { *(.debug_str); }
-#endif
+	.debug_str.dwo 0 : { *(.debug_str.dwo); }
+	.debug_str_offsets 0 : { *(.debug_str_offsets); }
+	.debug_str_offsets.dwo 0 : { *(.debug_str_offsets.dwo); }
+	.debug_tu_index 0 : { *(.debug_tu_index); }
+	.debug_types 0 : { *(.debug_types); }
 
 	/DISCARD/ : {
Index: kernel/arch/arm64/include/arch/asm.h
===================================================================
--- kernel/arch/arm64/include/arch/asm.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/arm64/include/arch/asm.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -54,4 +54,16 @@
 }
 
+/** Enables interrupts and blocks until an interrupt arrives,
+ * atomically if possible on target architecture.
+ * Disables interrupts again before returning to caller.
+ */
+_NO_TRACE static inline void cpu_interruptible_sleep(void)
+{
+	// FIXME: do this atomically
+	interrupts_enable();
+	cpu_sleep();
+	interrupts_disable();
+}
+
 /** Halts CPU. */
 _NO_TRACE static inline __attribute__((noreturn)) void cpu_halt(void)
@@ -59,4 +71,9 @@
 	while (true)
 		;
+}
+
+_NO_TRACE static inline void cpu_spin_hint(void)
+{
+	asm volatile ("yield");
 }
 
Index: kernel/arch/arm64/include/arch/context_struct.h
===================================================================
--- kernel/arch/arm64/include/arch/context_struct.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/arm64/include/arch/context_struct.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -43,6 +43,5 @@
 #define CONTEXT_OFFSET_X28  0x58
 #define CONTEXT_OFFSET_X29  0x60
-#define CONTEXT_OFFSET_IPL  0x68
-#define CONTEXT_SIZE        0x70
+#define CONTEXT_SIZE        0x68
 
 #ifndef __ASSEMBLER__
@@ -68,5 +67,4 @@
 	uint64_t x28;
 	uint64_t x29;
-	ipl_t ipl;
 } context_t;
 
Index: kernel/arch/arm64/include/arch/fpu_context.h
===================================================================
--- kernel/arch/arm64/include/arch/fpu_context.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/arm64/include/arch/fpu_context.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -39,6 +39,4 @@
 #include <arch/fpu_context_struct.h>
 
-#define FPU_CONTEXT_ALIGN  16
-
 #endif
 
Index: kernel/arch/arm64/include/arch/fpu_context_struct.h
===================================================================
--- kernel/arch/arm64/include/arch/fpu_context_struct.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/arm64/include/arch/fpu_context_struct.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -42,5 +42,5 @@
 /** ARM64 FPU context. */
 typedef struct fpu_context {
-	uint128_t vregs[32];
+	_Alignas(16) uint128_t vregs[32];
 	uint32_t fpcr;
 	uint32_t fpsr;
Index: kernel/arch/arm64/src/interrupt.c
===================================================================
--- kernel/arch/arm64/src/interrupt.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/arm64/src/interrupt.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -137,5 +137,5 @@
 	while (drift > timer_increment) {
 		drift -= timer_increment;
-		CPU->missed_clock_ticks++;
+		CPU_LOCAL->missed_clock_ticks++;
 	}
 	CNTV_CVAL_EL0_write(cntvct + timer_increment - drift);
Index: kernel/arch/ia32/_link.ld.in
===================================================================
--- kernel/arch/ia32/_link.ld.in	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia32/_link.ld.in	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -17,40 +17,74 @@
 	.unmapped (BOOT_OFFSET + SIZEOF_HEADERS): AT (BOOT_OFFSET + SIZEOF_HEADERS) {
 		unmapped_start = .;
-		*(K_TEXT_START);
-		*(K_DATA_START);
+		KEEP(*(K_TEXT_START));
+		KEEP(*(K_DATA_START));
 		unmapped_end = .;
 	}
 
-	.mapped (PA2KA(BOOT_OFFSET) + SIZEOF_HEADERS + SIZEOF(.unmapped)): AT (BOOT_OFFSET + SIZEOF_HEADERS + SIZEOF(.unmapped)) {
+	.text (PA2KA(BOOT_OFFSET) + SIZEOF_HEADERS + SIZEOF(.unmapped)): AT (BOOT_OFFSET + SIZEOF_HEADERS + SIZEOF(.unmapped)) {
 		ktext_start = .;
 		*(.text .text.*);
 		ktext_end = .;
+	}
 
+	/* stack unwinding data */
+	.eh_frame_hdr : {
+		eh_frame_hdr_start = .;
+		*(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*);
+		eh_frame_hdr_end = .;
+	}
+	.eh_frame : {
+		eh_frame_start = .;
+		KEEP(*(.eh_frame .eh_frame.*));
+		eh_frame_end = .;
+	}
+
+	.data : {
 		kdata_start = .;
-		*(.data);               /* initialized data */
-		*(.rodata .rodata.*);   /* string literals */
-		*(COMMON);              /* global variables */
+		*(.rodata .rodata.*);  /* read-only global variables */
+		*(.data .data.*);      /* non-zero initialized global variables */
 
-		/* bss can't be omitted from the ELF image. */
-		*(.bss);                /* uninitialized static variables */
+		/*
+		 * When .bss is not physically present in the ELF file (MemSz > FileSz)
+		 * the kernel crashes during early boot. Not sure which part of the
+		 * boot process is to blame, for now just keep .bss packaged with .data
+		 * so that FileSz == MemSz.
+		 */
 
-		. = ALIGN(8);
-		symbol_table = .;
-		*(symtab.*);            /* Symbol table, must be LAST symbol! */
+		 *(.bss .bss.*);        /* uninitialized global variables */
+		*(COMMON);             /* non-`static` global variables without an extern declaration */
 		kdata_end = .;
 	}
 
-#ifdef CONFIG_LINE_DEBUG
 	.comment 0 : { *(.comment); }
 	.debug_abbrev 0 : { *(.debug_abbrev); }
+	.debug_abbrev.dwo 0 : { *(	.debug_abbrev.dwo); }
+	.debug_addr 0 : { *(.debug_addr); }
 	.debug_aranges 0 : { *(.debug_aranges); }
+	.debug_cu_index 0 : { *(.debug_cu_index); }
+	.debug_frame 0 : { *(.debug_frame); }
+	.debug_frame_hdr 0 : { *(.debug_frame_hdr); }
 	.debug_info 0 : { *(.debug_info); }
+	.debug_info.dwo 0 : { *(.debug_info.dwo); }
 	.debug_line 0 : { *(.debug_line); }
+	.debug_line.dwo 0 : { *(.debug_line.dwo); }
+	.debug_line_str 0 : { *(.debug_line_str); }
 	.debug_loc 0 : { *(.debug_loc); }
+	.debug_loclists 0 : { *(.debug_loclists); }
+	.debug_loclists.dwo 0 : { *(.debug_loclists.dwo); }
+	.debug_macinfo 0 : { *(.debug_macinfo); }
+	.debug_macro 0 : { *(.debug_macro); }
+	.debug_macro.dwo 0 : { *(.debug_macro.dwo); }
+	.debug_names 0 : { *(.debug_names); }
 	.debug_pubnames 0 : { *(.debug_pubnames); }
 	.debug_pubtypes 0 : { *(.debug_pubtypes); }
 	.debug_ranges 0 : { *(.debug_ranges); }
+	.debug_rnglists 0 : { *(.debug_rnglists); }
 	.debug_str 0 : { *(.debug_str); }
-#endif
+	.debug_str.dwo 0 : { *(.debug_str.dwo); }
+	.debug_str_offsets 0 : { *(.debug_str_offsets); }
+	.debug_str_offsets.dwo 0 : { *(.debug_str_offsets.dwo); }
+	.debug_tu_index 0 : { *(.debug_tu_index); }
+	.debug_types 0 : { *(.debug_types); }
 
 	/DISCARD/ : {
Index: kernel/arch/ia32/include/arch/asm.h
===================================================================
--- kernel/arch/ia32/include/arch/asm.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia32/include/arch/asm.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -57,8 +57,21 @@
 }
 
-_NO_TRACE static inline void cpu_sleep(void)
-{
-	asm volatile (
+/** Enables interrupts and blocks until an interrupt arrives,
+ * atomically if possible on target architecture.
+ * Disables interrupts again before returning to caller.
+ */
+_NO_TRACE static inline void cpu_interruptible_sleep(void)
+{
+	asm volatile (
+	    "sti\n"
 	    "hlt\n"
+	    "cli\n"
+	);
+}
+
+_NO_TRACE static inline void cpu_spin_hint(void)
+{
+	asm volatile (
+	    "pause\n"
 	);
 }
Index: kernel/arch/ia32/include/arch/context_struct.h
===================================================================
--- kernel/arch/ia32/include/arch/context_struct.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia32/include/arch/context_struct.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -37,6 +37,5 @@
 #define CONTEXT_OFFSET_EBP  0x14
 #define CONTEXT_OFFSET_TP   0x18
-#define CONTEXT_OFFSET_IPL  0x1c
-#define CONTEXT_SIZE        0x20
+#define CONTEXT_SIZE        0x1c
 
 #ifndef __ASSEMBLER__
@@ -53,5 +52,4 @@
 	uint32_t ebp;
 	uint32_t tp;
-	ipl_t ipl;
 } context_t;
 
Index: kernel/arch/ia32/include/arch/fpu_context.h
===================================================================
--- kernel/arch/ia32/include/arch/fpu_context.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia32/include/arch/fpu_context.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -38,8 +38,6 @@
 #include <stdint.h>
 
-#define FPU_CONTEXT_ALIGN  16
-
 typedef struct {
-	uint8_t fpu[512];  /* FXSAVE & FXRSTOR storage area */
+	_Alignas(16) uint8_t fpu[512];  /* FXSAVE & FXRSTOR storage area */
 } fpu_context_t;
 
Index: kernel/arch/ia32/include/arch/mm/page.h
===================================================================
--- kernel/arch/ia32/include/arch/mm/page.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia32/include/arch/mm/page.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -190,5 +190,6 @@
 	    p->writeable << PAGE_WRITE_SHIFT |
 	    1 << PAGE_EXEC_SHIFT |
-	    p->global << PAGE_GLOBAL_SHIFT);
+	    p->global << PAGE_GLOBAL_SHIFT |
+	    p->page_write_through << PAGE_WRITE_COMBINE_SHIFT);
 }
 
@@ -197,9 +198,18 @@
 	pte_t *p = &pt[i];
 
-	p->page_cache_disable = !(flags & PAGE_CACHEABLE);
 	p->present = !(flags & PAGE_NOT_PRESENT);
 	p->uaccessible = (flags & PAGE_USER) != 0;
 	p->writeable = (flags & PAGE_WRITE) != 0;
 	p->global = (flags & PAGE_GLOBAL) != 0;
+
+	if (flags & PAGE_WRITE_COMBINE) {
+		/* We have mapped PCD+PWT bits to write-combine mode via PAT MSR. */
+		/* (If PAT is unsupported, it will default to uncached.) */
+		p->page_cache_disable = 1;
+		p->page_write_through = 1;
+	} else {
+		p->page_cache_disable = !(flags & PAGE_CACHEABLE);
+		p->page_write_through = 0;
+	}
 
 	/*
Index: kernel/arch/ia32/include/arch/mm/pat.h
===================================================================
--- kernel/arch/ia32/include/arch/mm/pat.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ kernel/arch/ia32/include/arch/mm/pat.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,1 @@
+../../../../amd64/include/arch/mm/pat.h
Index: kernel/arch/ia32/src/boot/vesa_real.inc
===================================================================
--- kernel/arch/ia32/src/boot/vesa_real.inc	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia32/src/boot/vesa_real.inc	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -21,5 +21,5 @@
 #define VESA_END_OF_MODES  0xffff
 
-#define VESA_OK  0x4f
+#define VESA_OK  0x004f
 
 #define VESA_GET_INFO       0x4f00
@@ -180,5 +180,5 @@
 
 	pop %di
-	cmp $VESA_OK, %al
+	cmp $VESA_OK, %ax
 	jnz no_mode
 
@@ -207,5 +207,5 @@
 		pop %di
 		pop %cx
-		cmp $VESA_OK, %al
+		cmp $VESA_OK, %ax
 		jne no_mode
 
@@ -254,5 +254,5 @@
 
 		pop %di
-		cmp $VESA_OK, %al
+		cmp $VESA_OK, %ax
 		jnz no_mode
 
@@ -364,5 +364,28 @@
 
 		/* No prefered mode found */
-
+		push %di
+		mov $e_vesa_init - vesa_init, %di
+		mov 2 + VESA_MODE_LIST_PTR_OFFSET(%di), %si
+		mov %si, %gs
+		mov VESA_MODE_LIST_PTR_OFFSET(%di), %si
+		pop %di
+
+		/* Check if list of supported modes contains fallback mode */
+	find_fallback_mode:
+
+		mov %gs:(%si), %cx
+
+		cmp $0x111, %cx
+		je fallback_mode_listed
+
+		cmp $VESA_END_OF_MODES, %cx
+		je text_mode
+
+		inc %si
+		inc %si
+		jmp find_fallback_mode /* 16-bit relative jump */
+
+	fallback_mode_listed:
+		/* Make sure fallback mode is really supported */
 		mov $0x111, %cx
 		push %di
@@ -373,7 +396,13 @@
 		pop %cx
 		pop %di
-		cmp $VESA_OK, %al
+		cmp $VESA_OK, %ax
 		jnz text_mode
-		jz set_mode  /* force relative jump */
+
+		/* Verify mode attributes */
+		and $0x99, %ax
+		cmp $0x99, %ax
+		jne text_mode
+
+		jmp set_mode  /* 16-bit relative jump */
 
 	text_mode:
Index: kernel/arch/ia32/src/cpu/cpu.c
===================================================================
--- kernel/arch/ia32/src/cpu/cpu.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia32/src/cpu/cpu.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -71,10 +71,10 @@
 void fpu_disable(void)
 {
-	write_cr0(read_cr0() & ~CR0_TS);
+	write_cr0(read_cr0() | CR0_TS);
 }
 
 void fpu_enable(void)
 {
-	write_cr0(read_cr0() | CR0_TS);
+	write_cr0(read_cr0() & ~CR0_TS);
 }
 
@@ -86,6 +86,4 @@
 	CPU->arch.tss = tss_p;
 	CPU->arch.tss->iomap_base = &CPU->arch.tss->iomap[0] - ((uint8_t *) CPU->arch.tss);
-
-	CPU->fpu_owner = NULL;
 
 	cpuid(INTEL_CPUID_STANDARD, &info);
Index: kernel/arch/ia32/src/ia32.c
===================================================================
--- kernel/arch/ia32/src/ia32.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia32/src/ia32.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,5 +1,5 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2001-2004 Jakub Jermar
- * Copyright (c) 2009 Jiri Svoboda
  * Copyright (c) 2009 Martin Decky
  * All rights reserved.
@@ -39,5 +39,5 @@
 #include <stdint.h>
 #include <errno.h>
-#include <mem.h>
+#include <memw.h>
 #include <interrupt.h>
 #include <console/console.h>
@@ -61,4 +61,5 @@
 #include <arch/pm.h>
 #include <arch/vreg.h>
+#include <arch/mm/pat.h>
 
 #ifdef CONFIG_SMP
@@ -104,4 +105,10 @@
 {
 	pm_init();
+
+#ifndef PROCESSOR_i486
+	/* Use PCD+PWT bit combination in PTE to mean write-combining mode. */
+	if (pat_supported())
+		pat_set_mapping(false, true, true, PAT_TYPE_WRITE_COMBINING);
+#endif
 
 	if (config.cpu_active == 1) {
Index: kernel/arch/ia32/src/pm.c
===================================================================
--- kernel/arch/ia32/src/pm.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia32/src/pm.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -44,5 +44,5 @@
 #include <mm/frame.h>
 #include <stdlib.h>
-#include <mem.h>
+#include <memw.h>
 #include <arch/boot/boot.h>
 #include <interrupt.h>
Index: kernel/arch/ia32/src/smp/ap.S
===================================================================
--- kernel/arch/ia32/src/smp/ap.S	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia32/src/smp/ap.S	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -75,6 +75,6 @@
 	movw %ax, %es
 	movw %ax, %ss
-	movl $KA2PA(ctx), %eax          /* KA2PA((uintptr_t) &ctx) */
-	movl CONTEXT_OFFSET_SP(%eax), %esp
+	movl $KA2PA(bootstrap_stack_top), %eax   /* KA2PA((uintptr_t) &bootstrap_stack_top) */
+	movl (%eax), %esp
 	leal KA2PA(0)(%esp), %esp       /* KA2PA(ctx.sp) */
 
Index: kernel/arch/ia32/src/smp/smp.c
===================================================================
--- kernel/arch/ia32/src/smp/smp.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia32/src/smp/smp.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -54,5 +54,5 @@
 #include <mm/as.h>
 #include <log.h>
-#include <mem.h>
+#include <memw.h>
 #include <genarch/drivers/i8259/i8259.h>
 #include <cpu.h>
@@ -178,6 +178,5 @@
 			 * supposed to wake us up.
 			 */
-			if (waitq_sleep_timeout(&ap_completion_wq, 1000000,
-			    SYNCH_FLAGS_NONE, NULL) == ETIMEOUT) {
+			if (semaphore_down_timeout(&ap_completion_semaphore, 1000000) != EOK) {
 				log(LF_ARCH, LVL_NOTE, "%s: waiting for cpu%u "
 				    "(APIC ID = %d) timed out", __FUNCTION__,
Index: kernel/arch/ia64/_link.ld.in
===================================================================
--- kernel/arch/ia64/_link.ld.in	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia64/_link.ld.in	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -15,30 +15,71 @@
 	kernel_load_address = LOAD_ADDRESS_V;
 
-	.image (LOAD_ADDRESS_V + SIZEOF_HEADERS): AT (LOAD_ADDRESS_P + SIZEOF_HEADERS) {
+	.text (LOAD_ADDRESS_V + SIZEOF_HEADERS): AT (LOAD_ADDRESS_P + SIZEOF_HEADERS) {
 		. = ALIGN(16);
 		ktext_start = .;
-		*(K_TEXT_START);
+		KEEP(*(K_TEXT_START));
 		*(.text .text.*)
 		ktext_end = .;
+	}
 
+	/* stack unwinding data */
+	.eh_frame_hdr : {
+		eh_frame_hdr_start = .;
+		*(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*);
+		eh_frame_hdr_end = .;
+	}
+
+	.eh_frame : {
+		eh_frame_start = .;
+		KEEP(*(.eh_frame .eh_frame.*));
+		eh_frame_end = .;
+	}
+	
+	.data : {
 		kdata_start = .;
-		*(K_DATA_START)
-		*(.rodata .rodata.*)
+		KEEP(*(K_DATA_START));
+		*(.rodata .rodata.*);
 		*(.opd)
 		*(.data .data.*)
 		__gp = .;
 		*(.got .got.*)
-		*(.sdata)
-		*(.sbss)
-		*(.scommon)
-		*(.bss)
+		*(.sdata .sdata.*)
+		*(.sbss .sbss.*)
+		*(.scommon .scommon.*)
+		*(.bss .bss.*)
 		*(COMMON);
-
-		. = ALIGN(8);
-		symbol_table = .;
-		*(symtab.*);            /* Symbol table, must be LAST symbol!*/
-
 		kdata_end = .;
 	}
+
+	.comment 0 : { *(.comment); }
+	.debug_abbrev 0 : { *(.debug_abbrev); }
+	.debug_abbrev.dwo 0 : { *(	.debug_abbrev.dwo); }
+	.debug_addr 0 : { *(.debug_addr); }
+	.debug_aranges 0 : { *(.debug_aranges); }
+	.debug_cu_index 0 : { *(.debug_cu_index); }
+	.debug_frame 0 : { *(.debug_frame); }
+	.debug_frame_hdr 0 : { *(.debug_frame_hdr); }
+	.debug_info 0 : { *(.debug_info); }
+	.debug_info.dwo 0 : { *(.debug_info.dwo); }
+	.debug_line 0 : { *(.debug_line); }
+	.debug_line.dwo 0 : { *(.debug_line.dwo); }
+	.debug_line_str 0 : { *(.debug_line_str); }
+	.debug_loc 0 : { *(.debug_loc); }
+	.debug_loclists 0 : { *(.debug_loclists); }
+	.debug_loclists.dwo 0 : { *(.debug_loclists.dwo); }
+	.debug_macinfo 0 : { *(.debug_macinfo); }
+	.debug_macro 0 : { *(.debug_macro); }
+	.debug_macro.dwo 0 : { *(.debug_macro.dwo); }
+	.debug_names 0 : { *(.debug_names); }
+	.debug_pubnames 0 : { *(.debug_pubnames); }
+	.debug_pubtypes 0 : { *(.debug_pubtypes); }
+	.debug_ranges 0 : { *(.debug_ranges); }
+	.debug_rnglists 0 : { *(.debug_rnglists); }
+	.debug_str 0 : { *(.debug_str); }
+	.debug_str.dwo 0 : { *(.debug_str.dwo); }
+	.debug_str_offsets 0 : { *(.debug_str_offsets); }
+	.debug_str_offsets.dwo 0 : { *(.debug_str_offsets.dwo); }
+	.debug_tu_index 0 : { *(.debug_tu_index); }
+	.debug_types 0 : { *(.debug_types); }
 
 	/DISCARD/ : {
Index: kernel/arch/ia64/include/arch/asm.h
===================================================================
--- kernel/arch/ia64/include/arch/asm.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia64/include/arch/asm.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -44,4 +44,8 @@
 #define IO_SPACE_BOUNDARY       ((void *) (64 * 1024))
 
+_NO_TRACE static inline void cpu_spin_hint(void)
+{
+}
+
 /** Map the I/O port address to a legacy I/O address. */
 _NO_TRACE static inline uintptr_t p2a(volatile void *p)
@@ -469,4 +473,16 @@
     uint64_t, uint64_t);
 
+/** Enables interrupts and blocks until an interrupt arrives,
+ * atomically if possible on target architecture.
+ * Disables interrupts again before returning to caller.
+ */
+_NO_TRACE static inline void cpu_interruptible_sleep(void)
+{
+	// FIXME: do this properly
+	interrupts_enable();
+	cpu_sleep();
+	interrupts_disable();
+}
+
 #endif
 
Index: kernel/arch/ia64/include/arch/context_struct.h
===================================================================
--- kernel/arch/ia64/include/arch/context_struct.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia64/include/arch/context_struct.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -72,6 +72,5 @@
 #define CONTEXT_OFFSET_F30             0x1d0
 #define CONTEXT_OFFSET_F31             0x1e0
-#define CONTEXT_OFFSET_IPL             0x1f0
-#define CONTEXT_SIZE                   0x200
+#define CONTEXT_SIZE                   0x1f0
 
 #ifndef __ASSEMBLER__
@@ -136,6 +135,4 @@
 	uint128_t f30;
 	uint128_t f31;
-
-	ipl_t ipl;
 } context_t;
 
Index: kernel/arch/ia64/include/arch/fpu_context.h
===================================================================
--- kernel/arch/ia64/include/arch/fpu_context.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia64/include/arch/fpu_context.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,6 +36,4 @@
 #define KERN_ia64_FPU_CONTEXT_H_
 
-#define FPU_CONTEXT_ALIGN 16
-
 #include <_bits/int128_t.h>
 
@@ -43,5 +41,5 @@
 
 typedef struct {
-	uint128_t fr[FRS];
+	_Alignas(16) uint128_t fr[FRS];
 } fpu_context_t;
 
Index: kernel/arch/ia64/src/cpu/cpu.c
===================================================================
--- kernel/arch/ia64/src/cpu/cpu.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia64/src/cpu/cpu.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -37,5 +37,5 @@
 #include <arch/register.h>
 #include <stdio.h>
-#include <mem.h>
+#include <memw.h>
 
 void cpu_arch_init(void)
Index: kernel/arch/ia64/src/drivers/it.c
===================================================================
--- kernel/arch/ia64/src/drivers/it.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia64/src/drivers/it.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -122,5 +122,5 @@
 		itm += IT_DELTA;
 		if (itm - itc < 0)
-			CPU->missed_clock_ticks++;
+			CPU_LOCAL->missed_clock_ticks++;
 		else
 			break;
Index: kernel/arch/ia64/src/drivers/ski.c
===================================================================
--- kernel/arch/ia64/src/drivers/ski.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia64/src/drivers/ski.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -258,5 +258,5 @@
 
 	instance->srlnin = srlnin;
-	thread_ready(instance->thread);
+	thread_start(instance->thread);
 
 	sysinfo_set_item_val("kbd", NULL, true);
Index: kernel/arch/ia64/src/mm/vhpt.c
===================================================================
--- kernel/arch/ia64/src/mm/vhpt.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ia64/src/mm/vhpt.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -33,5 +33,5 @@
  */
 
-#include <mem.h>
+#include <memw.h>
 #include <arch/barrier.h>
 #include <arch/mm/vhpt.h>
Index: kernel/arch/mips32/_link.ld.in
===================================================================
--- kernel/arch/mips32/_link.ld.in	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/mips32/_link.ld.in	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -28,21 +28,35 @@
 	.text : {
 		ktext_start = .;
+		/* FIXME: for some reason, mixing .text with .text.* produces a crash on malta-be */
 		*(.text);
+		*(.text.*);
 		ktext_end = .;
 	}
+
+	/* stack unwinding data */
+	.eh_frame_hdr : {
+		eh_frame_hdr_start = .;
+		*(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*);
+		eh_frame_hdr_end = .;
+	}
+
+	.eh_frame : {
+		eh_frame_start = .;
+		KEEP(*(.eh_frame .eh_frame.*));
+		eh_frame_end = .;
+	}
+
 	.data : {
 		kdata_start = .;
-		*(.data);                       /* initialized data */
-		*(.rodata*);
-		*(.sdata);
+		*(.data .data.*);                       /* initialized data */
+		*(.rodata .rodata.*);
+		*(.sdata .sdata.*);
 		*(.reginfo);
-		*(.sbss);
-		*(.scommon);
-		*(.bss);                        /* uninitialized static variables */
+		*(.sbss .sbss.*);
+		*(.scommon .scommon.*);
+		*(.bss .bss.*);                        /* uninitialized static variables */
 		*(COMMON);                      /* global variables */
-		. = ALIGN(8);
-		symbol_table = .;
-		*(symtab.*);
 	}
+
 	_gp = . + 0x8000;
 	.lit8 : { *(.lit8) }
@@ -50,4 +64,35 @@
 
 	kdata_end = .;
+
+	.comment 0 : { *(.comment); }
+	.debug_abbrev 0 : { *(.debug_abbrev); }
+	.debug_abbrev.dwo 0 : { *(	.debug_abbrev.dwo); }
+	.debug_addr 0 : { *(.debug_addr); }
+	.debug_aranges 0 : { *(.debug_aranges); }
+	.debug_cu_index 0 : { *(.debug_cu_index); }
+	.debug_frame 0 : { *(.debug_frame); }
+	.debug_frame_hdr 0 : { *(.debug_frame_hdr); }
+	.debug_info 0 : { *(.debug_info); }
+	.debug_info.dwo 0 : { *(.debug_info.dwo); }
+	.debug_line 0 : { *(.debug_line); }
+	.debug_line.dwo 0 : { *(.debug_line.dwo); }
+	.debug_line_str 0 : { *(.debug_line_str); }
+	.debug_loc 0 : { *(.debug_loc); }
+	.debug_loclists 0 : { *(.debug_loclists); }
+	.debug_loclists.dwo 0 : { *(.debug_loclists.dwo); }
+	.debug_macinfo 0 : { *(.debug_macinfo); }
+	.debug_macro 0 : { *(.debug_macro); }
+	.debug_macro.dwo 0 : { *(.debug_macro.dwo); }
+	.debug_names 0 : { *(.debug_names); }
+	.debug_pubnames 0 : { *(.debug_pubnames); }
+	.debug_pubtypes 0 : { *(.debug_pubtypes); }
+	.debug_ranges 0 : { *(.debug_ranges); }
+	.debug_rnglists 0 : { *(.debug_rnglists); }
+	.debug_str 0 : { *(.debug_str); }
+	.debug_str.dwo 0 : { *(.debug_str.dwo); }
+	.debug_str_offsets 0 : { *(.debug_str_offsets); }
+	.debug_str_offsets.dwo 0 : { *(.debug_str_offsets.dwo); }
+	.debug_tu_index 0 : { *(.debug_tu_index); }
+	.debug_types 0 : { *(.debug_types); }
 
 	/DISCARD/ : {
Index: kernel/arch/mips32/include/arch/asm.h
===================================================================
--- kernel/arch/mips32/include/arch/asm.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/mips32/include/arch/asm.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -45,4 +45,8 @@
 }
 
+_NO_TRACE static inline void cpu_spin_hint(void)
+{
+}
+
 _NO_TRACE static inline void pio_write_8(ioport8_t *port, uint8_t v)
 {
@@ -85,4 +89,16 @@
 extern bool interrupts_disabled(void);
 
+/** Enables interrupts and blocks until an interrupt arrives,
+ * atomically if possible on target architecture.
+ * Disables interrupts again before returning to caller.
+ */
+_NO_TRACE static inline void cpu_interruptible_sleep(void)
+{
+	// FIXME: do this atomically
+	interrupts_enable();
+	cpu_sleep();
+	interrupts_disable();
+}
+
 #endif
 
Index: kernel/arch/mips32/include/arch/context_struct.h
===================================================================
--- kernel/arch/mips32/include/arch/context_struct.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/mips32/include/arch/context_struct.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -43,6 +43,5 @@
 #define CONTEXT_OFFSET_GP  0x2c
 #define CONTEXT_OFFSET_TP  0x30
-#define CONTEXT_OFFSET_IPL 0x34
-#define CONTEXT_SIZE       0x38
+#define CONTEXT_SIZE       0x34
 
 #ifndef __ASSEMBLER__
@@ -66,5 +65,4 @@
 	/* We use the K1 register for userspace thread pointer. */
 	uint32_t tp;
-	ipl_t ipl;
 } context_t;
 
Index: kernel/arch/mips32/include/arch/fpu_context.h
===================================================================
--- kernel/arch/mips32/include/arch/fpu_context.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/mips32/include/arch/fpu_context.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -39,6 +39,4 @@
 #include <arch/fpu_context_struct.h>
 
-#define FPU_CONTEXT_ALIGN    sizeof(sysarg_t)
-
 #endif
 
Index: kernel/arch/mips32/include/arch/fpu_context_struct.h
===================================================================
--- kernel/arch/mips32/include/arch/fpu_context_struct.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/mips32/include/arch/fpu_context_struct.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -42,5 +42,5 @@
 
 typedef struct fpu_context {
-	sysarg_t dregs[32];
+	_Alignas(sizeof(sysarg_t)) sysarg_t dregs[32];
 	sysarg_t cregs[32];
 } fpu_context_t;
Index: kernel/arch/mips32/src/interrupt.c
===================================================================
--- kernel/arch/mips32/src/interrupt.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/mips32/src/interrupt.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -121,5 +121,5 @@
 	while (drift > cp0_compare_value) {
 		drift -= cp0_compare_value;
-		CPU->missed_clock_ticks++;
+		CPU_LOCAL->missed_clock_ticks++;
 	}
 
Index: kernel/arch/mips32/src/mips32.c
===================================================================
--- kernel/arch/mips32/src/mips32.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/mips32/src/mips32.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -40,5 +40,6 @@
 #include <macros.h>
 #include <str.h>
-#include <mem.h>
+#include <memw.h>
+#include <preemption.h>
 #include <userspace.h>
 #include <stdbool.h>
Index: kernel/arch/mips32/src/mm/frame.c
===================================================================
--- kernel/arch/mips32/src/mm/frame.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/mips32/src/mm/frame.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -113,7 +113,7 @@
 		return false;
 
-	/* Kernel stack */
+	/* Boot allocations */
 	if (overlaps(frame << ZERO_PAGE_WIDTH, ZERO_PAGE_SIZE,
-	    KA2PA(config.stack_base), config.stack_size))
+	    KA2PA(ballocs.base), ballocs.size))
 		return false;
 
Index: kernel/arch/ppc32/_link.ld.in
===================================================================
--- kernel/arch/ppc32/_link.ld.in	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ppc32/_link.ld.in	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -22,29 +22,70 @@
 	.unmapped (SIZEOF_HEADERS): AT (SIZEOF_HEADERS) {
 		. = ALIGN(0x100);
-		*(K_UNMAPPED_TEXT_START);
+		KEEP(*(K_UNMAPPED_TEXT_START));
 	}
 
-	.mapped PA2KA(BOOT_OFFSET): AT (BOOT_OFFSET) {
+	.text PA2KA(BOOT_OFFSET): AT (BOOT_OFFSET) {
 		ktext_start = .;
-		*(K_TEXT_START);
-		*(.text);
+		KEEP(*(K_TEXT_START));
+		*(.text .text.*);
 		ktext_end = .;
+	}
 
+	/* stack unwinding data */
+	.eh_frame_hdr : {
+		eh_frame_hdr_start = .;
+		*(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*);
+		eh_frame_hdr_end = .;
+	}
+
+	.eh_frame : {
+		eh_frame_start = .;
+		KEEP(*(.eh_frame .eh_frame.*));
+		eh_frame_end = .;
+	}
+
+	.data : {
 		kdata_start = .;
-		*(K_DATA_START);
+		KEEP(*(K_DATA_START));
 		*(.rodata .rodata.*);
-		*(.data);       /* initialized data */
-		*(.sdata);
-		*(.sdata2);
-		*(.sbss);
-		*(.bss);        /* uninitialized static variables */
+		*(.data .data.*);       /* initialized data */
+		*(.sdata .sdata.*);
+		*(.sdata2 .sdata2.*);
+		*(.sbss .sbss.*);
+		*(.bss .bss.*);        /* uninitialized static variables */
 		*(COMMON);      /* global variables */
-
-		. = ALIGN(8);
-		symbol_table = .;
-		*(symtab.*);    /* Symbol table, must be LAST symbol!*/
-
 		kdata_end = .;
 	}
+
+	.comment 0 : { *(.comment); }
+	.debug_abbrev 0 : { *(.debug_abbrev); }
+	.debug_abbrev.dwo 0 : { *(	.debug_abbrev.dwo); }
+	.debug_addr 0 : { *(.debug_addr); }
+	.debug_aranges 0 : { *(.debug_aranges); }
+	.debug_cu_index 0 : { *(.debug_cu_index); }
+	.debug_frame 0 : { *(.debug_frame); }
+	.debug_frame_hdr 0 : { *(.debug_frame_hdr); }
+	.debug_info 0 : { *(.debug_info); }
+	.debug_info.dwo 0 : { *(.debug_info.dwo); }
+	.debug_line 0 : { *(.debug_line); }
+	.debug_line.dwo 0 : { *(.debug_line.dwo); }
+	.debug_line_str 0 : { *(.debug_line_str); }
+	.debug_loc 0 : { *(.debug_loc); }
+	.debug_loclists 0 : { *(.debug_loclists); }
+	.debug_loclists.dwo 0 : { *(.debug_loclists.dwo); }
+	.debug_macinfo 0 : { *(.debug_macinfo); }
+	.debug_macro 0 : { *(.debug_macro); }
+	.debug_macro.dwo 0 : { *(.debug_macro.dwo); }
+	.debug_names 0 : { *(.debug_names); }
+	.debug_pubnames 0 : { *(.debug_pubnames); }
+	.debug_pubtypes 0 : { *(.debug_pubtypes); }
+	.debug_ranges 0 : { *(.debug_ranges); }
+	.debug_rnglists 0 : { *(.debug_rnglists); }
+	.debug_str 0 : { *(.debug_str); }
+	.debug_str.dwo 0 : { *(.debug_str.dwo); }
+	.debug_str_offsets 0 : { *(.debug_str_offsets); }
+	.debug_str_offsets.dwo 0 : { *(.debug_str_offsets.dwo); }
+	.debug_tu_index 0 : { *(.debug_tu_index); }
+	.debug_types 0 : { *(.debug_types); }
 
 	/DISCARD/ : {
Index: kernel/arch/ppc32/include/arch/asm.h
===================================================================
--- kernel/arch/ppc32/include/arch/asm.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ppc32/include/arch/asm.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -42,4 +42,8 @@
 #include <trace.h>
 
+_NO_TRACE static inline void cpu_spin_hint(void)
+{
+}
+
 _NO_TRACE static inline uint32_t msr_read(void)
 {
@@ -163,6 +167,13 @@
 }
 
-_NO_TRACE static inline void cpu_sleep(void)
-{
+/** Enables interrupts and blocks until an interrupt arrives,
+ * atomically if possible on target architecture.
+ * Disables interrupts again before returning to caller.
+ */
+_NO_TRACE static inline void cpu_interruptible_sleep(void)
+{
+	// FIXME: do this properly
+	interrupts_enable();
+	interrupts_disable();
 }
 
Index: kernel/arch/ppc32/include/arch/context_struct.h
===================================================================
--- kernel/arch/ppc32/include/arch/context_struct.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ppc32/include/arch/context_struct.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -53,6 +53,5 @@
 #define CONTEXT_OFFSET_R31  0x54
 #define CONTEXT_OFFSET_CR   0x58
-#define CONTEXT_OFFSET_IPL  0x5c
-#define CONTEXT_SIZE        0x60
+#define CONTEXT_SIZE        0x5c
 
 #ifndef __ASSEMBLER__
@@ -84,5 +83,4 @@
 	uint32_t r31;
 	uint32_t cr;
-	ipl_t ipl;
 } context_t;
 
Index: kernel/arch/ppc32/include/arch/fpu_context.h
===================================================================
--- kernel/arch/ppc32/include/arch/fpu_context.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ppc32/include/arch/fpu_context.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,6 +36,4 @@
 #define KERN_ppc32_FPU_CONTEXT_H_
 
-#define FPU_CONTEXT_ALIGN	8
-
 #include <arch/fpu_context_struct.h>
 
Index: kernel/arch/ppc32/include/arch/fpu_context_struct.h
===================================================================
--- kernel/arch/ppc32/include/arch/fpu_context_struct.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ppc32/include/arch/fpu_context_struct.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -70,5 +70,5 @@
 
 typedef struct fpu_context {
-	uint64_t fr0;
+	_Alignas(8) uint64_t fr0;
 	uint64_t fr1;
 	uint64_t fr2;
Index: kernel/arch/ppc32/src/mm/frame.c
===================================================================
--- kernel/arch/ppc32/src/mm/frame.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/ppc32/src/mm/frame.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -33,4 +33,5 @@
  */
 
+#include <arch/asm.h>
 #include <arch/boot/boot.h>
 #include <arch/mm/frame.h>
Index: kernel/arch/riscv64/_link.ld.in
===================================================================
--- kernel/arch/riscv64/_link.ld.in	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/riscv64/_link.ld.in	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -16,24 +16,66 @@
 	kernel_load_address = PA2KA(BOOT_OFFSET);
 
-	.image (PA2KA(BOOT_OFFSET) + SIZEOF_HEADERS) : AT (BOOT_OFFSET + SIZEOF_HEADERS) {
+	.text (PA2KA(BOOT_OFFSET) + SIZEOF_HEADERS) : AT (BOOT_OFFSET + SIZEOF_HEADERS) {
 		ktext_start = .;
-		*(K_TEXT_START);
-		*(.text);
+		KEEP(*(K_TEXT_START));
+		*(.text .text.*);
 		ktext_end = .;
+	}
 
+	/* stack unwinding data */
+	.eh_frame_hdr : {
+		eh_frame_hdr_start = .;
+		*(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*);
+		eh_frame_hdr_end = .;
+	}
+
+	.eh_frame : {
+		eh_frame_start = .;
+		KEEP(*(.eh_frame .eh_frame.*));
+		eh_frame_end = .;
+	}
+
+	.data : {
 		kdata_start = .;
-		*(.data);                       /* initialized data */
-		*(.rodata*);
-		*(.sdata);
-		*(.reginfo);
-		*(.sbss);
-		*(.scommon);
-		*(.bss);                        /* uninitialized static variables */
+		*(.data .data.*);                       /* initialized data */
+		*(.rodata .rodata.*);
+		*(.sdata .sdata.*);
+		*(.sbss .sbss.*);
+		*(.scommon .scommon.*);
+		*(.bss .bss.*);                        /* uninitialized static variables */
 		*(COMMON);                      /* global variables */
-		. = ALIGN(8);
-		symbol_table = .;
-		*(symtab.*);
 		kdata_end = .;
 	}
+
+	.comment 0 : { *(.comment); }
+	.debug_abbrev 0 : { *(.debug_abbrev); }
+	.debug_abbrev.dwo 0 : { *(	.debug_abbrev.dwo); }
+	.debug_addr 0 : { *(.debug_addr); }
+	.debug_aranges 0 : { *(.debug_aranges); }
+	.debug_cu_index 0 : { *(.debug_cu_index); }
+	.debug_frame 0 : { *(.debug_frame); }
+	.debug_frame_hdr 0 : { *(.debug_frame_hdr); }
+	.debug_info 0 : { *(.debug_info); }
+	.debug_info.dwo 0 : { *(.debug_info.dwo); }
+	.debug_line 0 : { *(.debug_line); }
+	.debug_line.dwo 0 : { *(.debug_line.dwo); }
+	.debug_line_str 0 : { *(.debug_line_str); }
+	.debug_loc 0 : { *(.debug_loc); }
+	.debug_loclists 0 : { *(.debug_loclists); }
+	.debug_loclists.dwo 0 : { *(.debug_loclists.dwo); }
+	.debug_macinfo 0 : { *(.debug_macinfo); }
+	.debug_macro 0 : { *(.debug_macro); }
+	.debug_macro.dwo 0 : { *(.debug_macro.dwo); }
+	.debug_names 0 : { *(.debug_names); }
+	.debug_pubnames 0 : { *(.debug_pubnames); }
+	.debug_pubtypes 0 : { *(.debug_pubtypes); }
+	.debug_ranges 0 : { *(.debug_ranges); }
+	.debug_rnglists 0 : { *(.debug_rnglists); }
+	.debug_str 0 : { *(.debug_str); }
+	.debug_str.dwo 0 : { *(.debug_str.dwo); }
+	.debug_str_offsets 0 : { *(.debug_str_offsets); }
+	.debug_str_offsets.dwo 0 : { *(.debug_str_offsets.dwo); }
+	.debug_tu_index 0 : { *(.debug_tu_index); }
+	.debug_types 0 : { *(.debug_types); }
 
 	/DISCARD/ : {
Index: kernel/arch/riscv64/include/arch/asm.h
===================================================================
--- kernel/arch/riscv64/include/arch/asm.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/riscv64/include/arch/asm.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -41,4 +41,8 @@
 #include <arch/mm/asid.h>
 #include <trace.h>
+
+_NO_TRACE static inline void cpu_spin_hint(void)
+{
+}
 
 _NO_TRACE static inline ipl_t interrupts_enable(void)
@@ -91,6 +95,13 @@
 }
 
-_NO_TRACE static inline void cpu_sleep(void)
+/** Enables interrupts and blocks until an interrupt arrives,
+ * atomically if possible on target architecture.
+ * Disables interrupts again before returning to caller.
+ */
+_NO_TRACE static inline void cpu_interruptible_sleep(void)
 {
+	// FIXME: do this properly
+	interrupts_enable();
+	interrupts_disable();
 }
 
Index: kernel/arch/riscv64/include/arch/context_struct.h
===================================================================
--- kernel/arch/riscv64/include/arch/context_struct.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/riscv64/include/arch/context_struct.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -46,6 +46,5 @@
 #define CONTEXT_OFFSET_S10  0x70
 #define CONTEXT_OFFSET_S11  0x78
-#define CONTEXT_OFFSET_IPL  0x80
-#define CONTEXT_SIZE        0x88
+#define CONTEXT_SIZE        0x80
 
 #ifndef __ASSEMBLER__
@@ -70,5 +69,4 @@
 	uint64_t s10;
 	uint64_t s11;
-	ipl_t ipl;
 } context_t;
 
Index: kernel/arch/riscv64/src/riscv64.c
===================================================================
--- kernel/arch/riscv64/src/riscv64.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/riscv64/src/riscv64.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -50,5 +50,5 @@
 #include <proc/thread.h>
 #include <console/console.h>
-#include <mem.h>
+#include <memw.h>
 #include <str.h>
 
Index: kernel/arch/sparc64/_link.ld.in
===================================================================
--- kernel/arch/sparc64/_link.ld.in	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/sparc64/_link.ld.in	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -14,27 +14,68 @@
 	kernel_load_address = VMA;
 
-	.image (VMA + SIZEOF_HEADERS): AT (LMA + SIZEOF_HEADERS) {
+	.text (VMA + SIZEOF_HEADERS): AT (LMA + SIZEOF_HEADERS) {
 		ktext_start = .;
-		*(K_TEXT_START)
+		KEEP(*(K_TEXT_START));
 		*(.text .text.*);
 		ktext_end = .;
+	}
 
+	/* stack unwinding data */
+	.eh_frame_hdr : {
+		eh_frame_hdr_start = .;
+		*(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*);
+		eh_frame_hdr_end = .;
+	}
+
+	.eh_frame : {
+		eh_frame_start = .;
+		KEEP(*(.eh_frame .eh_frame.*));
+		eh_frame_end = .;
+	}
+
+	.data : {
 		kdata_start = .;
-		*(K_DATA_START)
+		KEEP(*(K_DATA_START));
 		*(.rodata .rodata.*);
-		*(.data);                   /* initialized data */
-		*(.sdata);
-		*(.sdata2);
-		*(.sbss);
+		*(.data .data.*);                   /* initialized data */
+		*(.sdata .sdata.*);
+		*(.sdata2 .sdata2.*);
+		*(.sbss .sbss.*);
 		. = ALIGN(8);
-		*(.bss);                    /* uninitialized static variables */
+		*(.bss .bss.*);                    /* uninitialized static variables */
 		*(COMMON);                  /* global variables */
-
-		. = ALIGN(8);
-		symbol_table = .;
-		*(symtab.*);                /* Symbol table, must be LAST symbol!*/
-
 		kdata_end = .;
 	}
+
+	.comment 0 : { *(.comment); }
+	.debug_abbrev 0 : { *(.debug_abbrev); }
+	.debug_abbrev.dwo 0 : { *(	.debug_abbrev.dwo); }
+	.debug_addr 0 : { *(.debug_addr); }
+	.debug_aranges 0 : { *(.debug_aranges); }
+	.debug_cu_index 0 : { *(.debug_cu_index); }
+	.debug_frame 0 : { *(.debug_frame); }
+	.debug_frame_hdr 0 : { *(.debug_frame_hdr); }
+	.debug_info 0 : { *(.debug_info); }
+	.debug_info.dwo 0 : { *(.debug_info.dwo); }
+	.debug_line 0 : { *(.debug_line); }
+	.debug_line.dwo 0 : { *(.debug_line.dwo); }
+	.debug_line_str 0 : { *(.debug_line_str); }
+	.debug_loc 0 : { *(.debug_loc); }
+	.debug_loclists 0 : { *(.debug_loclists); }
+	.debug_loclists.dwo 0 : { *(.debug_loclists.dwo); }
+	.debug_macinfo 0 : { *(.debug_macinfo); }
+	.debug_macro 0 : { *(.debug_macro); }
+	.debug_macro.dwo 0 : { *(.debug_macro.dwo); }
+	.debug_names 0 : { *(.debug_names); }
+	.debug_pubnames 0 : { *(.debug_pubnames); }
+	.debug_pubtypes 0 : { *(.debug_pubtypes); }
+	.debug_ranges 0 : { *(.debug_ranges); }
+	.debug_rnglists 0 : { *(.debug_rnglists); }
+	.debug_str 0 : { *(.debug_str); }
+	.debug_str.dwo 0 : { *(.debug_str.dwo); }
+	.debug_str_offsets 0 : { *(.debug_str_offsets); }
+	.debug_str_offsets.dwo 0 : { *(.debug_str_offsets.dwo); }
+	.debug_tu_index 0 : { *(.debug_tu_index); }
+	.debug_types 0 : { *(.debug_types); }
 
 	/DISCARD/ : {
Index: kernel/arch/sparc64/include/arch/asm.h
===================================================================
--- kernel/arch/sparc64/include/arch/asm.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/sparc64/include/arch/asm.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -44,4 +44,8 @@
 #include <trace.h>
 
+_NO_TRACE static inline void cpu_spin_hint(void)
+{
+}
+
 _NO_TRACE static inline void pio_write_8(ioport8_t *port, uint8_t v)
 {
@@ -535,4 +539,16 @@
 extern void switch_to_userspace(uint64_t pc, uint64_t sp, uint64_t uarg);
 
+/** Enables interrupts and blocks until an interrupt arrives,
+ * atomically if possible on target architecture.
+ * Disables interrupts again before returning to caller.
+ */
+_NO_TRACE static inline void cpu_interruptible_sleep(void)
+{
+	// FIXME: do this atomically
+	interrupts_enable();
+	cpu_sleep();
+	interrupts_disable();
+}
+
 #endif
 
Index: kernel/arch/sparc64/include/arch/context_struct.h
===================================================================
--- kernel/arch/sparc64/include/arch/context_struct.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/sparc64/include/arch/context_struct.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -49,6 +49,5 @@
 #define CONTEXT_OFFSET_L7   0x88
 #define CONTEXT_OFFSET_TP   0x90
-#define CONTEXT_OFFSET_IPL  0x98
-#define CONTEXT_SIZE        0xa0
+#define CONTEXT_SIZE        0x98
 
 #ifndef __ASSEMBLER__
@@ -76,5 +75,4 @@
 	uint64_t l7;
 	uint64_t tp;  // %g7
-	ipl_t ipl;
 } context_t;
 
Index: kernel/arch/sparc64/include/arch/fpu_context.h
===================================================================
--- kernel/arch/sparc64/include/arch/fpu_context.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/sparc64/include/arch/fpu_context.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -38,9 +38,7 @@
 #include <stdint.h>
 
-#define FPU_CONTEXT_ALIGN	8
-
 typedef struct {
-	uint64_t	d[32];
-	uint64_t	fsr;
+	_Alignas(8) uint64_t d[32];
+	uint64_t fsr;
 } fpu_context_t;
 
Index: kernel/arch/sparc64/src/drivers/niagara.c
===================================================================
--- kernel/arch/sparc64/src/drivers/niagara.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/sparc64/src/drivers/niagara.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -253,5 +253,5 @@
 
 			instance->srlnin = srln;
-			thread_ready(instance->thread);
+			thread_start(instance->thread);
 		}
 	}
Index: kernel/arch/sparc64/src/drivers/tick.c
===================================================================
--- kernel/arch/sparc64/src/drivers/tick.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/sparc64/src/drivers/tick.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -117,5 +117,5 @@
 	while (drift > CPU->arch.clock_frequency / HZ) {
 		drift -= CPU->arch.clock_frequency / HZ;
-		CPU->missed_clock_ticks++;
+		CPU_LOCAL->missed_clock_ticks++;
 	}
 	CPU->arch.next_tick_cmpr = tick_counter_read() +
Index: kernel/arch/sparc64/src/mm/sun4u/as.c
===================================================================
--- kernel/arch/sparc64/src/mm/sun4u/as.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/sparc64/src/mm/sun4u/as.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -47,5 +47,5 @@
 #include <bitops.h>
 #include <macros.h>
-#include <mem.h>
+#include <memw.h>
 
 #endif /* CONFIG_TSB */
Index: kernel/arch/sparc64/src/mm/sun4v/as.c
===================================================================
--- kernel/arch/sparc64/src/mm/sun4v/as.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/sparc64/src/mm/sun4v/as.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -50,5 +50,5 @@
 #include <bitops.h>
 #include <macros.h>
-#include <mem.h>
+#include <memw.h>
 
 #endif /* CONFIG_TSB */
Index: kernel/arch/sparc64/src/proc/sun4u/scheduler.c
===================================================================
--- kernel/arch/sparc64/src/proc/sun4u/scheduler.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/sparc64/src/proc/sun4u/scheduler.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -76,4 +76,6 @@
 {
 	if (THREAD->uspace) {
+		asm volatile ("flushw");
+
 		/* sample the state of the userspace window buffer */
 		THREAD->arch.uspace_window_buffer =
Index: kernel/arch/sparc64/src/proc/sun4v/scheduler.c
===================================================================
--- kernel/arch/sparc64/src/proc/sun4v/scheduler.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/sparc64/src/proc/sun4v/scheduler.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -68,4 +68,6 @@
 {
 	if (THREAD->uspace) {
+		asm volatile ("flushw");
+
 		/* sample the state of the userspace window buffer */
 		THREAD->arch.uspace_window_buffer =
Index: kernel/arch/sparc64/src/smp/sun4u/smp.c
===================================================================
--- kernel/arch/sparc64/src/smp/sun4u/smp.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/sparc64/src/smp/sun4u/smp.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -106,6 +106,5 @@
 	waking_up_mid = mid;
 
-	if (waitq_sleep_timeout(&ap_completion_wq, 1000000,
-	    SYNCH_FLAGS_NONE, NULL) == ETIMEOUT)
+	if (semaphore_down_timeout(&ap_completion_semaphore, 1000000) != EOK)
 		log(LF_ARCH, LVL_NOTE, "%s: waiting for processor (mid = %" PRIu32
 		    ") timed out", __func__, mid);
Index: kernel/arch/sparc64/src/smp/sun4v/smp.c
===================================================================
--- kernel/arch/sparc64/src/smp/sun4v/smp.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/sparc64/src/smp/sun4v/smp.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -373,6 +373,5 @@
 #endif
 
-	if (waitq_sleep_timeout(&ap_completion_wq, 10000000,
-	    SYNCH_FLAGS_NONE, NULL) == ETIMEOUT)
+	if (semaphore_down_timeout(&ap_completion_semaphore, 10000000) != EOK)
 		printf("%s: waiting for processor (cpuid = %" PRIu64 ") timed out\n",
 		    __func__, cpuid);
Index: kernel/arch/sparc64/src/sun4u/start.S
===================================================================
--- kernel/arch/sparc64/src/sun4u/start.S	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/arch/sparc64/src/sun4u/start.S	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -356,6 +356,5 @@
 	 * in the ctx global variable.
 	 */
-	set ctx, %g1
-	add %g1, CONTEXT_OFFSET_SP, %g1
+	set bootstrap_stack_top, %g1
 	ldx [%g1], %o6
 
Index: kernel/genarch/include/genarch/mm/page_ht.h
===================================================================
--- kernel/genarch/include/genarch/mm/page_ht.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/genarch/include/genarch/mm/page_ht.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -57,10 +57,10 @@
 #define PTE_EXECUTABLE(pte)  ((pte)->x != 0)
 
-extern as_operations_t as_ht_operations;
-extern page_mapping_operations_t ht_mapping_operations;
+extern const as_operations_t as_ht_operations;
+extern const page_mapping_operations_t ht_mapping_operations;
 
 extern slab_cache_t *pte_cache;
 extern hash_table_t page_ht;
-extern hash_table_ops_t ht_ops;
+extern const hash_table_ops_t ht_ops;
 
 #endif
Index: kernel/genarch/include/genarch/mm/page_pt.h
===================================================================
--- kernel/genarch/include/genarch/mm/page_pt.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/genarch/include/genarch/mm/page_pt.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -140,6 +140,6 @@
 #define PTE_EXECUTABLE(p)  PTE_EXECUTABLE_ARCH((p))
 
-extern as_operations_t as_pt_operations;
-extern page_mapping_operations_t pt_mapping_operations;
+extern const as_operations_t as_pt_operations;
+extern const page_mapping_operations_t pt_mapping_operations;
 
 extern void page_mapping_insert_pt(as_t *, uintptr_t, uintptr_t, unsigned int);
Index: kernel/genarch/src/acpi/acpi.c
===================================================================
--- kernel/genarch/src/acpi/acpi.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/genarch/src/acpi/acpi.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -43,5 +43,5 @@
 #include <mm/km.h>
 #include <log.h>
-#include <mem.h>
+#include <memw.h>
 
 #define RSDP_SIGNATURE      "RSD PTR "
Index: kernel/genarch/src/drivers/ega/ega.c
===================================================================
--- kernel/genarch/src/drivers/ega/ega.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/genarch/src/drivers/ega/ega.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -42,5 +42,5 @@
 #include <typedefs.h>
 #include <arch/asm.h>
-#include <mem.h>
+#include <memw.h>
 #include <str.h>
 #include <console/chardev.h>
Index: kernel/genarch/src/drivers/via-cuda/cuda.c
===================================================================
--- kernel/genarch/src/drivers/via-cuda/cuda.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/genarch/src/drivers/via-cuda/cuda.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -41,5 +41,5 @@
 #include <stdlib.h>
 #include <synch/spinlock.h>
-#include <mem.h>
+#include <memw.h>
 
 static irq_ownership_t cuda_claim(irq_t *irq);
Index: kernel/genarch/src/fb/fb.c
===================================================================
--- kernel/genarch/src/fb/fb.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/genarch/src/fb/fb.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -45,5 +45,5 @@
 #include <align.h>
 #include <panic.h>
-#include <mem.h>
+#include <memw.h>
 #include <config.h>
 #include <bitops.h>
@@ -633,5 +633,5 @@
 
 	instance->addr = (uint8_t *) km_map((uintptr_t) props->addr, fbsize,
-	    KM_NATURAL_ALIGNMENT, PAGE_WRITE | PAGE_NOT_CACHEABLE);
+	    KM_NATURAL_ALIGNMENT, PAGE_WRITE | PAGE_WRITE_COMBINE);
 	if (!instance->addr) {
 		LOG("Unable to map framebuffer.");
Index: kernel/genarch/src/kbrd/kbrd.c
===================================================================
--- kernel/genarch/src/kbrd/kbrd.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/genarch/src/kbrd/kbrd.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -200,5 +200,5 @@
 
 	instance->sink = sink;
-	thread_ready(instance->thread);
+	thread_start(instance->thread);
 
 	return &instance->raw;
Index: kernel/genarch/src/kbrd/kbrd_at.c
===================================================================
--- kernel/genarch/src/kbrd/kbrd_at.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/genarch/src/kbrd/kbrd_at.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -198,5 +198,5 @@
 
 	instance->sink = sink;
-	thread_ready(instance->thread);
+	thread_start(instance->thread);
 
 	return &instance->raw;
Index: kernel/genarch/src/mm/as_ht.c
===================================================================
--- kernel/genarch/src/mm/as_ht.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/genarch/src/mm/as_ht.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -53,5 +53,5 @@
 static bool ht_locked(as_t *);
 
-as_operations_t as_ht_operations = {
+const as_operations_t as_ht_operations = {
 	.page_table_create = ht_create,
 	.page_table_destroy = ht_destroy,
Index: kernel/genarch/src/mm/as_pt.c
===================================================================
--- kernel/genarch/src/mm/as_pt.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/genarch/src/mm/as_pt.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -44,5 +44,5 @@
 #include <arch/mm/as.h>
 #include <typedefs.h>
-#include <mem.h>
+#include <memw.h>
 #include <arch.h>
 
@@ -54,5 +54,5 @@
 static bool pt_locked(as_t *);
 
-as_operations_t as_pt_operations = {
+const as_operations_t as_pt_operations = {
 	.page_table_create = ptl0_create,
 	.page_table_destroy = ptl0_destroy,
Index: kernel/genarch/src/mm/page_ht.c
===================================================================
--- kernel/genarch/src/mm/page_ht.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/genarch/src/mm/page_ht.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -82,5 +82,5 @@
 
 /** Hash table operations for page hash table. */
-hash_table_ops_t ht_ops = {
+const hash_table_ops_t ht_ops = {
 	.hash = ht_hash,
 	.key_hash = ht_key_hash,
@@ -90,5 +90,5 @@
 
 /** Page mapping operations for page hash table architectures. */
-page_mapping_operations_t ht_mapping_operations = {
+const page_mapping_operations_t ht_mapping_operations = {
 	.mapping_insert = ht_mapping_insert,
 	.mapping_remove = ht_mapping_remove,
Index: kernel/genarch/src/mm/page_pt.c
===================================================================
--- kernel/genarch/src/mm/page_pt.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/genarch/src/mm/page_pt.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -47,5 +47,5 @@
 #include <typedefs.h>
 #include <arch/asm.h>
-#include <mem.h>
+#include <memw.h>
 #include <align.h>
 #include <macros.h>
@@ -58,5 +58,5 @@
 static void pt_mapping_make_global(uintptr_t, size_t);
 
-page_mapping_operations_t pt_mapping_operations = {
+const page_mapping_operations_t pt_mapping_operations = {
 	.mapping_insert = pt_mapping_insert,
 	.mapping_remove = pt_mapping_remove,
Index: kernel/genarch/src/ofw/ofw_tree.c
===================================================================
--- kernel/genarch/src/ofw/ofw_tree.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/genarch/src/ofw/ofw_tree.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -39,5 +39,5 @@
 #include <stdlib.h>
 #include <sysinfo/sysinfo.h>
-#include <mem.h>
+#include <memw.h>
 #include <str.h>
 #include <panic.h>
Index: kernel/genarch/src/srln/srln.c
===================================================================
--- kernel/genarch/src/srln/srln.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/genarch/src/srln/srln.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -156,5 +156,5 @@
 
 	instance->sink = sink;
-	thread_ready(instance->thread);
+	thread_start(instance->thread);
 
 	return &instance->raw;
Index: kernel/generic/common
===================================================================
--- kernel/generic/common	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ kernel/generic/common	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,1 @@
+../../common
Index: rnel/generic/include/adt/bitmap.h
===================================================================
--- kernel/generic/include/adt/bitmap.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,92 +1,0 @@
-/*
- * Copyright (c) 2006 Jakub Jermar
- * 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 kernel_generic_adt
- * @{
- */
-/** @file
- */
-
-#ifndef KERN_BITMAP_H_
-#define KERN_BITMAP_H_
-
-#include <stddef.h>
-#include <stdbool.h>
-#include <stdint.h>
-
-#define BITMAP_ELEMENT   8
-#define BITMAP_REMAINER  7
-
-typedef struct {
-	size_t elements;
-	uint8_t *bits;
-	size_t next_fit;
-} bitmap_t;
-
-static inline void bitmap_set(bitmap_t *bitmap, size_t element,
-    unsigned int value)
-{
-	if (element >= bitmap->elements)
-		return;
-
-	size_t byte = element / BITMAP_ELEMENT;
-	uint8_t mask = 1 << (element & BITMAP_REMAINER);
-
-	if (value) {
-		bitmap->bits[byte] |= mask;
-	} else {
-		bitmap->bits[byte] &= ~mask;
-		bitmap->next_fit = byte;
-	}
-}
-
-static inline unsigned int bitmap_get(bitmap_t *bitmap, size_t element)
-{
-	if (element >= bitmap->elements)
-		return 0;
-
-	size_t byte = element / BITMAP_ELEMENT;
-	uint8_t mask = 1 << (element & BITMAP_REMAINER);
-
-	return !!((bitmap->bits)[byte] & mask);
-}
-
-extern size_t bitmap_size(size_t);
-extern void bitmap_initialize(bitmap_t *, size_t, void *);
-
-extern void bitmap_set_range(bitmap_t *, size_t, size_t);
-extern void bitmap_clear_range(bitmap_t *, size_t, size_t);
-
-extern bool bitmap_allocate_range(bitmap_t *, size_t, size_t, size_t, size_t,
-    size_t *);
-extern void bitmap_copy(bitmap_t *, bitmap_t *, size_t);
-
-#endif
-
-/** @}
- */
Index: rnel/generic/include/adt/fifo.h
===================================================================
--- kernel/generic/include/adt/fifo.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,122 +1,0 @@
-/*
- * Copyright (c) 2006 Jakub Jermar
- * 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 kernel_generic_adt
- * @{
- */
-/** @file
- */
-
-/*
- * This implementation of FIFO stores values in an array
- * (static or dynamic). As such, these FIFOs have upper bound
- * on number of values they can store. Push and pop operations
- * are done via accessing the array through head and tail indices.
- * Because of better operation ordering in fifo_pop(), the access
- * policy for these two indices is to 'increment (mod size of FIFO)
- * and use'.
- */
-
-#ifndef KERN_FIFO_H_
-#define KERN_FIFO_H_
-
-#include <stdlib.h>
-
-/** Create and initialize static FIFO.
- *
- * FIFO is allocated statically.
- * This macro is suitable for creating smaller FIFOs.
- *
- * @param name Name of FIFO.
- * @param t Type of values stored in FIFO.
- * @param itms Number of items that can be stored in FIFO.
- */
-#define FIFO_INITIALIZE_STATIC(name, t, itms)		\
-	struct {					\
-		t fifo[(itms)];				\
-		size_t items;				\
-		size_t head;				\
-		size_t tail;				\
-	} name = {					\
-		.items = (itms),			\
-		.head = 0,				\
-		.tail = 0				\
-	}
-
-/** Create and prepare dynamic FIFO.
- *
- * FIFO is allocated dynamically.
- * This macro is suitable for creating larger FIFOs.
- *
- * @param name Name of FIFO.
- * @param t Type of values stored in FIFO.
- * @param itms Number of items that can be stored in FIFO.
- */
-#define FIFO_INITIALIZE_DYNAMIC(name, t, itms)		\
-	struct {					\
-		t *fifo;				\
-		size_t items;				\
-		size_t head;				\
-		size_t tail;				\
-	} name = {					\
-		.fifo = NULL,				\
-		.items = (itms),			\
-		.head = 0,				\
-		.tail = 0				\
-	}
-
-/** Pop value from head of FIFO.
- *
- * @param name FIFO name.
- *
- * @return Leading value in FIFO.
- */
-#define fifo_pop(name) \
-	name.fifo[name.head = (name.head + 1) < name.items ? (name.head + 1) : 0]
-
-/** Push value to tail of FIFO.
- *
- * @param name FIFO name.
- * @param value Value to be appended to FIFO.
- *
- */
-#define fifo_push(name, value) \
-	name.fifo[name.tail = \
-	    (name.tail + 1) < name.items ? (name.tail + 1) : 0] = (value)
-
-/** Allocate memory for dynamic FIFO.
- *
- * @param name FIFO name.
- */
-#define fifo_create(name) \
-	name.fifo = malloc(sizeof(*name.fifo) * name.items)
-
-#endif
-
-/** @}
- */
Index: rnel/generic/include/adt/hash.h
===================================================================
--- kernel/generic/include/adt/hash.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,112 +1,0 @@
-/*
- * Copyright (c) 2012 Adam Hraska
- * 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 kernel_generic_adt
- * @{
- */
-/** @file
- */
-#ifndef KERN_HASH_H_
-#define KERN_HASH_H_
-
-#include <stdint.h>
-
-/** Produces a uniform hash affecting all output bits from the skewed input. */
-static inline uint32_t hash_mix32(uint32_t hash)
-{
-	/*
-	 * Thomas Wang's modification of Bob Jenkin's hash mixing function:
-	 * http://www.concentric.net/~Ttwang/tech/inthash.htm
-	 * Public domain.
-	 */
-	hash = ~hash + (hash << 15);
-	hash = hash ^ (hash >> 12);
-	hash = hash + (hash << 2);
-	hash = hash ^ (hash >> 4);
-	hash = hash * 2057;
-	hash = hash ^ (hash >> 16);
-	return hash;
-}
-
-/** Produces a uniform hash affecting all output bits from the skewed input. */
-static inline uint64_t hash_mix64(uint64_t hash)
-{
-	/*
-	 * Thomas Wang's public domain 64-bit hash mixing function:
-	 * http://www.concentric.net/~Ttwang/tech/inthash.htm
-	 */
-	hash = (hash ^ 61) ^ (hash >> 16);
-	hash = hash + (hash << 3);
-	hash = hash ^ (hash >> 4);
-	hash = hash * 0x27d4eb2d;
-	hash = hash ^ (hash >> 15);
-	/*
-	 * Lower order bits are mixed more thoroughly. Swap them with
-	 * the higher order bits and make the resulting higher order bits
-	 * more usable.
-	 */
-	return (hash << 32) | (hash >> 32);
-}
-
-/** Produces a uniform hash affecting all output bits from the skewed input. */
-static inline size_t hash_mix(size_t hash)
-{
-#ifdef __32_BITS__
-	return hash_mix32(hash);
-#elif defined(__64_BITS__)
-	return hash_mix64(hash);
-#else
-#error Unknown size_t size - cannot select proper hash mix function.
-#endif
-}
-
-/** Use to create a hash from multiple values.
- *
- * Typical usage:
- * @code
- * int car_id;
- * bool car_convertible;
- * // ..
- * size_t hash = 0;
- * hash = hash_combine(hash, car_id);
- * hash = hash_combine(hash, car_convertible);
- * // Now use hash as a hash of both car_id and car_convertible.
- * @endcode
- */
-static inline size_t hash_combine(size_t seed, size_t hash)
-{
-	/*
-	 * todo: use Bob Jenkin's proper mixing hash pass:
-	 * http://burtleburtle.net/bob/c/lookup3.c
-	 */
-	seed ^= hash + 0x9e3779b9 +
-	    ((seed << 5) | (seed >> (sizeof(size_t) * 8 - 5)));
-	return seed;
-}
-
-#endif
Index: rnel/generic/include/adt/hash_table.h
===================================================================
--- kernel/generic/include/adt/hash_table.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,108 +1,0 @@
-/*
- * Copyright (c) 2006 Jakub Jermar
- * Copyright (c) 2012 Adam Hraska
- *
- * 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 kernel_generic
- * @{
- */
-/** @file
- */
-
-#ifndef KERNEL_HASH_TABLE_H_
-#define KERNEL_HASH_TABLE_H_
-
-#include <adt/list.h>
-#include <stdbool.h>
-#include <macros.h>
-#include <member.h>
-
-/** Opaque hash table link type. */
-typedef struct ht_link {
-	link_t link;
-} ht_link_t;
-
-/** Set of operations for hash table. */
-typedef struct {
-	/** Returns the hash of the key stored in the item (ie its lookup key). */
-	size_t (*hash)(const ht_link_t *item);
-
-	/** Returns the hash of the key. */
-	size_t (*key_hash)(const void *key);
-
-	/** True if the items are equal (have the same lookup keys). */
-	bool (*equal)(const ht_link_t *item1, const ht_link_t *item2);
-
-	/** Returns true if the key is equal to the item's lookup key. */
-	bool (*key_equal)(const void *key, const ht_link_t *item);
-
-	/** Hash table item removal callback.
-	 *
-	 * Must not invoke any mutating functions of the hash table.
-	 *
-	 * @param item Item that was removed from the hash table.
-	 */
-	void (*remove_callback)(ht_link_t *item);
-} hash_table_ops_t;
-
-/** Hash table structure. */
-typedef struct {
-	hash_table_ops_t *op;
-	list_t *bucket;
-	size_t bucket_cnt;
-	size_t full_item_cnt;
-	size_t item_cnt;
-	size_t max_load;
-	bool apply_ongoing;
-} hash_table_t;
-
-#define hash_table_get_inst(item, type, member) \
-	member_to_inst((item), type, member)
-
-extern bool hash_table_create(hash_table_t *, size_t, size_t,
-    hash_table_ops_t *);
-extern void hash_table_destroy(hash_table_t *);
-
-extern bool hash_table_empty(hash_table_t *);
-extern size_t hash_table_size(hash_table_t *);
-
-extern void hash_table_clear(hash_table_t *);
-extern void hash_table_insert(hash_table_t *, ht_link_t *);
-extern bool hash_table_insert_unique(hash_table_t *, ht_link_t *);
-extern ht_link_t *hash_table_find(const hash_table_t *, const void *);
-extern ht_link_t *hash_table_find_next(const hash_table_t *, ht_link_t *,
-    ht_link_t *);
-extern size_t hash_table_remove(hash_table_t *, const void *);
-extern void hash_table_remove_item(hash_table_t *, ht_link_t *);
-extern void hash_table_apply(hash_table_t *, bool (*)(ht_link_t *, void *),
-    void *);
-
-#endif
-
-/** @}
- */
Index: rnel/generic/include/adt/list.h
===================================================================
--- kernel/generic/include/adt/list.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,413 +1,0 @@
-/*
- * Copyright (c) 2001-2004 Jakub Jermar
- * Copyright (c) 2013 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 kernel_generic_adt
- * @{
- */
-/** @file
- */
-
-#ifndef KERN_LIST_H_
-#define KERN_LIST_H_
-
-#include <assert.h>
-#include <macros.h>
-#include <member.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <trace.h>
-
-/** Doubly linked list link. */
-typedef struct link {
-	struct link *prev;  /**< Pointer to the previous item in the list. */
-	struct link *next;  /**< Pointer to the next item in the list. */
-} link_t;
-
-/** Doubly linked list. */
-typedef struct list {
-	link_t head;  /**< List head. Does not have any data. */
-} list_t;
-
-extern bool list_member(const link_t *, const list_t *);
-extern void list_splice(list_t *, link_t *);
-extern unsigned long list_count(const list_t *);
-
-/** Declare and initialize statically allocated list.
- *
- * @param name Name of the new statically allocated list.
- *
- */
-#define LIST_INITIALIZE(name) \
-	list_t name = LIST_INITIALIZER(name)
-
-/** Initializer for statically allocated list.
- *
- * @code
- * struct named_list {
- *     const char *name;
- *     list_t list;
- * } var = {
- *     .name = "default name",
- *     .list = LIST_INITIALIZER(name_list.list)
- * };
- * @endcode
- *
- * @param name Name of the new statically allocated list.
- *
- */
-#define LIST_INITIALIZER(name) \
-	{ \
-		.head = { \
-			.prev = &(name).head, \
-			.next = &(name).head \
-		} \
-	}
-
-#define list_get_instance(link, type, member) \
-	member_to_inst(link, type, member)
-
-#define list_foreach(list, member, itype, iterator) \
-	for (itype *iterator = NULL; iterator == NULL; iterator = (itype *) sizeof(itype)) \
-		for (link_t *_link = (list).head.next; \
-		    iterator = list_get_instance(_link, itype, member), \
-		    _link != &(list).head; _link = _link->next)
-
-#define list_foreach_rev(list, member, itype, iterator) \
-	for (itype *iterator = NULL; iterator == NULL; iterator = (itype *) sizeof(itype)) \
-		for (link_t *_link = (list).head.prev; \
-		    iterator = list_get_instance(_link, itype, member), \
-		    _link != &(list).head; _link = _link->prev)
-
-/** Unlike list_foreach(), allows removing items while traversing a list.
- *
- * @code
- * list_t mylist;
- * typedef struct item {
- *     int value;
- *     link_t item_link;
- * } item_t;
- *
- * //..
- *
- * // Print each list element's value and remove the element from the list.
- * list_foreach_safe(mylist, cur_link, next_link) {
- *     item_t *cur_item = list_get_instance(cur_link, item_t, item_link);
- *     printf("%d\n", cur_item->value);
- *     list_remove(cur_link);
- * }
- * @endcode
- *
- * @param list List to traverse.
- * @param iterator Iterator to the current element of the list.
- *             The item this iterator points may be safely removed
- *             from the list.
- * @param next_iter Iterator to the next element of the list.
- */
-#define list_foreach_safe(list, iterator, next_iter) \
-	for (link_t *iterator = (list).head.next, \
-	    *next_iter = iterator->next; \
-	    iterator != &(list).head; \
-	    iterator = next_iter, next_iter = iterator->next)
-
-#define assert_link_not_used(link) \
-	assert(!link_used(link))
-
-/** Returns true if the link is definitely part of a list. False if not sure. */
-static inline bool link_in_use(const link_t *link)
-{
-	return link->prev != NULL && link->next != NULL;
-}
-
-/** Initialize doubly-linked circular list link
- *
- * Initialize doubly-linked list link.
- *
- * @param link Pointer to link_t structure to be initialized.
- *
- */
-_NO_TRACE static inline void link_initialize(link_t *link)
-{
-	link->prev = NULL;
-	link->next = NULL;
-}
-
-/** Initialize doubly-linked circular list
- *
- * Initialize doubly-linked circular list.
- *
- * @param list Pointer to list_t structure.
- *
- */
-_NO_TRACE static inline void list_initialize(list_t *list)
-{
-	list->head.prev = &list->head;
-	list->head.next = &list->head;
-}
-
-/** Insert item before another item in doubly-linked circular list.
- *
- */
-static inline void list_insert_before(link_t *lnew, link_t *lold)
-{
-	lnew->next = lold;
-	lnew->prev = lold->prev;
-	lold->prev->next = lnew;
-	lold->prev = lnew;
-}
-
-/** Insert item after another item in doubly-linked circular list.
- *
- */
-static inline void list_insert_after(link_t *lnew, link_t *lold)
-{
-	lnew->prev = lold;
-	lnew->next = lold->next;
-	lold->next->prev = lnew;
-	lold->next = lnew;
-}
-
-/** Add item to the beginning of doubly-linked circular list
- *
- * Add item to the beginning of doubly-linked circular list.
- *
- * @param link Pointer to link_t structure to be added.
- * @param list Pointer to list_t structure.
- *
- */
-_NO_TRACE static inline void list_prepend(link_t *link, list_t *list)
-{
-	list_insert_after(link, &list->head);
-}
-
-/** Add item to the end of doubly-linked circular list
- *
- * Add item to the end of doubly-linked circular list.
- *
- * @param link Pointer to link_t structure to be added.
- * @param list Pointer to list_t structure.
- *
- */
-_NO_TRACE static inline void list_append(link_t *link, list_t *list)
-{
-	list_insert_before(link, &list->head);
-}
-
-/** Remove item from doubly-linked circular list
- *
- * Remove item from doubly-linked circular list.
- *
- * @param link Pointer to link_t structure to be removed from the list
- *             it is contained in.
- *
- */
-_NO_TRACE static inline void list_remove(link_t *link)
-{
-	if ((link->prev != NULL) && (link->next != NULL)) {
-		link->next->prev = link->prev;
-		link->prev->next = link->next;
-	}
-
-	link_initialize(link);
-}
-
-/** Query emptiness of doubly-linked circular list
- *
- * Query emptiness of doubly-linked circular list.
- *
- * @param list Pointer to lins_t structure.
- *
- */
-_NO_TRACE static inline bool list_empty(const list_t *list)
-{
-	return (list->head.next == &list->head);
-}
-
-/** Get first item in list.
- *
- * @param list Pointer to list_t structure.
- *
- * @return Head item of the list.
- * @return NULL if the list is empty.
- *
- */
-static inline link_t *list_first(const list_t *list)
-{
-	return ((list->head.next == &list->head) ? NULL : list->head.next);
-}
-
-/** Get last item in list.
- *
- * @param list Pointer to list_t structure.
- *
- * @return Head item of the list.
- * @return NULL if the list is empty.
- *
- */
-static inline link_t *list_last(const list_t *list)
-{
-	return (list->head.prev == &list->head) ? NULL : list->head.prev;
-}
-
-/** Get next item in list.
- *
- * @param link Current item link
- * @param list List containing @a link
- *
- * @return Next item or NULL if @a link is the last item.
- */
-static inline link_t *list_next(const link_t *link, const list_t *list)
-{
-	return (link->next == &list->head) ? NULL : link->next;
-}
-
-/** Get previous item in list.
- *
- * @param link Current item link
- * @param list List containing @a link
- *
- * @return Previous item or NULL if @a link is the first item.
- */
-static inline link_t *list_prev(const link_t *link, const list_t *list)
-{
-	return (link->prev == &list->head) ? NULL : link->prev;
-}
-
-/** Split or concatenate headless doubly-linked circular list
- *
- * Split or concatenate headless doubly-linked circular list.
- *
- * Note that the algorithm works both directions:
- * concatenates splitted lists and splits concatenated lists.
- *
- * @param part1 Pointer to link_t structure leading the first
- *              (half of the headless) list.
- * @param part2 Pointer to link_t structure leading the second
- *              (half of the headless) list.
- *
- */
-_NO_TRACE static inline void headless_list_split_or_concat(link_t *part1, link_t *part2)
-{
-	part1->prev->next = part2;
-	part2->prev->next = part1;
-
-	link_t *hlp = part1->prev;
-
-	part1->prev = part2->prev;
-	part2->prev = hlp;
-}
-
-/** Split headless doubly-linked circular list
- *
- * Split headless doubly-linked circular list.
- *
- * @param part1 Pointer to link_t structure leading
- *              the first half of the headless list.
- * @param part2 Pointer to link_t structure leading
- *              the second half of the headless list.
- *
- */
-_NO_TRACE static inline void headless_list_split(link_t *part1, link_t *part2)
-{
-	headless_list_split_or_concat(part1, part2);
-}
-
-/** Concatenate two headless doubly-linked circular lists
- *
- * Concatenate two headless doubly-linked circular lists.
- *
- * @param part1 Pointer to link_t structure leading
- *              the first headless list.
- * @param part2 Pointer to link_t structure leading
- *              the second headless list.
- *
- */
-_NO_TRACE static inline void headless_list_concat(link_t *part1, link_t *part2)
-{
-	headless_list_split_or_concat(part1, part2);
-}
-
-/** Concatenate two lists
- *
- * Concatenate lists @a list1 and @a list2, producing a single
- * list @a list1 containing items from both (in @a list1, @a list2
- * order) and empty list @a list2.
- *
- * @param list1		First list and concatenated output
- * @param list2 	Second list and empty output.
- *
- */
-_NO_TRACE static inline void list_concat(list_t *list1, list_t *list2)
-{
-	list_splice(list2, list1->head.prev);
-}
-
-/** Get n-th item in a list.
- *
- * @param list Pointer to link_t structure representing the list.
- * @param n    Item number (indexed from zero).
- *
- * @return n-th item of the list.
- * @return NULL if no n-th item found.
- *
- */
-static inline link_t *list_nth(const list_t *list, size_t n)
-{
-	size_t cnt = 0;
-
-	link_t *link = list_first(list);
-	while (link != NULL) {
-		if (cnt == n)
-			return link;
-
-		cnt++;
-		link = list_next(link, list);
-	}
-
-	return NULL;
-}
-
-/** Determine if link is used.
- *
- * @param link Link
- * @return @c true if link is used, @c false if not.
- */
-static inline bool link_used(link_t *link)
-{
-	if (link->prev == NULL && link->next == NULL)
-		return false;
-
-	assert(link->prev != NULL && link->next != NULL);
-	return true;
-}
-
-#endif
-
-/** @}
- */
Index: rnel/generic/include/adt/odict.h
===================================================================
--- kernel/generic/include/adt/odict.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,71 +1,0 @@
-/*
- * Copyright (c) 2016 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 kernel_generic_adt
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_ODICT_H_
-#define _LIBC_ODICT_H_
-
-#include <errno.h>
-#include <member.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <types/adt/odict.h>
-
-#define odict_get_instance(odlink, type, member) \
-	member_to_inst(odlink, type, member)
-
-extern void odict_initialize(odict_t *, odgetkey_t, odcmp_t);
-extern void odict_finalize(odict_t *);
-extern void odlink_initialize(odlink_t *);
-extern void odict_insert(odlink_t *, odict_t *, odlink_t *);
-extern void odict_remove(odlink_t *);
-extern void odict_key_update(odlink_t *, odict_t *);
-extern bool odlink_used(odlink_t *);
-extern bool odict_empty(odict_t *);
-extern unsigned long odict_count(odict_t *);
-extern odlink_t *odict_first(odict_t *);
-extern odlink_t *odict_last(odict_t *);
-extern odlink_t *odict_prev(odlink_t *, odict_t *);
-extern odlink_t *odict_next(odlink_t *, odict_t *);
-extern odlink_t *odict_find_eq(odict_t *, void *, odlink_t *);
-extern odlink_t *odict_find_eq_last(odict_t *, void *, odlink_t *);
-extern odlink_t *odict_find_geq(odict_t *, void *, odlink_t *);
-extern odlink_t *odict_find_gt(odict_t *, void *, odlink_t *);
-extern odlink_t *odict_find_leq(odict_t *, void *, odlink_t *);
-extern odlink_t *odict_find_lt(odict_t *, void *, odlink_t *);
-extern errno_t odict_validate(odict_t *);
-
-#endif
-
-/** @}
- */
Index: kernel/generic/include/arch.h
===================================================================
--- kernel/generic/include/arch.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/arch.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -75,4 +75,5 @@
 typedef struct {
 	size_t preemption;      /**< Preemption disabled counter and flag. */
+	size_t mutex_locks;
 	struct thread *thread;  /**< Current thread. */
 	struct task *task;      /**< Current task. */
Index: kernel/generic/include/atomic.h
===================================================================
--- kernel/generic/include/atomic.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/atomic.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -40,4 +40,14 @@
 #include <stdatomic.h>
 
+/*
+ * Shorthand for relaxed atomic read/write, something that's needed to formally
+ * avoid undefined behavior in cases where we need to read a variable in
+ * different threads and we don't particularly care about ordering
+ * (e.g. statistic printouts). This is most likely translated into the same
+ * assembly instructions as regular read/writes.
+ */
+#define atomic_set_unordered(var, val) atomic_store_explicit((var), (val), memory_order_relaxed)
+#define atomic_get_unordered(var) atomic_load_explicit((var), memory_order_relaxed)
+
 #define atomic_predec(val) \
 	(atomic_fetch_sub((val), 1) - 1)
@@ -63,4 +73,95 @@
 	    (new_val), memory_order_relaxed)
 
+#if __64_BITS__
+
+typedef struct {
+	atomic_uint_fast64_t value;
+} atomic_time_stat_t;
+
+#define ATOMIC_TIME_INITIALIZER() (atomic_time_stat_t) {}
+
+static inline void atomic_time_increment(atomic_time_stat_t *time, int a)
+{
+	/*
+	 * We require increments to be synchronized with each other, so we
+	 * can use ordinary reads and writes instead of a more expensive atomic
+	 * read-modify-write operations.
+	 */
+	uint64_t v = atomic_load_explicit(&time->value, memory_order_relaxed);
+	atomic_store_explicit(&time->value, v + a, memory_order_relaxed);
+}
+
+static inline uint64_t atomic_time_read(atomic_time_stat_t *time)
+{
+	return atomic_load_explicit(&time->value, memory_order_relaxed);
+}
+
+#else
+
+/**
+ * A monotonically increasing 64b time statistic.
+ * Increments must be synchronized with each other (or limited to a single
+ * thread/CPU), but reads can be performed from any thread.
+ *
+ */
+typedef struct {
+	uint64_t true_value;
+	atomic_uint_fast32_t high1;
+	atomic_uint_fast32_t high2;
+	atomic_uint_fast32_t low;
+} atomic_time_stat_t;
+
+#define ATOMIC_TIME_INITIALIZER() (atomic_time_stat_t) {}
+
+static inline void atomic_time_increment(atomic_time_stat_t *time, int a)
+{
+	/*
+	 * On 32b architectures, we can't rely on 64b memory reads/writes being
+	 * architecturally atomic, but we also don't want to pay the cost of
+	 * emulating atomic reads/writes, so instead we split value in half
+	 * and perform some ordering magic to make sure readers always get
+	 * consistent value.
+	 */
+
+	/* true_value is only used by the writer, so this need not be atomic. */
+	uint64_t val = time->true_value;
+	uint32_t old_high = val >> 32;
+	val += a;
+	uint32_t new_high = val >> 32;
+	time->true_value = val;
+
+	/* Tell GCC that the first branch is far more likely than the second. */
+	if (__builtin_expect(old_high == new_high, 1)) {
+		/* If the high half didn't change, we need not bother with barriers. */
+		atomic_store_explicit(&time->low, (uint32_t) val, memory_order_relaxed);
+	} else {
+		/*
+		 * If both halves changed, extra ordering is necessary.
+		 * The idea is that if reader reads high1 and high2 with the same value,
+		 * it is guaranteed that they read the correct low half for that value.
+		 *
+		 * This is the same sequence that is used by userspace to read clock.
+		 */
+		atomic_store_explicit(&time->high1, new_high, memory_order_relaxed);
+		atomic_store_explicit(&time->low, (uint32_t) val, memory_order_release);
+		atomic_store_explicit(&time->high2, new_high, memory_order_release);
+	}
+}
+
+static inline uint64_t atomic_time_read(atomic_time_stat_t *time)
+{
+	uint32_t high2 = atomic_load_explicit(&time->high2, memory_order_acquire);
+	uint32_t low = atomic_load_explicit(&time->low, memory_order_acquire);
+	uint32_t high1 = atomic_load_explicit(&time->high1, memory_order_relaxed);
+
+	if (high1 != high2)
+		low = 0;
+
+	/* If the values differ, high1 is always the newer value. */
+	return (uint64_t) high1 << 32 | (uint64_t) low;
+}
+
+#endif /* __64_BITS__ */
+
 #endif
 
Index: kernel/generic/include/cc.h
===================================================================
--- kernel/generic/include/cc.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/cc.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -38,10 +38,7 @@
 
 #ifndef __clang__
-#define ATTRIBUTE_OPTIMIZE(opt) \
-	__attribute__ ((optimize(opt)))
 #define ATTRIBUTE_USED \
 	__attribute__ ((used))
 #else
-#define ATTRIBUTE_OPTIMIZE(opt)
 #define ATTRIBUTE_USED
 #endif
Index: kernel/generic/include/config.h
===================================================================
--- kernel/generic/include/config.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/config.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -87,9 +87,4 @@
 	size_t kernel_size;
 
-	/** Base adddress of initial stack. */
-	uintptr_t stack_base;
-	/** Size of initial stack. */
-	size_t stack_size;
-
 	bool identity_configured;
 	/** Base address of the kernel identity mapped memory. */
Index: kernel/generic/include/console/chardev.h
===================================================================
--- kernel/generic/include/console/chardev.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/console/chardev.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -39,5 +39,5 @@
 #include <stdbool.h>
 #include <stddef.h>
-#include <synch/waitq.h>
+#include <synch/semaphore.h>
 #include <synch/spinlock.h>
 
@@ -64,5 +64,5 @@
 typedef struct indev {
 	const char *name;
-	waitq_t wq;
+	semaphore_t wq;
 
 	/** Protects everything below. */
Index: kernel/generic/include/console/console.h
===================================================================
--- kernel/generic/include/console/console.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/console/console.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -69,6 +69,4 @@
 SPINLOCK_EXTERN(kio_lock);
 
-extern char32_t getc(indev_t *indev);
-extern size_t gets(indev_t *indev, char *buf, size_t buflen);
 extern sys_errno_t sys_kio(int cmd, uspace_addr_t buf, size_t size);
 
Index: kernel/generic/include/context.h
===================================================================
--- kernel/generic/include/context.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/context.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,6 +36,8 @@
 #define KERN_CONTEXT_H_
 
+#include <panic.h>
 #include <trace.h>
 #include <arch/context.h>
+#include <arch/faddr.h>
 
 #define context_set_generic(ctx, _pc, stack, size) \
@@ -47,35 +49,4 @@
 extern int context_save_arch(context_t *ctx) __attribute__((returns_twice));
 extern void context_restore_arch(context_t *ctx) __attribute__((noreturn));
-
-/** Save register context.
- *
- * Save the current register context (including stack pointer) to a context
- * structure. A subsequent call to context_restore() will return to the same
- * address as the corresponding call to context_save().
- *
- * Note that context_save_arch() must reuse the stack frame of the function
- * which called context_save(). We guarantee this by:
- *
- *   a) implementing context_save_arch() in assembly so that it does not create
- *      its own stack frame, and by
- *   b) defining context_save() as a macro because the inline keyword is just a
- *      hint for the compiler, not a real constraint; the application of a macro
- *      will definitely not create a stack frame either.
- *
- * To imagine what could happen if there were some extra stack frames created
- * either by context_save() or context_save_arch(), we need to realize that the
- * sp saved in the contex_t structure points to the current stack frame as it
- * existed when context_save_arch() was executing. After the return from
- * context_save_arch() and context_save(), any extra stack frames created by
- * these functions will be destroyed and their contents sooner or later
- * overwritten by functions called next. Any attempt to restore to a context
- * saved like that would therefore lead to a disaster.
- *
- * @param ctx Context structure.
- *
- * @return context_save() returns 1, context_restore() returns 0.
- *
- */
-#define context_save(ctx)  context_save_arch(ctx)
 
 /** Restore register context.
@@ -91,7 +62,37 @@
  *
  */
-_NO_TRACE static inline void context_restore(context_t *ctx)
+_NO_TRACE __attribute__((noreturn))
+    static inline void context_restore(context_t *ctx)
 {
 	context_restore_arch(ctx);
+}
+
+/**
+ * Saves current context to the variable pointed to by `self`,
+ * and restores the context denoted by `other`.
+ *
+ * When the `self` context is later restored by another call to
+ * `context_swap()`, the control flow behaves as if the earlier call to
+ * `context_swap()` just returned.
+ */
+_NO_TRACE static inline void context_swap(context_t *self, context_t *other)
+{
+	if (context_save_arch(self))
+		context_restore_arch(other);
+}
+
+_NO_TRACE static inline void context_create(context_t *context,
+    void (*fn)(void), void *stack_base, size_t stack_size)
+{
+	*context = (context_t) { 0 };
+	context_set(context, FADDR(fn), stack_base, stack_size);
+}
+
+__attribute__((noreturn)) static inline void context_replace(void (*fn)(void),
+    void *stack_base, size_t stack_size)
+{
+	context_t ctx;
+	context_create(&ctx, fn, stack_base, stack_size);
+	context_restore(&ctx);
 }
 
Index: kernel/generic/include/cpu.h
===================================================================
--- kernel/generic/include/cpu.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/cpu.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -44,5 +44,39 @@
 #include <arch.h>
 
-#define CPU                  CURRENT->cpu
+#define CPU                  (CURRENT->cpu)
+#define CPU_LOCAL            (&CPU->local)
+
+/**
+ * Contents of CPU_LOCAL. These are variables that are only ever accessed by
+ * the CPU they belong to, so they don't need any synchronization,
+ * just locally disabled interrupts.
+ */
+typedef struct cpu_local {
+	/**
+	 * When system clock loses a tick, it is
+	 * recorded here so that clock() can react.
+	 */
+	size_t missed_clock_ticks;
+
+	uint64_t current_clock_tick;
+	uint64_t preempt_deadline;  /* < when should the currently running thread be preempted */
+	uint64_t relink_deadline;
+
+	/**
+	 * Stack used by scheduler when there is no running thread.
+	 * This field is unchanged after initialization.
+	 */
+	uint8_t *stack;
+
+	/**
+	 * Processor cycle accounting.
+	 */
+	bool idle;
+	uint64_t last_cycle;
+
+	context_t scheduler_context;
+
+	struct thread *prev_thread;
+} cpu_local_t;
 
 /** CPU structure.
@@ -51,14 +85,11 @@
  */
 typedef struct cpu {
-	IRQ_SPINLOCK_DECLARE(lock);
+	IRQ_SPINLOCK_DECLARE(tlb_lock);
 
 	tlb_shootdown_msg_t tlb_messages[TLB_MESSAGE_QUEUE_LEN];
 	size_t tlb_messages_count;
 
-	context_t saved_context;
-
 	atomic_size_t nrdy;
 	runq_t rq[RQ_COUNT];
-	volatile size_t needs_relink;
 
 	IRQ_SPINLOCK_DECLARE(timeoutlock);
@@ -66,19 +97,8 @@
 
 	/**
-	 * When system clock loses a tick, it is
-	 * recorded here so that clock() can react.
-	 * This variable is CPU-local and can be
-	 * only accessed when interrupts are
-	 * disabled.
-	 */
-	size_t missed_clock_ticks;
-
-	/**
 	 * Processor cycle accounting.
 	 */
-	bool idle;
-	uint64_t last_cycle;
-	uint64_t idle_cycles;
-	uint64_t busy_cycles;
+	atomic_time_stat_t idle_cycles;
+	atomic_time_stat_t busy_cycles;
 
 	/**
@@ -95,10 +115,11 @@
 	cpu_arch_t arch;
 
-	struct thread *fpu_owner;
+#ifdef CONFIG_FPU_LAZY
+	/* For synchronization between FPU trap and thread destructor. */
+	IRQ_SPINLOCK_DECLARE(fpu_lock);
+#endif
+	_Atomic(struct thread *) fpu_owner;
 
-	/**
-	 * Stack used by scheduler when there is no running thread.
-	 */
-	uint8_t *stack;
+	cpu_local_t local;
 } cpu_t;
 
Index: kernel/generic/include/cpu/cpu_mask.h
===================================================================
--- kernel/generic/include/cpu/cpu_mask.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/cpu/cpu_mask.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -37,5 +37,5 @@
 #include <cpu.h>
 #include <config.h>
-#include <lib/memfnc.h>
+#include <memw.h>
 
 /** Iterates over all cpu id's whose bit is included in the cpu mask.
Index: kernel/generic/include/debug.h
===================================================================
--- kernel/generic/include/debug.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/debug.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -37,7 +37,13 @@
 
 #include <log.h>
-#include <symtab_lookup.h>
+#include <printf/verify.h>
 
 #define CALLER  ((uintptr_t) __builtin_return_address(0))
+
+/* An empty printf function to ensure syntactic correctness of disabled debug prints. */
+static inline void dummy_printf(const char *fmt, ...) _HELENOS_PRINTF_ATTRIBUTE(1, 2);
+static inline void dummy_printf(const char *fmt, ...)
+{
+}
 
 #ifdef CONFIG_LOG
@@ -53,5 +59,5 @@
 	do { \
 		log(LF_OTHER, LVL_DEBUG, \
-		    "%s() from %s at %s:%u: " format,__func__, \
+		    "%s() from %s at %s:%u: " format, __func__, \
 		    symtab_fmt_name_lookup(CALLER), __FILE__, __LINE__, \
 		    ##__VA_ARGS__); \
@@ -60,5 +66,5 @@
 #else /* CONFIG_LOG */
 
-#define LOG(format, ...)
+#define LOG(format, ...) dummy_printf(format, ##__VA_ARGS__)
 
 #endif /* CONFIG_LOG */
Index: kernel/generic/include/debug/constants.h
===================================================================
--- kernel/generic/include/debug/constants.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ kernel/generic/include/debug/constants.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,826 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * 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.
+ */
+
+#ifndef DWARFS_CONSTANTS_H_
+#define DWARFS_CONSTANTS_H_
+
+enum {
+	DW_SECT_INFO = 1,        // .debug_info.dwo
+	DW_SECT_ABBREV = 3,      // .debug_abbrev.dwo
+	DW_SECT_LINE = 4,        // .debug_line.dwo
+	DW_SECT_LOCLISTS = 5,    // .debug_loclists.dwo
+	DW_SECT_STR_OFFSETS = 6, // .debug_str_offsets.dwo
+	DW_SECT_MACRO = 7,       // .debug_macro.dwo
+	DW_SECT_RNGLISTS = 8,    // .debug_rnglists.dwo
+
+	DW_SECT_MAX,
+};
+
+enum {
+	DW_ACCESS_public = 0x01,
+	DW_ACCESS_protected = 0x02,
+	DW_ACCESS_private = 0x03,
+
+	DW_ACCESS_MAX,
+};
+
+enum {
+	DW_AT_sibling = 0x01,
+	DW_AT_location = 0x02,
+	DW_AT_name = 0x03,
+	DW_AT_reserved_0x04 = 0x04,
+	DW_AT_fund_type = 0x05,
+	DW_AT_mod_fund_type = 0x06,
+	DW_AT_user_def_type = 0x07,
+	DW_AT_mod_u_d_type = 0x08,
+	DW_AT_ordering = 0x09,
+	DW_AT_subscr_data = 0x0a,
+	DW_AT_byte_size = 0x0b,
+	DW_AT_bit_offset = 0x0c,
+	DW_AT_bit_size = 0x0d,
+	DW_AT_reserved_0x0e = 0x0e,
+	DW_AT_unused8 = 0x0f,
+	DW_AT_stmt_list = 0x10,
+	DW_AT_low_pc = 0x11,
+	DW_AT_high_pc = 0x12,
+	DW_AT_language = 0x13,
+	DW_AT_member = 0x14,
+	DW_AT_discr = 0x15,
+	DW_AT_discr_value = 0x16,
+	DW_AT_visibility = 0x17,
+	DW_AT_import = 0x18,
+	DW_AT_string_length = 0x19,
+	DW_AT_common_reference = 0x1a,
+	DW_AT_comp_dir = 0x1b,
+	DW_AT_const_value = 0x1c,
+	DW_AT_containing_type = 0x1d,
+	DW_AT_default_value = 0x1e,
+	DW_AT_reserved_0x1f = 0x1f,
+	DW_AT_inline = 0x20,
+	DW_AT_is_optional = 0x21,
+	DW_AT_lower_bound = 0x22,
+	DW_AT_reserved_0x23 = 0x23,
+	DW_AT_reserved_0x24 = 0x24,
+	DW_AT_producer = 0x25,
+	DW_AT_reserved_0x26 = 0x26,
+	DW_AT_prototyped = 0x27,
+	DW_AT_reserved_0x28 = 0x28,
+	DW_AT_reserved_0x29 = 0x29,
+	DW_AT_return_addr = 0x2a,
+	DW_AT_reserved_0x2b = 0x2b,
+	DW_AT_start_scope = 0x2c,
+	DW_AT_reserved_0x2d = 0x2d,
+	DW_AT_bit_stride = 0x2e,
+	DW_AT_upper_bound = 0x2f,
+	DW_AT_virtual = 0x30,
+	DW_AT_abstract_origin = 0x31,
+	DW_AT_accessibility = 0x32,
+	DW_AT_address_class = 0x33,
+	DW_AT_artificial = 0x34,
+	DW_AT_base_types = 0x35,
+	DW_AT_calling_convention = 0x36,
+	DW_AT_count = 0x37,
+	DW_AT_data_member_location = 0x38,
+	DW_AT_decl_column = 0x39,
+	DW_AT_decl_file = 0x3a,
+	DW_AT_decl_line = 0x3b,
+	DW_AT_declaration = 0x3c,
+	DW_AT_discr_list = 0x3d,
+	DW_AT_encoding = 0x3e,
+	DW_AT_external = 0x3f,
+	DW_AT_frame_base = 0x40,
+	DW_AT_friend = 0x41,
+	DW_AT_identifier_case = 0x42,
+	DW_AT_macro_info = 0x43,
+	DW_AT_namelist_item = 0x44,
+	DW_AT_priority = 0x45,
+	DW_AT_segment = 0x46,
+	DW_AT_specification = 0x47,
+	DW_AT_static_link = 0x48,
+	DW_AT_type = 0x49,
+	DW_AT_use_location = 0x4a,
+	DW_AT_variable_parameter = 0x4b,
+	DW_AT_virtuality = 0x4c,
+	DW_AT_vtable_elem_location = 0x4d,
+	DW_AT_allocated = 0x4e,
+	DW_AT_associated = 0x4f,
+	DW_AT_data_location = 0x50,
+	DW_AT_byte_stride = 0x51,
+	DW_AT_entry_pc = 0x52,
+	DW_AT_use_UTF8 = 0x53,
+	DW_AT_extension = 0x54,
+	DW_AT_ranges = 0x55,
+	DW_AT_trampoline = 0x56,
+	DW_AT_call_column = 0x57,
+	DW_AT_call_file = 0x58,
+	DW_AT_call_line = 0x59,
+	DW_AT_description = 0x5a,
+	DW_AT_binary_scale = 0x5b,
+	DW_AT_decimal_scale = 0x5c,
+	DW_AT_small = 0x5d,
+	DW_AT_decimal_sign = 0x5e,
+	DW_AT_digit_count = 0x5f,
+	DW_AT_picture_string = 0x60,
+	DW_AT_mutable = 0x61,
+	DW_AT_threads_scaled = 0x62,
+	DW_AT_explicit = 0x63,
+	DW_AT_object_pointer = 0x64,
+	DW_AT_endianity = 0x65,
+	DW_AT_elemental = 0x66,
+	DW_AT_pure = 0x67,
+	DW_AT_recursive = 0x68,
+	DW_AT_signature = 0x69,
+	DW_AT_main_subprogram = 0x6a,
+	DW_AT_data_bit_offset = 0x6b,
+	DW_AT_const_expr = 0x6c,
+	DW_AT_enum_class = 0x6d,
+	DW_AT_linkage_name = 0x6e,
+	DW_AT_string_length_bit_size = 0x6f,
+	DW_AT_string_length_byte_size = 0x70,
+	DW_AT_rank = 0x71,
+	DW_AT_str_offsets_base = 0x72,
+	DW_AT_addr_base = 0x73,
+	DW_AT_rnglists_base = 0x74,
+	DW_AT_dwo_id = 0x75,
+	DW_AT_dwo_name = 0x76,
+	DW_AT_reference = 0x77,
+	DW_AT_rvalue_reference = 0x78,
+	DW_AT_macros = 0x79,
+	DW_AT_call_all_calls = 0x7a,
+	DW_AT_call_all_source_calls = 0x7b,
+	DW_AT_call_all_tail_calls = 0x7c,
+	DW_AT_call_return_pc = 0x7d,
+	DW_AT_call_value = 0x7e,
+	DW_AT_call_origin = 0x7f,
+	DW_AT_call_parameter = 0x80,
+	DW_AT_call_pc = 0x81,
+	DW_AT_call_tail_call = 0x82,
+	DW_AT_call_target = 0x83,
+	DW_AT_call_target_clobbered = 0x84,
+	DW_AT_call_data_location = 0x85,
+	DW_AT_call_data_value = 0x86,
+	DW_AT_noreturn = 0x87,
+	DW_AT_alignment = 0x88,
+	DW_AT_export_symbols = 0x89,
+	DW_AT_deleted = 0x8a,
+	DW_AT_defaulted = 0x8b,
+	DW_AT_loclists_base = 0x8c,
+
+	DW_AT_MAX,
+
+	DW_AT_lo_user = 0x2000,
+	DW_AT_hi_user = 0x3fff,
+};
+
+enum {
+	DW_ATE_address = 0x01,
+	DW_ATE_boolean = 0x02,
+	DW_ATE_complex_float = 0x03,
+	DW_ATE_float = 0x04,
+	DW_ATE_signed = 0x05,
+	DW_ATE_signed_char = 0x06,
+	DW_ATE_unsigned = 0x07,
+	DW_ATE_unsigned_char = 0x08,
+	DW_ATE_imaginary_float = 0x09,
+	DW_ATE_packed_decimal = 0x0a,
+	DW_ATE_numeric_string = 0x0b,
+	DW_ATE_edited = 0x0c,
+	DW_ATE_signed_fixed = 0x0d,
+	DW_ATE_unsigned_fixed = 0x0e,
+	DW_ATE_decimal_float = 0x0f,
+	DW_ATE_UTF = 0x10,
+	DW_ATE_UCS = 0x11,
+	DW_ATE_ASCII = 0x12,
+
+	DW_ATE_MAX,
+
+	DW_ATE_lo_user = 0x80,
+	DW_ATE_hi_user = 0xff,
+};
+
+enum {
+	DW_CC_normal = 0x01,
+	DW_CC_program = 0x02,
+	DW_CC_nocall = 0x03,
+	DW_CC_pass_by_reference = 0x04,
+	DW_CC_pass_by_value = 0x05,
+
+	DW_CC_MAX,
+
+	DW_CC_lo_user = 0x40,
+	DW_CC_hi_user = 0xff,
+};
+
+enum {
+	DW_UT_compile = 1,
+	DW_UT_type = 2,
+	DW_UT_partial = 3,
+	DW_UT_skeleton = 4,
+	DW_UT_split_compile = 5,
+	DW_UT_split_type = 6,
+
+	DW_UT_MAX,
+
+	DW_UT_lo_user = 0x80,
+	DW_UT_hi_user = 0xff,
+};
+
+enum {
+	DW_TAG_array_type = 0x01,
+	DW_TAG_class_type = 0x02,
+	DW_TAG_entry_point = 0x03,
+	DW_TAG_enumeration_type = 0x04,
+	DW_TAG_formal_parameter = 0x05,
+	DW_TAG_global_subroutine = 0x06,
+	DW_TAG_global_variable = 0x07,
+	DW_TAG_imported_declaration = 0x08,
+	DW_TAG_reserved_0x09 = 0x09,
+	DW_TAG_label = 0x0a,
+	DW_TAG_lexical_block = 0x0b,
+	DW_TAG_local_variable = 0x0c,
+	DW_TAG_member = 0x0d,
+	DW_TAG_reserved_0x0e = 0x0e,
+	DW_TAG_pointer_type = 0x0f,
+	DW_TAG_reference_type = 0x10,
+	DW_TAG_compile_unit = 0x11,
+	DW_TAG_string_type = 0x12,
+	DW_TAG_structure_type = 0x13,
+	DW_TAG_subroutine = 0x14,
+	DW_TAG_subroutine_type = 0x15,
+	DW_TAG_typedef = 0x16,
+	DW_TAG_union_type = 0x17,
+	DW_TAG_unspecified_parameters = 0x18,
+	DW_TAG_variant = 0x19,
+	DW_TAG_common_block = 0x1a,
+	DW_TAG_common_inclusion = 0x1b,
+	DW_TAG_inheritance = 0x1c,
+	DW_TAG_inlined_subroutine = 0x1d,
+	DW_TAG_module = 0x1e,
+	DW_TAG_ptr_to_member_type = 0x1f,
+	DW_TAG_set_type = 0x20,
+	DW_TAG_subrange_type = 0x21,
+	DW_TAG_with_stmt = 0x22,
+	DW_TAG_access_declaration = 0x23,
+	DW_TAG_base_type = 0x24,
+	DW_TAG_catch_block = 0x25,
+	DW_TAG_const_type = 0x26,
+	DW_TAG_constant = 0x27,
+	DW_TAG_enumerator = 0x28,
+	DW_TAG_file_type = 0x29,
+	DW_TAG_friend = 0x2a,
+	DW_TAG_namelist = 0x2b,
+	DW_TAG_namelist_item = 0x2c,
+	DW_TAG_packed_type = 0x2d,
+	DW_TAG_subprogram = 0x2e,
+	DW_TAG_template_type_parameter = 0x2f,
+	DW_TAG_template_value_parameter = 0x30,
+	DW_TAG_thrown_type = 0x31,
+	DW_TAG_try_block = 0x32,
+	DW_TAG_variant_part = 0x33,
+	DW_TAG_variable = 0x34,
+	DW_TAG_volatile_type = 0x35,
+	DW_TAG_dwarf_procedure = 0x36,
+	DW_TAG_restrict_type = 0x37,
+	DW_TAG_interface_type = 0x38,
+	DW_TAG_namespace = 0x39,
+	DW_TAG_imported_module = 0x3a,
+	DW_TAG_unspecified_type = 0x3b,
+	DW_TAG_partial_unit = 0x3c,
+	DW_TAG_imported_unit = 0x3d,
+	DW_TAG_mutable_type = 0x3e,
+	DW_TAG_condition = 0x3f,
+	DW_TAG_shared_type = 0x40,
+	DW_TAG_type_unit = 0x41,
+	DW_TAG_rvalue_reference_type = 0x42,
+	DW_TAG_template_alias = 0x43,
+	DW_TAG_coarray_type = 0x44,
+	DW_TAG_generic_subrange = 0x45,
+	DW_TAG_dynamic_type = 0x46,
+	DW_TAG_atomic_type = 0x47,
+	DW_TAG_call_site = 0x48,
+	DW_TAG_call_site_parameter = 0x49,
+	DW_TAG_skeleton_unit = 0x4a,
+	DW_TAG_immutable_type = 0x4b,
+
+	DW_TAG_MAX,
+
+	DW_TAG_lo_user = 0x4080,
+	DW_TAG_hi_user = 0xffff,
+};
+
+enum {
+	DW_FORM_addr = 0x01,
+	DW_FORM_reserved_0x02 = 0x02,
+	DW_FORM_block2 = 0x03,
+	DW_FORM_block4 = 0x04,
+	DW_FORM_data2 = 0x05,
+	DW_FORM_data4 = 0x06,
+	DW_FORM_data8 = 0x07,
+	DW_FORM_string = 0x08,
+	DW_FORM_block = 0x09,
+	DW_FORM_block1 = 0x0a,
+	DW_FORM_data1 = 0x0b,
+	DW_FORM_flag = 0x0c,
+	DW_FORM_sdata = 0x0d,
+	DW_FORM_strp = 0x0e,
+	DW_FORM_udata = 0x0f,
+	DW_FORM_ref_addr = 0x10,
+	DW_FORM_ref1 = 0x11,
+	DW_FORM_ref2 = 0x12,
+	DW_FORM_ref4 = 0x13,
+	DW_FORM_ref8 = 0x14,
+	DW_FORM_ref_udata = 0x15,
+	DW_FORM_indirect = 0x16,
+	DW_FORM_sec_offset = 0x17,
+	DW_FORM_exprloc = 0x18,
+	DW_FORM_flag_present = 0x19,
+	DW_FORM_strx = 0x1a,
+	DW_FORM_addrx = 0x1b,
+	DW_FORM_ref_sup4 = 0x1c,
+	DW_FORM_strp_sup = 0x1d,
+	DW_FORM_data16 = 0x1e,
+	DW_FORM_line_strp = 0x1f,
+	DW_FORM_ref_sig8 = 0x20,
+	DW_FORM_implicit_const = 0x21,
+	DW_FORM_loclistx = 0x22,
+	DW_FORM_rnglistx = 0x23,
+	DW_FORM_ref_sup8 = 0x24,
+	DW_FORM_strx1 = 0x25,
+	DW_FORM_strx2 = 0x26,
+	DW_FORM_strx3 = 0x27,
+	DW_FORM_strx4 = 0x28,
+	DW_FORM_addrx1 = 0x29,
+	DW_FORM_addrx2 = 0x2a,
+	DW_FORM_addrx3 = 0x2b,
+	DW_FORM_addrx4 = 0x2c,
+
+	DW_FORM_MAX,
+};
+
+enum {
+	DW_OP_reg = 0x01,
+	DW_OP_basereg = 0x02,
+	DW_OP_addr = 0x03,
+	DW_OP_const = 0x04,
+	DW_OP_deref2 = 0x05,
+	DW_OP_deref = 0x06,
+	DW_OP_add = 0x07,
+	DW_OP_const1u = 0x08,
+	DW_OP_const1s = 0x09,
+	DW_OP_const2u = 0x0a,
+	DW_OP_const2s = 0x0b,
+	DW_OP_const4u = 0x0c,
+	DW_OP_const4s = 0x0d,
+	DW_OP_const8u = 0x0e,
+	DW_OP_const8s = 0x0f,
+	DW_OP_constu = 0x10,
+	DW_OP_consts = 0x11,
+	DW_OP_dup = 0x12,
+	DW_OP_drop = 0x13,
+	DW_OP_over = 0x14,
+	DW_OP_pick = 0x15,
+	DW_OP_swap = 0x16,
+	DW_OP_rot = 0x17,
+	DW_OP_xderef = 0x18,
+	DW_OP_abs = 0x19,
+	DW_OP_and = 0x1a,
+	DW_OP_div = 0x1b,
+	DW_OP_minus = 0x1c,
+	DW_OP_mod = 0x1d,
+	DW_OP_mul = 0x1e,
+	DW_OP_neg = 0x1f,
+	DW_OP_not = 0x20,
+	DW_OP_or = 0x21,
+	DW_OP_plus = 0x22,
+	DW_OP_plus_uconst = 0x23,
+	DW_OP_shl = 0x24,
+	DW_OP_shr = 0x25,
+	DW_OP_shra = 0x26,
+	DW_OP_xor = 0x27,
+	DW_OP_bra = 0x28,
+	DW_OP_eq = 0x29,
+	DW_OP_ge = 0x2a,
+	DW_OP_gt = 0x2b,
+	DW_OP_le = 0x2c,
+	DW_OP_lt = 0x2d,
+	DW_OP_ne = 0x2e,
+	DW_OP_skip = 0x2f,
+	DW_OP_lit0 = 0x30,
+	DW_OP_lit1 = 0x31,
+	DW_OP_lit2 = 0x32,
+	DW_OP_lit3 = 0x33,
+	DW_OP_lit4 = 0x34,
+	DW_OP_lit5 = 0x35,
+	DW_OP_lit6 = 0x36,
+	DW_OP_lit7 = 0x37,
+	DW_OP_lit8 = 0x38,
+	DW_OP_lit9 = 0x39,
+	DW_OP_lit10 = 0x3a,
+	DW_OP_lit11 = 0x3b,
+	DW_OP_lit12 = 0x3c,
+	DW_OP_lit13 = 0x3d,
+	DW_OP_lit14 = 0x3e,
+	DW_OP_lit15 = 0x3f,
+	DW_OP_lit16 = 0x40,
+	DW_OP_lit17 = 0x41,
+	DW_OP_lit18 = 0x42,
+	DW_OP_lit19 = 0x43,
+	DW_OP_lit20 = 0x44,
+	DW_OP_lit21 = 0x45,
+	DW_OP_lit22 = 0x46,
+	DW_OP_lit23 = 0x47,
+	DW_OP_lit24 = 0x48,
+	DW_OP_lit25 = 0x49,
+	DW_OP_lit26 = 0x4a,
+	DW_OP_lit27 = 0x4b,
+	DW_OP_lit28 = 0x4c,
+	DW_OP_lit29 = 0x4d,
+	DW_OP_lit30 = 0x4e,
+	DW_OP_lit31 = 0x4f,
+	DW_OP_reg0 = 0x50,
+	DW_OP_reg1 = 0x51,
+	DW_OP_reg2 = 0x52,
+	DW_OP_reg3 = 0x53,
+	DW_OP_reg4 = 0x54,
+	DW_OP_reg5 = 0x55,
+	DW_OP_reg6 = 0x56,
+	DW_OP_reg7 = 0x57,
+	DW_OP_reg8 = 0x58,
+	DW_OP_reg9 = 0x59,
+	DW_OP_reg10 = 0x5a,
+	DW_OP_reg11 = 0x5b,
+	DW_OP_reg12 = 0x5c,
+	DW_OP_reg13 = 0x5d,
+	DW_OP_reg14 = 0x5e,
+	DW_OP_reg15 = 0x5f,
+	DW_OP_reg16 = 0x60,
+	DW_OP_reg17 = 0x61,
+	DW_OP_reg18 = 0x62,
+	DW_OP_reg19 = 0x63,
+	DW_OP_reg20 = 0x64,
+	DW_OP_reg21 = 0x65,
+	DW_OP_reg22 = 0x66,
+	DW_OP_reg23 = 0x67,
+	DW_OP_reg24 = 0x68,
+	DW_OP_reg25 = 0x69,
+	DW_OP_reg26 = 0x6a,
+	DW_OP_reg27 = 0x6b,
+	DW_OP_reg28 = 0x6c,
+	DW_OP_reg29 = 0x6d,
+	DW_OP_reg30 = 0x6e,
+	DW_OP_reg31 = 0x6f,
+	DW_OP_breg0 = 0x70,
+	DW_OP_breg1 = 0x71,
+	DW_OP_breg2 = 0x72,
+	DW_OP_breg3 = 0x73,
+	DW_OP_breg4 = 0x74,
+	DW_OP_breg5 = 0x75,
+	DW_OP_breg6 = 0x76,
+	DW_OP_breg7 = 0x77,
+	DW_OP_breg8 = 0x78,
+	DW_OP_breg9 = 0x79,
+	DW_OP_breg10 = 0x7a,
+	DW_OP_breg11 = 0x7b,
+	DW_OP_breg12 = 0x7c,
+	DW_OP_breg13 = 0x7d,
+	DW_OP_breg14 = 0x7e,
+	DW_OP_breg15 = 0x7f,
+	DW_OP_breg16 = 0x80,
+	DW_OP_breg17 = 0x81,
+	DW_OP_breg18 = 0x82,
+	DW_OP_breg19 = 0x83,
+	DW_OP_breg20 = 0x84,
+	DW_OP_breg21 = 0x85,
+	DW_OP_breg22 = 0x86,
+	DW_OP_breg23 = 0x87,
+	DW_OP_breg24 = 0x88,
+	DW_OP_breg25 = 0x89,
+	DW_OP_breg26 = 0x8a,
+	DW_OP_breg27 = 0x8b,
+	DW_OP_breg28 = 0x8c,
+	DW_OP_breg29 = 0x8d,
+	DW_OP_breg30 = 0x8e,
+	DW_OP_breg31 = 0x8f,
+	DW_OP_regx = 0x90,
+	DW_OP_fbreg = 0x91,
+	DW_OP_bregx = 0x92,
+	DW_OP_piece = 0x93,
+	DW_OP_deref_size = 0x94,
+	DW_OP_xderef_size = 0x95,
+	DW_OP_nop = 0x96,
+	DW_OP_push_object_address = 0x97,
+	DW_OP_call2 = 0x98,
+	DW_OP_call4 = 0x99,
+	DW_OP_call_ref = 0x9a,
+	DW_OP_form_tls_address = 0x9b,
+	DW_OP_call_frame_cfa = 0x9c,
+	DW_OP_bit_piece = 0x9d,
+	DW_OP_implicit_value = 0x9e,
+	DW_OP_stack_value = 0x9f,
+	DW_OP_implicit_pointer = 0xa0,
+	DW_OP_addrx = 0xa1,
+	DW_OP_constx = 0xa2,
+	DW_OP_entry_value = 0xa3,
+	DW_OP_const_type = 0xa4,
+	DW_OP_regval_type = 0xa5,
+	DW_OP_deref_type = 0xa6,
+	DW_OP_xderef_type = 0xa7,
+	DW_OP_convert = 0xa8,
+	DW_OP_reinterpret = 0xa9,
+
+	DW_OP_MAX,
+
+	DW_OP_lo_user = 0xe0,
+	DW_OP_hi_user = 0xff,
+};
+
+enum {
+	DW_LLE_end_of_list = 0x00,
+	DW_LLE_base_addressx = 0x01,
+	DW_LLE_startx_endx = 0x02,
+	DW_LLE_startx_length = 0x03,
+	DW_LLE_offset_pair = 0x04,
+	DW_LLE_default_location = 0x05,
+	DW_LLE_base_address = 0x06,
+	DW_LLE_start_end = 0x07,
+	DW_LLE_start_length = 0x08,
+
+	DW_LLE_MAX,
+};
+
+enum {
+	DW_DS_unsigned = 0x01,
+	DW_DS_leading_overpunch = 0x02,
+	DW_DS_trailing_overpunch = 0x03,
+	DW_DS_leading_separate = 0x04,
+	DW_DS_trailing_separate = 0x05,
+
+	DW_DS_MAX,
+};
+
+enum {
+	DW_END_default = 0x00,
+	DW_END_big = 0x01,
+	DW_END_little = 0x02,
+
+	DW_END_MAX,
+
+	DW_END_lo_user = 0x40,
+	DW_END_hi_user = 0xff,
+};
+
+enum {
+	DW_VIS_local = 0x01,
+	DW_VIS_exported = 0x02,
+	DW_VIS_qualified = 0x03,
+
+	DW_VIS_MAX,
+};
+
+enum {
+	DW_VIRTUALITY_none = 0x00,
+	DW_VIRTUALITY_virtual = 0x01,
+	DW_VIRTUALITY_pure_virtual = 0x02,
+
+	DW_VIRTUALITY_MAX,
+};
+
+enum {
+	DW_LANG_C89 = 0x01,
+	DW_LANG_C = 0x02,
+	DW_LANG_Ada83 = 0x03,
+	DW_LANG_C_plus_plus = 0x04,
+	DW_LANG_Cobol74 = 0x05,
+	DW_LANG_Cobol85 = 0x06,
+	DW_LANG_Fortran77 = 0x07,
+	DW_LANG_Fortran90 = 0x08,
+	DW_LANG_Pascal83 = 0x09,
+	DW_LANG_Modula2 = 0x0a,
+	DW_LANG_Java = 0x0b,
+	DW_LANG_C99 = 0x0c,
+	DW_LANG_Ada95 = 0x0d,
+	DW_LANG_Fortran95 = 0x0e,
+	DW_LANG_PLI = 0x0f,
+	DW_LANG_ObjC = 0x10,
+	DW_LANG_ObjC_plus_plus = 0x11,
+	DW_LANG_UPC = 0x12,
+	DW_LANG_D = 0x13,
+	DW_LANG_Python = 0x14,
+	DW_LANG_OpenCL = 0x15,
+	DW_LANG_Go = 0x16,
+	DW_LANG_Modula3 = 0x17,
+	DW_LANG_Haskell = 0x18,
+	DW_LANG_C_plus_plus_03 = 0x19,
+	DW_LANG_C_plus_plus_11 = 0x1a,
+	DW_LANG_OCaml = 0x1b,
+	DW_LANG_Rust = 0x1c,
+	DW_LANG_C11 = 0x1d,
+	DW_LANG_Swift = 0x1e,
+	DW_LANG_Julia = 0x1f,
+	DW_LANG_Dylan = 0x20,
+	DW_LANG_C_plus_plus_14 = 0x21,
+	DW_LANG_Fortran03 = 0x22,
+	DW_LANG_Fortran08 = 0x23,
+	DW_LANG_RenderScript = 0x24,
+	DW_LANG_BLISS = 0x25,
+
+	DW_LANG_MAX,
+
+	DW_LANG_lo_user = 0x8000,
+	DW_LANG_hi_user = 0xffff,
+};
+
+enum {
+	DW_ADDR_none = 0,
+};
+
+enum {
+	DW_ID_case_sensitive = 0x00,
+	DW_ID_up_case = 0x01,
+	DW_ID_down_case = 0x02,
+	DW_ID_case_insensitive = 0x03,
+
+	DW_ID_MAX,
+};
+
+enum {
+	DW_LNS_copy = 0x01,
+	DW_LNS_advance_pc = 0x02,
+	DW_LNS_advance_line = 0x03,
+	DW_LNS_set_file = 0x04,
+	DW_LNS_set_column = 0x05,
+	DW_LNS_negate_stmt = 0x06,
+	DW_LNS_set_basic_block = 0x07,
+	DW_LNS_const_add_pc = 0x08,
+	DW_LNS_fixed_advance_pc = 0x09,
+	DW_LNS_set_prologue_end = 0x0a,
+	DW_LNS_set_epilogue_begin = 0x0b,
+	DW_LNS_set_isa = 0x0c,
+
+	DW_LNS_MAX,
+};
+
+enum {
+	DW_LNE_end_sequence = 0x01,
+	DW_LNE_set_address = 0x02,
+	DW_LNE_define_file = 0x03,
+	DW_LNE_set_discriminator = 0x04,
+
+	DW_LNE_MAX,
+
+	DW_LNE_lo_user = 0x80,
+	DW_LNE_hi_user = 0xff,
+};
+
+enum {
+	DW_LNCT_path = 0x1,
+	DW_LNCT_directory_index = 0x2,
+	DW_LNCT_timestamp = 0x3,
+	DW_LNCT_size = 0x4,
+	DW_LNCT_MD5 = 0x5,
+
+	DW_LNCT_MAX,
+
+	DW_LNCT_lo_user = 0x2000,
+	DW_LNCT_hi_user = 0x3fff,
+};
+
+enum {
+	DW_INL_not_inlined = 0x00,
+	DW_INL_inlined = 0x01,
+	DW_INL_declared_not_inlined = 0x02,
+	DW_INL_declared_inlined = 0x03,
+
+	DW_INL_MAX,
+};
+
+enum {
+	DW_ORD_row_major = 0x00,
+	DW_ORD_col_major = 0x01,
+
+	DW_ORD_MAX,
+};
+
+enum {
+	DW_DSC_label = 0x00,
+	DW_DSC_range = 0x01,
+
+	DW_DSC_MAX,
+};
+
+enum {
+	DW_IDX_compile_unit = 1,
+	DW_IDX_type_unit = 2,
+	DW_IDX_die_offset = 3,
+	DW_IDX_parent = 4,
+	DW_IDX_type_hash = 5,
+
+	DW_IDX_MAX,
+
+	DW_IDX_lo_user = 0x2000,
+	DW_IDX_hi_user = 0x3fff,
+};
+
+enum {
+	DW_DEFAULTED_no = 0x00,
+	DW_DEFAULTED_in_class = 0x01,
+	DW_DEFAULTED_out_of_class = 0x02,
+
+	DW_DEFAULTED_MAX,
+};
+
+enum {
+	DW_MACRO_define = 0x01,
+	DW_MACRO_undef = 0x02,
+	DW_MACRO_start_file = 0x03,
+	DW_MACRO_end_file = 0x04,
+	DW_MACRO_define_strp = 0x05,
+	DW_MACRO_undef_strp = 0x06,
+	DW_MACRO_import = 0x07,
+	DW_MACRO_define_sup = 0x08,
+	DW_MACRO_undef_sup = 0x09,
+	DW_MACRO_import_sup = 0x0a,
+	DW_MACRO_define_strx = 0x0b,
+	DW_MACRO_undef_strx = 0x0c,
+
+	DW_MACRO_MAX,
+
+	DW_MACRO_lo_user = 0xe0,
+	DW_MACRO_hi_user = 0xff,
+};
+
+enum {
+	DW_CFA_nop = 0,
+	DW_CFA_set_loc = 0x01,
+	DW_CFA_advance_loc1 = 0x02,
+	DW_CFA_advance_loc2 = 0x03,
+	DW_CFA_advance_loc4 = 0x04,
+	DW_CFA_offset_extended = 0x05,
+	DW_CFA_restore_extended = 0x06,
+	DW_CFA_undefined = 0x07,
+	DW_CFA_same_value = 0x08,
+	DW_CFA_register = 0x09,
+	DW_CFA_remember_state = 0x0a,
+	DW_CFA_restore_state = 0x0b,
+	DW_CFA_def_cfa = 0x0c,
+	DW_CFA_def_cfa_register = 0x0d,
+	DW_CFA_def_cfa_offset = 0x0e,
+	DW_CFA_def_cfa_expression = 0x0f,
+	DW_CFA_expression = 0x10,
+	DW_CFA_offset_extended_sf = 0x11,
+	DW_CFA_def_cfa_sf = 0x12,
+	DW_CFA_def_cfa_offset_sf = 0x13,
+	DW_CFA_val_offset = 0x14,
+	DW_CFA_val_offset_sf = 0x15,
+	DW_CFA_val_expression = 0x16,
+
+	DW_CFA_MAX,
+
+	DW_CFA_lo_user = 0x1c,
+	DW_CFA_hi_user = 0x3f,
+
+	/* These hold extra data in the 6 low-order bits. */
+	DW_CFA_advance_loc = 0x40,
+	DW_CFA_offset = 0x80,
+	DW_CFA_restore = 0xc0,
+};
+
+enum {
+	DW_RLE_end_of_list = 0x00,
+	DW_RLE_base_addressx = 0x01,
+	DW_RLE_startx_endx = 0x02,
+	DW_RLE_startx_length = 0x03,
+	DW_RLE_offset_pair = 0x04,
+	DW_RLE_base_address = 0x05,
+	DW_RLE_start_end = 0x06,
+	DW_RLE_start_length = 0x07,
+
+	DW_RLE_MAX,
+};
+
+#endif /* DWARFS_CONSTANTS_H_ */
Index: kernel/generic/include/debug/line.h
===================================================================
--- kernel/generic/include/debug/line.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ kernel/generic/include/debug/line.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * 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.
+ */
+
+#ifndef DWARFS_LINE_H_
+#define DWARFS_LINE_H_
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <debug/sections.h>
+
+struct debug_line_program_header {
+	uint64_t unit_length;
+	uint64_t header_length;
+	const uint8_t *unit_end;
+	const uint8_t *header_end;
+	const uint8_t *standard_opcode_lengths;
+	size_t standard_opcode_lengths_size;
+	unsigned width;
+	uint16_t version;
+	uint8_t minimum_instruction_length;
+	bool default_is_stmt;
+	int8_t line_base;
+	uint8_t line_range;
+	uint8_t opcode_base;
+
+	union {
+		struct {
+			const uint8_t *include_directories;
+			const uint8_t *include_directories_end;
+			const uint8_t *file_names;
+		} v3;
+		struct {
+			uint64_t directories_count;
+			uint64_t file_names_count;
+			const uint8_t *directory_entry_format;
+			const uint8_t *directory_entry_format_end;
+			const uint8_t *directories;
+			const uint8_t *directories_end;
+			const uint8_t *file_name_entry_format;
+			const uint8_t *file_name_entry_format_end;
+			const uint8_t *file_names;
+			const uint8_t *file_names_end;
+			uint8_t address_size;
+			uint8_t segment_selector_size;
+			uint8_t directory_entry_format_count;
+			uint8_t file_name_entry_format_count;
+			uint8_t maximum_operations_per_instruction;
+		} v5;
+	};
+};
+
+struct debug_line_program {
+	const struct debug_line_program_header *hdr;
+	const uint8_t *program;
+	const uint8_t *program_end;
+
+	uintptr_t address;
+	int op_advance;
+	int file;
+	int line;
+	int column;
+
+	bool end_sequence;
+	bool truncated;
+};
+
+static inline struct debug_line_program debug_line_program_create(const uint8_t *program,
+    const uint8_t *const program_end,
+    const struct debug_line_program_header *hdr)
+{
+	return (struct debug_line_program) {
+		.hdr = hdr,
+		.program = program,
+		.program_end = program_end,
+		.end_sequence = true,
+		.truncated = false,
+	};
+}
+
+extern bool debug_line_get_address_info(debug_sections_t *scs, uintptr_t addr, int op_index, const char **file, const char **dir, int *line, int *col);
+
+#endif /* DWARFS_LINE_H_ */
Index: kernel/generic/include/debug/names.h
===================================================================
--- kernel/generic/include/debug/names.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ kernel/generic/include/debug/names.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * 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.
+ */
+
+#ifndef DEBUG_DWARF_NAMES_H_
+#define DEBUG_DWARF_NAMES_H_
+
+#include <debug/types.h>
+
+extern const char *dw_access_name(dw_access_t);
+extern const char *dw_at_name(dw_at_t);
+extern const char *dw_ate_name(dw_ate_t);
+extern const char *dw_cc_name(dw_cc_t);
+extern const char *dw_cfa_name(dw_cfa_t);
+extern const char *dw_defaulted_name(dw_defaulted_t);
+extern const char *dw_ds_name(dw_ds_t);
+extern const char *dw_dsc_name(dw_dsc_t);
+extern const char *dw_end_name(dw_end_t);
+extern const char *dw_form_name(dw_form_t);
+extern const char *dw_id_name(dw_id_t);
+extern const char *dw_idx_name(dw_idx_t);
+extern const char *dw_inl_name(dw_inl_t);
+extern const char *dw_lang_name(dw_lang_t);
+extern const char *dw_lle_name(dw_lle_t);
+extern const char *dw_lnct_name(dw_lnct_t);
+extern const char *dw_lne_name(dw_lne_t);
+extern const char *dw_lns_name(dw_lns_t);
+extern const char *dw_macro_name(dw_macro_t);
+extern const char *dw_op_name(dw_op_t);
+extern const char *dw_ord_name(dw_ord_t);
+extern const char *dw_rle_name(dw_rle_t);
+extern const char *dw_tag_name(dw_tag_t);
+extern const char *dw_ut_name(dw_ut_t);
+extern const char *dw_virtuality_name(dw_virtuality_t);
+extern const char *dw_vis_name(dw_vis_t);
+
+#endif /* DEBUG_DWARF_NAMES_H_ */
Index: kernel/generic/include/debug/register.h
===================================================================
--- kernel/generic/include/debug/register.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ kernel/generic/include/debug/register.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * 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.
+ */
+
+#ifndef DEBUG_REGISTER_H_
+#define DEBUG_REGISTER_H_
+
+/** Accepts data of an ELF file containing debug sections. */
+void register_debug_data(const void *data, size_t data_size);
+
+#endif /* DEBUG_REGISTER_H_ */
Index: kernel/generic/include/debug/sections.h
===================================================================
--- kernel/generic/include/debug/sections.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ kernel/generic/include/debug/sections.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * 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.
+ */
+
+#ifndef DEBUG_SECTIONS_H_
+#define DEBUG_SECTIONS_H_
+
+#include <abi/elf.h>
+#include <stddef.h>
+
+typedef struct {
+	const void *debug_aranges;
+	size_t debug_aranges_size;
+
+	const void *debug_info;
+	size_t debug_info_size;
+
+	const void *debug_abbrev;
+	size_t debug_abbrev_size;
+
+	const void *debug_line;
+	size_t debug_line_size;
+
+	const char *debug_str;
+	size_t debug_str_size;
+
+	const char *debug_line_str;
+	size_t debug_line_str_size;
+
+	const void *debug_rnglists;
+	size_t debug_rnglists_size;
+
+	const void *eh_frame_hdr;
+	size_t eh_frame_hdr_size;
+
+	const void *eh_frame;
+	size_t eh_frame_size;
+
+	const elf_symbol_t *symtab;
+	size_t symtab_size;
+
+	const char *strtab;
+	size_t strtab_size;
+} debug_sections_t;
+
+extern debug_sections_t kernel_sections;
+
+debug_sections_t get_debug_sections(const void *elf, size_t elf_size);
+
+#endif /* DEBUG_SECTIONS_H_ */
Index: kernel/generic/include/debug/types.h
===================================================================
--- kernel/generic/include/debug/types.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ kernel/generic/include/debug/types.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * 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.
+ */
+
+#ifndef DEBUG_DWARF_TYPES_H_
+#define DEBUG_DWARF_TYPES_H_
+
+typedef unsigned dw_access_t;
+typedef unsigned dw_at_t;
+typedef unsigned dw_ate_t;
+typedef unsigned dw_cc_t;
+typedef unsigned dw_cfa_t;
+typedef unsigned dw_defaulted_t;
+typedef unsigned dw_ds_t;
+typedef unsigned dw_dsc_t;
+typedef unsigned dw_end_t;
+typedef unsigned dw_form_t;
+typedef unsigned dw_id_t;
+typedef unsigned dw_idx_t;
+typedef unsigned dw_idx_t;
+typedef unsigned dw_inl_t;
+typedef unsigned dw_lang_t;
+typedef unsigned dw_lle_t;
+typedef unsigned dw_lnct_t;
+typedef unsigned dw_lne_t;
+typedef unsigned dw_lns_t;
+typedef unsigned dw_macro_t;
+typedef unsigned dw_op_t;
+typedef unsigned dw_ord_t;
+typedef unsigned dw_rle_t;
+typedef unsigned dw_sect_t;
+typedef unsigned dw_tag_t;
+typedef unsigned dw_ut_t;
+typedef unsigned dw_virtuality_t;
+typedef unsigned dw_vis_t;
+
+#endif /* DEBUG_DWARF_TYPES_H_ */
Index: rnel/generic/include/gsort.h
===================================================================
--- kernel/generic/include/gsort.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,48 +1,0 @@
-/*
- * Copyright (c) 2005 Sergey Bondari
- * 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 kernel_generic
- * @{
- */
-/** @file
- */
-
-#ifndef KERN_GSORT_H_
-#define KERN_GSORT_H_
-
-#include <stdbool.h>
-#include <stddef.h>
-
-typedef int (*sort_cmp_t)(void *, void *, void *);
-
-extern bool gsort(void *, size_t, size_t, sort_cmp_t, void *);
-
-#endif
-
-/** @}
- */
Index: rnel/generic/include/lib/memfnc.h
===================================================================
--- kernel/generic/include/lib/memfnc.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,62 +1,0 @@
-/*
- * Copyright (c) 2011 Martin Decky
- * 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 kernel_generic
- * @{
- */
-/** @file
- */
-
-#ifndef KERN_LIB_MEMFNC_H_
-#define KERN_LIB_MEMFNC_H_
-
-#include <stddef.h>
-#include <cc.h>
-
-#ifdef CONFIG_LTO
-#define DO_NOT_DISCARD ATTRIBUTE_USED
-#else
-#define DO_NOT_DISCARD
-#endif
-
-extern void *memset(void *, int, size_t)
-    __attribute__((nonnull(1)))
-    ATTRIBUTE_OPTIMIZE("-fno-tree-loop-distribute-patterns") DO_NOT_DISCARD;
-extern void *memcpy(void *, const void *, size_t)
-    __attribute__((nonnull(1, 2)))
-    ATTRIBUTE_OPTIMIZE("-fno-tree-loop-distribute-patterns") DO_NOT_DISCARD;
-extern int memcmp(const void *, const void *, size_t len)
-    __attribute__((nonnull(1, 2)))
-    ATTRIBUTE_OPTIMIZE("-fno-tree-loop-distribute-patterns") DO_NOT_DISCARD;
-
-#define alloca(size) __builtin_alloca((size))
-
-#endif
-
-/** @}
- */
Index: kernel/generic/include/lib/refcount.h
===================================================================
--- kernel/generic/include/lib/refcount.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/lib/refcount.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -49,7 +49,11 @@
 } atomic_refcount_t;
 
+#define REFCOUNT_INITIALIZER() { \
+	.__cnt = ATOMIC_VAR_INIT(0), \
+}
+
 static inline void refcount_init(atomic_refcount_t *rc)
 {
-	atomic_store_explicit(&rc->__cnt, 0, memory_order_relaxed);
+	atomic_init(&rc->__cnt, 0);
 }
 
@@ -74,4 +78,24 @@
 	assert(old >= 0);
 	(void) old;
+}
+
+/**
+ * Try to upgrade a weak reference.
+ * Naturally, this is contingent on another form of synchronization being used
+ * to ensure that the object continues to exist while the weak reference is in
+ * use.
+ */
+static inline bool refcount_try_up(atomic_refcount_t *rc)
+{
+	int cnt = atomic_load_explicit(&rc->__cnt, memory_order_relaxed);
+
+	while (cnt >= 0) {
+		if (atomic_compare_exchange_weak_explicit(&rc->__cnt, &cnt, cnt + 1,
+		    memory_order_relaxed, memory_order_relaxed)) {
+			return true;
+		}
+	}
+
+	return false;
 }
 
Index: kernel/generic/include/main/main.h
===================================================================
--- kernel/generic/include/main/main.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/main/main.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -43,6 +43,4 @@
 extern uint8_t kdata_end[];
 
-extern uintptr_t stack_safe;
-
 extern void main_bsp(void);
 extern void main_ap(void);
Index: rnel/generic/include/mem.h
===================================================================
--- kernel/generic/include/mem.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,62 +1,0 @@
-/*
- * Copyright (c) 2001-2004 Jakub Jermar
- * 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 kernel_generic
- * @{
- */
-/** @file
- */
-
-#ifndef KERN_MEM_H_
-#define KERN_MEM_H_
-
-#include <stddef.h>
-#include <stdint.h>
-#include <cc.h>
-
-#ifdef CONFIG_LTO
-#define DO_NOT_DISCARD ATTRIBUTE_USED
-#else
-#define DO_NOT_DISCARD
-#endif
-
-#define memset(dst, val, cnt)  __builtin_memset((dst), (val), (cnt))
-#define memcpy(dst, src, cnt)  __builtin_memcpy((dst), (src), (cnt))
-#define memcmp(s1, s2, cnt)    __builtin_memcmp((s1), (s2), (cnt))
-
-extern void memsetb(void *, size_t, uint8_t)
-    __attribute__((nonnull(1)));
-extern void memsetw(void *, size_t, uint16_t)
-    __attribute__((nonnull(1)));
-extern void *memmove(void *, const void *, size_t)
-    __attribute__((nonnull(1, 2))) DO_NOT_DISCARD;
-
-#endif
-
-/** @}
- */
Index: kernel/generic/include/memw.h
===================================================================
--- kernel/generic/include/memw.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ kernel/generic/include/memw.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2001-2004 Jakub Jermar
+ * 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 kernel_generic
+ * @{
+ */
+/** @file
+ */
+
+#ifndef KERN_MEM_H_
+#define KERN_MEM_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <mem.h>
+
+extern void memsetb(void *, size_t, uint8_t)
+    __attribute__((nonnull(1)));
+extern void memsetw(void *, size_t, uint16_t)
+    __attribute__((nonnull(1)));
+
+#endif
+
+/** @}
+ */
Index: kernel/generic/include/mm/as.h
===================================================================
--- kernel/generic/include/mm/as.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/mm/as.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -307,5 +307,5 @@
 extern as_t *AS_KERNEL;
 
-extern as_operations_t *as_operations;
+extern const as_operations_t *as_operations;
 extern list_t inactive_as_with_asid_list;
 
Index: kernel/generic/include/mm/mm.h
===================================================================
--- kernel/generic/include/mm/mm.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/mm/mm.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -46,4 +46,5 @@
 #define PAGE_EXEC_SHIFT			5
 #define PAGE_GLOBAL_SHIFT		6
+#define PAGE_WRITE_COMBINE_SHIFT  7
 
 #define PAGE_NOT_CACHEABLE		(0 << PAGE_CACHEABLE_SHIFT)
@@ -62,4 +63,6 @@
 #define PAGE_GLOBAL			(1 << PAGE_GLOBAL_SHIFT)
 
+#define PAGE_WRITE_COMBINE  (1 << PAGE_WRITE_COMBINE_SHIFT)
+
 #endif
 
Index: kernel/generic/include/mm/page.h
===================================================================
--- kernel/generic/include/mm/page.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/mm/page.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -53,5 +53,5 @@
 } page_mapping_operations_t;
 
-extern page_mapping_operations_t *page_mapping_operations;
+extern const page_mapping_operations_t *page_mapping_operations;
 
 extern void page_init(void);
Index: kernel/generic/include/mm/tlb.h
===================================================================
--- kernel/generic/include/mm/tlb.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/mm/tlb.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,4 +36,5 @@
 #define KERN_TLB_H_
 
+#include <arch/asm.h>
 #include <arch/mm/asid.h>
 #include <typedefs.h>
Index: kernel/generic/include/proc/program.h
===================================================================
--- kernel/generic/include/proc/program.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/proc/program.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -56,5 +56,5 @@
 
 extern errno_t program_create(as_t *, uspace_addr_t, char *, program_t *);
-extern errno_t program_create_from_image(void *, char *, program_t *);
+extern errno_t program_create_from_image(void *, size_t, char *, program_t *);
 extern errno_t program_create_loader(program_t *, char *);
 extern void program_ready(program_t *);
Index: kernel/generic/include/proc/scheduler.h
===================================================================
--- kernel/generic/include/proc/scheduler.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/proc/scheduler.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -41,4 +41,5 @@
 #include <atomic.h>
 #include <adt/list.h>
+#include <abi/proc/thread.h>
 
 #define RQ_COUNT          16
@@ -56,8 +57,12 @@
 
 extern void scheduler_fpu_lazy_request(void);
-extern void scheduler(void);
 extern void kcpulb(void *arg);
 
 extern void sched_print_list(void);
+
+extern void scheduler_run(void) __attribute__((noreturn));
+extern void scheduler_enter(state_t);
+
+extern void thread_main_func(void);
 
 /*
Index: kernel/generic/include/proc/task.h
===================================================================
--- kernel/generic/include/proc/task.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/proc/task.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,26 +36,27 @@
 #define KERN_TASK_H_
 
-#include <cpu.h>
-#include <ipc/ipc.h>
-#include <ipc/event.h>
-#include <ipc/kbox.h>
-#include <synch/spinlock.h>
-#include <synch/mutex.h>
+#include <abi/proc/task.h>
+#include <abi/sysinfo.h>
 #include <adt/list.h>
 #include <adt/odict.h>
-#include <security/perm.h>
+#include <arch/context.h>
+#include <arch/cpu.h>
+#include <arch/fpu_context.h>
 #include <arch/proc/task.h>
 #include <arch/proc/thread.h>
-#include <arch/context.h>
-#include <arch/fpu_context.h>
-#include <arch/cpu.h>
+#include <arch.h>
+#include <cap/cap.h>
+#include <cpu.h>
+#include <debug/sections.h>
+#include <ipc/event.h>
+#include <ipc/ipc.h>
+#include <ipc/kbox.h>
+#include <mm/as.h>
 #include <mm/tlb.h>
 #include <proc/scheduler.h>
+#include <security/perm.h>
+#include <synch/mutex.h>
+#include <synch/spinlock.h>
 #include <udebug/udebug.h>
-#include <mm/as.h>
-#include <abi/proc/task.h>
-#include <abi/sysinfo.h>
-#include <arch.h>
-#include <cap/cap.h>
 
 #define TASK                 CURRENT->task
@@ -87,6 +88,7 @@
 
 	/** Number of references (i.e. threads). */
-	atomic_size_t refcount;
+	atomic_refcount_t refcount;
 	/** Number of threads that haven't exited yet. */
+	// TODO: remove
 	atomic_size_t lifecount;
 
@@ -130,4 +132,6 @@
 	uint64_t ucycles;
 	uint64_t kcycles;
+
+	debug_sections_t *debug_sections;
 } task_t;
 
@@ -140,5 +144,4 @@
 extern void task_done(void);
 extern task_t *task_create(as_t *, const char *);
-extern void task_destroy(task_t *);
 extern void task_hold(task_t *);
 extern void task_release(task_t *);
Index: kernel/generic/include/proc/thread.h
===================================================================
--- kernel/generic/include/proc/thread.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/proc/thread.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -70,4 +70,6 @@
 /** Thread structure. There is one per thread. */
 typedef struct thread {
+	atomic_refcount_t refcount;
+
 	link_t rq_link;  /**< Run queue link. */
 	link_t wq_link;  /**< Wait queue link. */
@@ -77,11 +79,48 @@
 	odlink_t lthreads;
 
-	/** Lock protecting thread structure.
+	/** Tracking variable for thread_wait/thread_wakeup */
+	atomic_int sleep_state;
+
+	/**
+	 * If true, the thread is terminating.
+	 * It will not go to sleep in interruptible synchronization functions
+	 * and will call thread_exit() before returning to userspace.
+	 */
+	volatile bool interrupted;
+
+	/** Wait queue in which this thread sleeps. Used for debug printouts. */
+	_Atomic(waitq_t *) sleep_queue;
+
+	/** Waitq for thread_join_timeout(). */
+	waitq_t join_wq;
+
+	/** Thread accounting. */
+	atomic_time_stat_t ucycles;
+	atomic_time_stat_t kcycles;
+
+	/** Architecture-specific data. */
+	thread_arch_t arch;
+
+#ifdef CONFIG_UDEBUG
+	/**
+	 * If true, the scheduler will print a stack trace
+	 * to the kernel console upon scheduling this thread.
+	 */
+	atomic_int_fast8_t btrace;
+
+	/** Debugging stuff */
+	udebug_thread_t udebug;
+#endif /* CONFIG_UDEBUG */
+
+	/*
+	 * Immutable fields.
 	 *
-	 * Protects the whole thread structure except list links above.
-	 */
-	IRQ_SPINLOCK_DECLARE(lock);
-
-	char name[THREAD_NAME_BUFLEN];
+	 * These fields are only modified during initialization, and are not
+	 * changed at any time between initialization and destruction.
+	 * Can be accessed without synchronization in most places.
+	 */
+
+	/** Thread ID. */
+	thread_id_t tid;
 
 	/** Function implementing the thread. */
@@ -90,4 +129,32 @@
 	void *thread_arg;
 
+	char name[THREAD_NAME_BUFLEN];
+
+	/** Thread is executed in user space. */
+	bool uspace;
+
+	/** Thread doesn't affect accumulated accounting. */
+	bool uncounted;
+
+	/** Containing task. */
+	task_t *task;
+
+	/** Thread's kernel stack. */
+	uint8_t *kstack;
+
+	/*
+	 * Local fields.
+	 *
+	 * These fields can be safely accessed from code that _controls execution_
+	 * of this thread. Code controls execution of a thread if either:
+	 *  - it runs in the context of said thread AND interrupts are disabled
+	 *    (interrupts can and will access these fields)
+	 *  - the thread is not running, and the code accessing it can legally
+	 *    add/remove the thread to/from a runqueue, i.e., either:
+	 *    - it is allowed to enqueue thread in a new runqueue
+	 *    - it holds the lock to the runqueue containing the thread
+	 *
+	 */
+
 	/**
 	 * From here, the stored context is restored
@@ -96,32 +163,5 @@
 	context_t saved_context;
 
-	/**
-	 * From here, the stored timeout context
-	 * is restored when sleep times out.
-	 */
-	context_t sleep_timeout_context;
-
-	/**
-	 * From here, the stored interruption context
-	 * is restored when sleep is interrupted.
-	 */
-	context_t sleep_interruption_context;
-
-	/** If true, the thread can be interrupted from sleep. */
-	bool sleep_interruptible;
-
-	/**
-	 * If true, and this thread's sleep returns without a wakeup
-	 * (timed out or interrupted), waitq ignores the next wakeup.
-	 * This is necessary for futex to be able to handle those conditions.
-	 */
-	bool sleep_composable;
-
-	/** Wait queue in which this thread sleeps. */
-	waitq_t *sleep_queue;
-	/** Timeout used for timeoutable sleeping.  */
-	timeout_t sleep_timeout;
-	/** Flag signalling sleep timeout in progress. */
-	volatile bool timeout_pending;
+	// TODO: we only need one of the two bools below
 
 	/**
@@ -137,76 +177,35 @@
 	bool in_copy_to_uspace;
 
-	/**
-	 * If true, the thread will not go to sleep at all and will call
-	 * thread_exit() before returning to userspace.
-	 */
-	bool interrupted;
-
-	/** If true, thread_join_timeout() cannot be used on this thread. */
-	bool detached;
-	/** Waitq for thread_join_timeout(). */
-	waitq_t join_wq;
-	/** Link used in the joiner_head list. */
-	link_t joiner_link;
-
-	fpu_context_t *saved_fpu_context;
+	/*
+	 * FPU context is a special case. If lazy FPU switching is disabled,
+	 * it acts as a regular local field. However, if lazy switching is enabled,
+	 * the context is synchronized via CPU->fpu_lock
+	 */
+#ifdef CONFIG_FPU
+	fpu_context_t fpu_context;
+#endif
 	bool fpu_context_exists;
-
-	/*
-	 * Defined only if thread doesn't run.
-	 * It means that fpu context is in CPU that last time executes this
-	 * thread. This disables migration.
-	 */
-	bool fpu_context_engaged;
 
 	/* The thread will not be migrated if nomigrate is non-zero. */
 	unsigned int nomigrate;
 
-	/** Thread state. */
-	state_t state;
-
-	/** Thread CPU. */
-	cpu_t *cpu;
-	/** Containing task. */
-	task_t *task;
-	/** Thread is wired to CPU. */
-	bool wired;
 	/** Thread was migrated to another CPU and has not run yet. */
 	bool stolen;
-	/** Thread is executed in user space. */
-	bool uspace;
-
-	/** Ticks before preemption. */
-	uint64_t ticks;
-
-	/** Thread accounting. */
-	uint64_t ucycles;
-	uint64_t kcycles;
+
+	/**
+	 * Thread state (state_t).
+	 * This is atomic because we read it via some commands for debug output,
+	 * otherwise it could just be a regular local.
+	 */
+	atomic_int_fast32_t state;
+
+	/** Thread CPU. */
+	_Atomic(cpu_t *) cpu;
+
+	/** Thread's priority. Implemented as index to CPU->rq */
+	atomic_int_fast32_t priority;
+
 	/** Last sampled cycle. */
 	uint64_t last_cycle;
-	/** Thread doesn't affect accumulated accounting. */
-	bool uncounted;
-
-	/** Thread's priority. Implemented as index to CPU->rq */
-	int priority;
-	/** Thread ID. */
-	thread_id_t tid;
-
-	/** Architecture-specific data. */
-	thread_arch_t arch;
-
-	/** Thread's kernel stack. */
-	uint8_t *kstack;
-
-#ifdef CONFIG_UDEBUG
-	/**
-	 * If true, the scheduler will print a stack trace
-	 * to the kernel console upon scheduling this thread.
-	 */
-	bool btrace;
-
-	/** Debugging stuff */
-	udebug_thread_t udebug;
-#endif /* CONFIG_UDEBUG */
 } thread_t;
 
@@ -219,8 +218,44 @@
 extern void thread_wire(thread_t *, cpu_t *);
 extern void thread_attach(thread_t *, task_t *);
-extern void thread_ready(thread_t *);
+extern void thread_start(thread_t *);
+extern void thread_requeue_sleeping(thread_t *);
 extern void thread_exit(void) __attribute__((noreturn));
 extern void thread_interrupt(thread_t *);
-extern bool thread_interrupted(thread_t *);
+
+enum sleep_state {
+	SLEEP_INITIAL,
+	SLEEP_ASLEEP,
+	SLEEP_WOKE,
+};
+
+typedef enum {
+	THREAD_OK,
+	THREAD_TERMINATING,
+} thread_termination_state_t;
+
+typedef enum {
+	THREAD_WAIT_SUCCESS,
+	THREAD_WAIT_TIMEOUT,
+} thread_wait_result_t;
+
+extern thread_termination_state_t thread_wait_start(void);
+extern thread_wait_result_t thread_wait_finish(deadline_t);
+extern void thread_wakeup(thread_t *);
+
+static inline thread_t *thread_ref(thread_t *thread)
+{
+	refcount_up(&thread->refcount);
+	return thread;
+}
+
+static inline thread_t *thread_try_ref(thread_t *thread)
+{
+	if (refcount_try_up(&thread->refcount))
+		return thread;
+	else
+		return NULL;
+}
+
+extern void thread_put(thread_t *);
 
 #ifndef thread_create_arch
@@ -239,12 +274,11 @@
 extern void thread_usleep(uint32_t);
 
-#define thread_join(t) \
-	thread_join_timeout((t), SYNCH_NO_TIMEOUT, SYNCH_FLAGS_NONE)
-
+extern errno_t thread_join(thread_t *);
 extern errno_t thread_join_timeout(thread_t *, uint32_t, unsigned int);
 extern void thread_detach(thread_t *);
 
+extern void thread_yield(void);
+
 extern void thread_print_list(bool);
-extern void thread_destroy(thread_t *, bool);
 extern thread_t *thread_find_by_id(thread_id_t);
 extern size_t thread_count(void);
@@ -252,5 +286,5 @@
 extern thread_t *thread_next(thread_t *);
 extern void thread_update_accounting(bool);
-extern bool thread_exists(thread_t *);
+extern thread_t *thread_try_get(thread_t *);
 
 extern void thread_migration_disable(void);
Index: kernel/generic/include/smp/smp.h
===================================================================
--- kernel/generic/include/smp/smp.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/smp/smp.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,7 +36,7 @@
 #define KERN_SMP_H_
 
-#include <synch/waitq.h>
+#include <synch/semaphore.h>
 
-extern waitq_t ap_completion_wq;
+extern semaphore_t ap_completion_semaphore;
 
 #ifdef CONFIG_SMP
Index: kernel/generic/include/stacktrace.h
===================================================================
--- kernel/generic/include/stacktrace.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/stacktrace.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -52,5 +52,5 @@
 	bool (*frame_pointer_prev)(stack_trace_context_t *, uintptr_t *);
 	bool (*return_address_get)(stack_trace_context_t *, uintptr_t *);
-	bool (*symbol_resolve)(uintptr_t, const char **, uintptr_t *);
+	bool (*symbol_resolve)(uintptr_t, int, const char **, uintptr_t *, const char **, const char **, int *, int *);
 } stack_trace_ops_t;
 
Index: rnel/generic/include/stdlib.h
===================================================================
--- kernel/generic/include/stdlib.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,49 +1,0 @@
-/*
- * Copyright (c) 2006 Ondrej Palkovsky
- * 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 kernel_generic_mm
- * @{
- */
-/** @file
- */
-
-#ifndef KERN_STDLIB_H_
-#define KERN_STDLIB_H_
-
-#include <stddef.h>
-
-extern void *malloc(size_t)
-    __attribute__((malloc));
-extern void *realloc(void *, size_t)
-    __attribute__((warn_unused_result));
-extern void free(void *);
-
-#endif
-
-/** @}
- */
Index: rnel/generic/include/str.h
===================================================================
--- kernel/generic/include/str.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,116 +1,0 @@
-/*
- * Copyright (c) 2001-2004 Jakub Jermar
- * Copyright (c) 2005 Martin Decky
- * Copyright (c) 2011 Oleg Romanenko
- * 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 kernel_generic
- * @{
- */
-/** @file
- */
-
-#ifndef KERN_STR_H_
-#define KERN_STR_H_
-
-#include <errno.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-
-/* Common Unicode characters */
-#define U_SPECIAL  '?'
-
-#define U_LEFT_ARROW   0x2190
-#define U_UP_ARROW     0x2191
-#define U_RIGHT_ARROW  0x2192
-#define U_DOWN_ARROW   0x2193
-
-#define U_PAGE_UP      0x21de
-#define U_PAGE_DOWN    0x21df
-
-#define U_HOME_ARROW   0x21f1
-#define U_END_ARROW    0x21f2
-
-#define U_NULL         0x2400
-#define U_ESCAPE       0x241b
-#define U_DELETE       0x2421
-
-#define U_CURSOR       0x2588
-
-/** No size limit constant */
-#define STR_NO_LIMIT  ((size_t) -1)
-
-/** Maximum size of a string containing @c length characters */
-#define STR_BOUNDS(length)  ((length) << 2)
-
-extern char32_t str_decode(const char *, size_t *, size_t);
-extern errno_t chr_encode(char32_t, char *, size_t *, size_t);
-
-extern size_t str_size(const char *);
-extern size_t wstr_size(const char32_t *);
-
-extern size_t str_lsize(const char *, size_t);
-extern size_t wstr_lsize(const char32_t *, size_t);
-
-extern size_t str_length(const char *);
-extern size_t wstr_length(const char32_t *);
-
-extern size_t str_nlength(const char *, size_t);
-extern size_t wstr_nlength(const char32_t *, size_t);
-
-extern bool ascii_check(char32_t);
-extern bool chr_check(char32_t);
-
-extern int str_cmp(const char *, const char *);
-extern int str_lcmp(const char *, const char *, size_t);
-
-extern void str_cpy(char *, size_t, const char *);
-extern void str_ncpy(char *, size_t, const char *, size_t);
-extern void wstr_to_str(char *, size_t, const char32_t *);
-
-extern char *str_chr(const char *, char32_t);
-
-extern bool wstr_linsert(char32_t *, char32_t, size_t, size_t);
-extern bool wstr_remove(char32_t *, size_t);
-
-extern char *str_dup(const char *);
-extern char *str_ndup(const char *, size_t);
-
-extern errno_t str_uint64_t(const char *, char **, unsigned int, bool,
-    uint64_t *);
-
-extern void order_suffix(const uint64_t, uint64_t *, char *);
-extern void bin_order_suffix(const uint64_t, uint64_t *, const char **, bool);
-
-extern const char *str_error(errno_t);
-extern const char *str_error_name(errno_t);
-
-#endif
-
-/** @}
- */
Index: kernel/generic/include/symtab.h
===================================================================
--- kernel/generic/include/symtab.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/symtab.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,9 +36,13 @@
 #define KERN_SYMTAB_H_
 
-#include <symtab_lookup.h>
-#include <console/chardev.h>
+#include <stdbool.h>
+#include <typedefs.h>
+#include <debug/sections.h>
 
 extern void symtab_print_search(const char *);
 extern const char *symtab_hints_enum(const char *, const char **, void **);
+extern const char *symtab_fmt_name_lookup(uintptr_t);
+extern errno_t symtab_addr_lookup(const char *, uintptr_t *);
+extern const char *symtab_name_lookup(uintptr_t, uintptr_t *, debug_sections_t *);
 
 #endif
Index: rnel/generic/include/symtab_lookup.h
===================================================================
--- kernel/generic/include/symtab_lookup.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,63 +1,0 @@
-/*
- * Copyright (c) 2005 Ondrej Palkovsky
- * 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 kernel_generic
- * @{
- */
-/** @file
- */
-
-#ifndef KERN_SYMTAB_LOOKUP_H_
-#define KERN_SYMTAB_LOOKUP_H_
-
-#include <typedefs.h>
-
-#define MAX_SYMBOL_NAME  64
-
-struct symtab_entry {
-	uint64_t address_le;
-	char symbol_name[MAX_SYMBOL_NAME];
-};
-
-extern errno_t symtab_name_lookup(uintptr_t, const char **, uintptr_t *);
-extern const char *symtab_fmt_name_lookup(uintptr_t);
-extern errno_t symtab_addr_lookup(const char *, uintptr_t *);
-
-#ifdef CONFIG_SYMTAB
-
-/** Symtable linked together by build process
- *
- */
-extern struct symtab_entry symbol_table[];
-
-#endif /* CONFIG_SYMTAB */
-
-#endif
-
-/** @}
- */
Index: kernel/generic/include/synch/condvar.h
===================================================================
--- kernel/generic/include/synch/condvar.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/synch/condvar.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -46,9 +46,4 @@
 } condvar_t;
 
-#define condvar_wait(cv, mtx) \
-	_condvar_wait_timeout((cv), (mtx), SYNCH_NO_TIMEOUT, SYNCH_FLAGS_NONE)
-#define condvar_wait_timeout(cv, mtx, usec) \
-	_condvar_wait_timeout((cv), (mtx), (usec), SYNCH_FLAGS_NONE)
-
 #ifdef CONFIG_SMP
 #define _condvar_wait_timeout_spinlock(cv, lock, usec, flags) \
@@ -62,6 +57,8 @@
 extern void condvar_signal(condvar_t *cv);
 extern void condvar_broadcast(condvar_t *cv);
-extern errno_t _condvar_wait_timeout(condvar_t *cv, mutex_t *mtx, uint32_t usec,
-    int flags);
+
+extern errno_t condvar_wait(condvar_t *cv, mutex_t *mtx);
+extern errno_t condvar_wait_timeout(condvar_t *cv, mutex_t *mtx, uint32_t usec);
+
 extern errno_t _condvar_wait_timeout_spinlock_impl(condvar_t *cv, spinlock_t *lock,
     uint32_t usec, int flags);
Index: kernel/generic/include/synch/mutex.h
===================================================================
--- kernel/generic/include/synch/mutex.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/synch/mutex.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -56,16 +56,9 @@
 } mutex_t;
 
-#define mutex_lock(mtx) \
-	_mutex_lock_timeout((mtx), SYNCH_NO_TIMEOUT, SYNCH_FLAGS_NONE)
-
-#define mutex_trylock(mtx) \
-	_mutex_lock_timeout((mtx), SYNCH_NO_TIMEOUT, SYNCH_FLAGS_NON_BLOCKING)
-
-#define mutex_lock_timeout(mtx, usec) \
-	_mutex_lock_timeout((mtx), (usec), SYNCH_FLAGS_NON_BLOCKING)
-
 extern void mutex_initialize(mutex_t *, mutex_type_t);
 extern bool mutex_locked(mutex_t *);
-extern errno_t _mutex_lock_timeout(mutex_t *, uint32_t, unsigned int);
+extern errno_t mutex_trylock(mutex_t *);
+extern void mutex_lock(mutex_t *);
+extern errno_t mutex_lock_timeout(mutex_t *, uint32_t);
 extern void mutex_unlock(mutex_t *);
 
Index: kernel/generic/include/synch/semaphore.h
===================================================================
--- kernel/generic/include/synch/semaphore.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/synch/semaphore.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -45,21 +45,9 @@
 } semaphore_t;
 
-#define semaphore_down(s) \
-	_semaphore_down_timeout((s), SYNCH_NO_TIMEOUT, SYNCH_FLAGS_NONE)
-
-#define semaphore_trydown(s) \
-	_semaphore_down_timeout((s), SYNCH_NO_TIMEOUT, SYNCH_FLAGS_NON_BLOCKING)
-
-#define semaphore_down_timeout(s, usec) \
-	_semaphore_down_timeout((s), (usec), SYNCH_FLAGS_NONE)
-
-#define semaphore_down_interruptable(s) \
-	(_semaphore_down_timeout((s), SYNCH_NO_TIMEOUT, \
-		SYNCH_FLAGS_INTERRUPTIBLE) != EINTR)
-
 extern void semaphore_initialize(semaphore_t *, int);
-extern errno_t _semaphore_down_timeout(semaphore_t *, uint32_t, unsigned int);
+extern errno_t semaphore_down_timeout(semaphore_t *, uint32_t);
+extern errno_t semaphore_trydown(semaphore_t *);
+extern void semaphore_down(semaphore_t *);
 extern void semaphore_up(semaphore_t *);
-extern int semaphore_count_get(semaphore_t *);
 
 #endif
Index: kernel/generic/include/synch/spinlock.h
===================================================================
--- kernel/generic/include/synch/spinlock.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/synch/spinlock.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,5 @@
 /*
  * Copyright (c) 2001-2004 Jakub Jermar
+ * Copyright (c) 2023 Jiří Zárevúcky
  * All rights reserved.
  *
@@ -36,108 +37,15 @@
 #define KERN_SPINLOCK_H_
 
-#include <assert.h>
 #include <stdatomic.h>
 #include <stdbool.h>
-#include <preemption.h>
-#include <arch/asm.h>
-
-#ifdef CONFIG_SMP
-
-typedef struct spinlock {
-	atomic_flag flag;
-
-#ifdef CONFIG_DEBUG_SPINLOCK
-	const char *name;
-#endif /* CONFIG_DEBUG_SPINLOCK */
-} spinlock_t;
-
-/*
- * SPINLOCK_DECLARE is to be used for dynamically allocated spinlocks,
- * where the lock gets initialized in run time.
- */
-#define SPINLOCK_DECLARE(lock_name)  spinlock_t lock_name
-#define SPINLOCK_EXTERN(lock_name)   extern spinlock_t lock_name
-
-/*
- * SPINLOCK_INITIALIZE and SPINLOCK_STATIC_INITIALIZE are to be used
- * for statically allocated spinlocks. They declare (either as global
- * or static) symbol and initialize the lock.
- */
-#ifdef CONFIG_DEBUG_SPINLOCK
-
-#define SPINLOCK_INITIALIZE_NAME(lock_name, desc_name) \
-	spinlock_t lock_name = { \
-		.name = desc_name, \
-		.flag = ATOMIC_FLAG_INIT \
-	}
-
-#define SPINLOCK_STATIC_INITIALIZE_NAME(lock_name, desc_name) \
-	static spinlock_t lock_name = { \
-		.name = desc_name, \
-		.flag = ATOMIC_FLAG_INIT \
-	}
-
-#define ASSERT_SPINLOCK(expr, lock) \
-	assert_verbose(expr, (lock)->name)
-
-#define spinlock_lock(lock)    spinlock_lock_debug((lock))
-#define spinlock_unlock(lock)  spinlock_unlock_debug((lock))
-
-#else /* CONFIG_DEBUG_SPINLOCK */
-
-#define SPINLOCK_INITIALIZE_NAME(lock_name, desc_name) \
-	spinlock_t lock_name = { \
-		.flag = ATOMIC_FLAG_INIT \
-	}
-
-#define SPINLOCK_STATIC_INITIALIZE_NAME(lock_name, desc_name) \
-	static spinlock_t lock_name = { \
-		.flag = ATOMIC_FLAG_INIT \
-	}
-
-#define ASSERT_SPINLOCK(expr, lock) \
-	assert(expr)
-
-/** Acquire spinlock
- *
- * @param lock  Pointer to spinlock_t structure.
- */
-_NO_TRACE static inline void spinlock_lock(spinlock_t *lock)
-{
-	preemption_disable();
-	while (atomic_flag_test_and_set_explicit(&lock->flag,
-	    memory_order_acquire))
-		;
-}
-
-/** Release spinlock
- *
- * @param lock  Pointer to spinlock_t structure.
- */
-_NO_TRACE static inline void spinlock_unlock(spinlock_t *lock)
-{
-	atomic_flag_clear_explicit(&lock->flag, memory_order_release);
-	preemption_enable();
-}
-
-#endif /* CONFIG_DEBUG_SPINLOCK */
-
-#define SPINLOCK_INITIALIZE(lock_name) \
-	SPINLOCK_INITIALIZE_NAME(lock_name, #lock_name)
-
-#define SPINLOCK_STATIC_INITIALIZE(lock_name) \
-	SPINLOCK_STATIC_INITIALIZE_NAME(lock_name, #lock_name)
-
-extern void spinlock_initialize(spinlock_t *, const char *);
-extern bool spinlock_trylock(spinlock_t *);
-extern void spinlock_lock_debug(spinlock_t *);
-extern void spinlock_unlock_debug(spinlock_t *);
-extern bool spinlock_locked(spinlock_t *);
-
-#ifdef CONFIG_DEBUG_SPINLOCK
+
+#include <arch/types.h>
+#include <assert.h>
+
+#define DEADLOCK_THRESHOLD  100000000
+
+#if defined(CONFIG_SMP) && defined(CONFIG_DEBUG_SPINLOCK)
 
 #include <log.h>
-
-#define DEADLOCK_THRESHOLD  100000000
 
 #define DEADLOCK_PROBE_INIT(pname)  size_t pname = 0
@@ -159,39 +67,100 @@
 #endif /* CONFIG_DEBUG_SPINLOCK */
 
-#else /* CONFIG_SMP */
-
-/* On UP systems, spinlocks are effectively left out. */
-
-/* Allow the use of spinlock_t as an incomplete type. */
-typedef struct spinlock spinlock_t;
-
-#define SPINLOCK_DECLARE(name)
-#define SPINLOCK_EXTERN(name)
-
-#define SPINLOCK_INITIALIZE(name)
-#define SPINLOCK_STATIC_INITIALIZE(name)
-
-#define SPINLOCK_INITIALIZE_NAME(name, desc_name)
-#define SPINLOCK_STATIC_INITIALIZE_NAME(name, desc_name)
-
-#define ASSERT_SPINLOCK(expr, lock)  assert(expr)
-
-#define spinlock_initialize(lock, name)
-
-#define spinlock_lock(lock)     preemption_disable()
-#define spinlock_trylock(lock)  ({ preemption_disable(); 1; })
-#define spinlock_unlock(lock)   preemption_enable()
-#define spinlock_locked(lock)	1
-#define spinlock_unlocked(lock)	1
-
-#define DEADLOCK_PROBE_INIT(pname)
-#define DEADLOCK_PROBE(pname, value)
-
-#endif /* CONFIG_SMP */
+typedef struct spinlock {
+#ifdef CONFIG_SMP
+	atomic_flag flag;
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+	const char *name;
+#endif /* CONFIG_DEBUG_SPINLOCK */
+#endif
+} spinlock_t;
+
+/*
+ * SPINLOCK_DECLARE is to be used for dynamically allocated spinlocks,
+ * where the lock gets initialized in run time.
+ */
+#define SPINLOCK_DECLARE(lock_name)  spinlock_t lock_name
+#define SPINLOCK_EXTERN(lock_name)   extern spinlock_t lock_name
+
+#ifdef CONFIG_SMP
+#ifdef CONFIG_DEBUG_SPINLOCK
+#define SPINLOCK_INITIALIZER(desc_name) { .name = (desc_name), .flag = ATOMIC_FLAG_INIT }
+#else
+#define SPINLOCK_INITIALIZER(desc_name) { .flag = ATOMIC_FLAG_INIT }
+#endif
+#else
+#define SPINLOCK_INITIALIZER(desc_name) {}
+#endif
+
+/*
+ * SPINLOCK_INITIALIZE and SPINLOCK_STATIC_INITIALIZE are to be used
+ * for statically allocated spinlocks. They declare (either as global
+ * or static) symbol and initialize the lock.
+ */
+#define SPINLOCK_INITIALIZE_NAME(lock_name, desc_name) \
+	spinlock_t lock_name = SPINLOCK_INITIALIZER(desc_name)
+
+#define SPINLOCK_STATIC_INITIALIZE_NAME(lock_name, desc_name) \
+	static spinlock_t lock_name = SPINLOCK_INITIALIZER(desc_name)
+
+#if defined(CONFIG_SMP) && defined(CONFIG_DEBUG_SPINLOCK)
+#define ASSERT_SPINLOCK(expr, lock) assert_verbose(expr, (lock)->name)
+#else /* CONFIG_DEBUG_SPINLOCK */
+#define ASSERT_SPINLOCK(expr, lock) assert(expr)
+#endif /* CONFIG_DEBUG_SPINLOCK */
+
+#define SPINLOCK_INITIALIZE(lock_name) \
+	SPINLOCK_INITIALIZE_NAME(lock_name, #lock_name)
+
+#define SPINLOCK_STATIC_INITIALIZE(lock_name) \
+	SPINLOCK_STATIC_INITIALIZE_NAME(lock_name, #lock_name)
+
+#ifdef CONFIG_SMP
+
+extern void spinlock_initialize(spinlock_t *, const char *);
+extern bool spinlock_trylock(spinlock_t *);
+extern void spinlock_lock(spinlock_t *);
+extern void spinlock_unlock(spinlock_t *);
+extern bool spinlock_locked(spinlock_t *);
+
+#else
+
+#include <preemption.h>
+
+static inline void spinlock_initialize(spinlock_t *l, const char *name)
+{
+}
+
+static inline bool spinlock_trylock(spinlock_t *l)
+{
+	preemption_disable();
+	return true;
+}
+
+static inline void spinlock_lock(spinlock_t *l)
+{
+	preemption_disable();
+}
+
+static inline void spinlock_unlock(spinlock_t *l)
+{
+	preemption_enable();
+}
+
+static inline bool spinlock_locked(spinlock_t *l)
+{
+	return true;
+}
+
+#endif
 
 typedef struct {
-	SPINLOCK_DECLARE(lock);  /**< Spinlock */
-	bool guard;              /**< Flag whether ipl is valid */
-	ipl_t ipl;               /**< Original interrupt level */
+	spinlock_t lock;              /**< Spinlock */
+	bool guard;                   /**< Flag whether ipl is valid */
+	ipl_t ipl;                    /**< Original interrupt level */
+#ifdef CONFIG_DEBUG_SPINLOCK
+	_Atomic(struct cpu *) owner;  /**< Which cpu currently owns this lock */
+#endif
 } irq_spinlock_t;
 
@@ -199,8 +168,13 @@
 #define IRQ_SPINLOCK_EXTERN(lock_name)   extern irq_spinlock_t lock_name
 
-#ifdef CONFIG_SMP
-
 #define ASSERT_IRQ_SPINLOCK(expr, irq_lock) \
 	ASSERT_SPINLOCK(expr, &((irq_lock)->lock))
+
+#define IRQ_SPINLOCK_INITIALIZER(desc_name) \
+	{ \
+		.lock = SPINLOCK_INITIALIZER(desc_name), \
+		.guard = false, \
+		.ipl = 0, \
+	}
 
 /*
@@ -209,72 +183,9 @@
  * as global or static symbol) and initialize the lock.
  */
-#ifdef CONFIG_DEBUG_SPINLOCK
-
 #define IRQ_SPINLOCK_INITIALIZE_NAME(lock_name, desc_name) \
-	irq_spinlock_t lock_name = { \
-		.lock = { \
-			.name = desc_name, \
-			.flag = ATOMIC_FLAG_INIT \
-		}, \
-		.guard = false, \
-		.ipl = 0 \
-	}
+	irq_spinlock_t lock_name = IRQ_SPINLOCK_INITIALIZER(desc_name)
 
 #define IRQ_SPINLOCK_STATIC_INITIALIZE_NAME(lock_name, desc_name) \
-	static irq_spinlock_t lock_name = { \
-		.lock = { \
-			.name = desc_name, \
-			.flag = ATOMIC_FLAG_INIT \
-		}, \
-		.guard = false, \
-		.ipl = 0 \
-	}
-
-#else /* CONFIG_DEBUG_SPINLOCK */
-
-#define IRQ_SPINLOCK_INITIALIZE_NAME(lock_name, desc_name) \
-	irq_spinlock_t lock_name = { \
-		.lock = { \
-			.flag = ATOMIC_FLAG_INIT \
-		}, \
-		.guard = false, \
-		.ipl = 0 \
-	}
-
-#define IRQ_SPINLOCK_STATIC_INITIALIZE_NAME(lock_name, desc_name) \
-	static irq_spinlock_t lock_name = { \
-		.lock = { \
-			.flag = ATOMIC_FLAG_INIT \
-		}, \
-		.guard = false, \
-		.ipl = 0 \
-	}
-
-#endif /* CONFIG_DEBUG_SPINLOCK */
-
-#else /* CONFIG_SMP */
-
-/*
- * Since the spinlocks are void on UP systems, we also need
- * to have a special variant of interrupts-disabled spinlock
- * macros which take this into account.
- */
-
-#define ASSERT_IRQ_SPINLOCK(expr, irq_lock) \
-	ASSERT_SPINLOCK(expr, NULL)
-
-#define IRQ_SPINLOCK_INITIALIZE_NAME(lock_name, desc_name) \
-	irq_spinlock_t lock_name = { \
-		.guard = false, \
-		.ipl = 0 \
-	}
-
-#define IRQ_SPINLOCK_STATIC_INITIALIZE_NAME(lock_name, desc_name) \
-	static irq_spinlock_t lock_name = { \
-		.guard = false, \
-		.ipl = 0 \
-	}
-
-#endif /* CONFIG_SMP */
+	static irq_spinlock_t lock_name = IRQ_SPINLOCK_INITIALIZER(desc_name)
 
 #define IRQ_SPINLOCK_INITIALIZE(lock_name) \
Index: kernel/generic/include/synch/waitq.h
===================================================================
--- kernel/generic/include/synch/waitq.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/synch/waitq.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -41,9 +41,4 @@
 #include <adt/list.h>
 
-typedef enum {
-	WAKEUP_FIRST = 0,
-	WAKEUP_ALL
-} wakeup_mode_t;
-
 /** Wait queue structure.
  *
@@ -57,31 +52,34 @@
 
 	/**
-	 * Number of waitq_wakeup() calls that didn't find a thread to wake up.
-	 *
+	 * If negative, number of wakeups that are to be ignored (necessary for futex operation).
+	 * If positive, number of wakeups that weren't able to wake a thread.
 	 */
-	int missed_wakeups;
-
-	/** Number of wakeups that need to be ignored due to futex timeout. */
-	int ignore_wakeups;
+	int wakeup_balance;
 
 	/** List of sleeping threads for which there was no missed_wakeup. */
 	list_t sleepers;
+
+	bool closed;
 } waitq_t;
 
-#define waitq_sleep(wq) \
-	waitq_sleep_timeout((wq), SYNCH_NO_TIMEOUT, SYNCH_FLAGS_NONE, NULL)
+typedef struct wait_guard {
+	ipl_t ipl;
+} wait_guard_t;
 
 struct thread;
 
 extern void waitq_initialize(waitq_t *);
-extern errno_t waitq_sleep_timeout(waitq_t *, uint32_t, unsigned int, bool *);
-extern ipl_t waitq_sleep_prepare(waitq_t *);
-extern errno_t waitq_sleep_timeout_unsafe(waitq_t *, uint32_t, unsigned int, bool *);
-extern void waitq_sleep_finish(waitq_t *, bool, ipl_t);
-extern void waitq_wakeup(waitq_t *, wakeup_mode_t);
-extern void _waitq_wakeup_unsafe(waitq_t *, wakeup_mode_t);
-extern void waitq_interrupt_sleep(struct thread *);
-extern int waitq_count_get(waitq_t *);
-extern void waitq_count_set(waitq_t *, int val);
+extern void waitq_initialize_with_count(waitq_t *, int);
+extern errno_t waitq_sleep(waitq_t *);
+extern errno_t _waitq_sleep_timeout(waitq_t *, uint32_t, unsigned int);
+extern errno_t waitq_sleep_timeout(waitq_t *, uint32_t);
+extern wait_guard_t waitq_sleep_prepare(waitq_t *);
+extern errno_t waitq_sleep_unsafe(waitq_t *, wait_guard_t);
+extern errno_t waitq_sleep_timeout_unsafe(waitq_t *, uint32_t, unsigned int, wait_guard_t);
+
+extern void waitq_wake_one(waitq_t *);
+extern void waitq_wake_all(waitq_t *);
+extern void waitq_signal(waitq_t *);
+extern void waitq_close(waitq_t *);
 
 #endif
Index: kernel/generic/include/time/timeout.h
===================================================================
--- kernel/generic/include/time/timeout.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/include/time/timeout.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -42,11 +42,12 @@
 typedef void (*timeout_handler_t)(void *arg);
 
+typedef uint64_t deadline_t;
+#define DEADLINE_NEVER ((deadline_t) UINT64_MAX)
+
 typedef struct {
-	IRQ_SPINLOCK_DECLARE(lock);
-
-	/** Link to the list of active timeouts on CURRENT->cpu */
+	/** Link to the list of active timeouts on timeout->cpu */
 	link_t link;
-	/** Timeout will be activated in this amount of clock() ticks. */
-	uint64_t ticks;
+	/** Timeout will be activated when current clock tick reaches this value. */
+	deadline_t deadline;
 	/** Function that will be called on timeout activation. */
 	timeout_handler_t handler;
@@ -55,12 +56,16 @@
 	/** On which processor is this timeout registered. */
 	cpu_t *cpu;
+	/** Used to synchronize with the handler callback. */
+	atomic_bool finished;
 } timeout_t;
 
 #define us2ticks(us)  ((uint64_t) (((uint32_t) (us) / (1000000 / HZ))))
 
+extern deadline_t timeout_deadline_in_usec(uint32_t us);
+
 extern void timeout_init(void);
 extern void timeout_initialize(timeout_t *);
-extern void timeout_reinitialize(timeout_t *);
 extern void timeout_register(timeout_t *, uint64_t, timeout_handler_t, void *);
+extern void timeout_register_deadline(timeout_t *, deadline_t, timeout_handler_t, void *);
 extern bool timeout_unregister(timeout_t *);
 
Index: kernel/generic/meson.build
===================================================================
--- kernel/generic/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,8 +36,17 @@
 
 generic_src += files(
-	'src/adt/bitmap.c',
-	'src/adt/hash_table.c',
-	'src/adt/list.c',
-	'src/adt/odict.c',
+	'common/adt/bitmap.c',
+	'common/adt/hash_table.c',
+	'common/adt/list.c',
+	'common/adt/odict.c',
+	'common/printf/printf_core.c',
+	'common/stdc/calloc.c',
+	'common/stdc/ctype.c',
+	'common/stdc/mem.c',
+	'common/gsort.c',
+	'common/str.c',
+	'common/str_error.c',
+	'common/strtol.c',
+
 	'src/cap/cap.c',
 	'src/console/chardev.c',
@@ -46,8 +55,12 @@
 	'src/cpu/cpu_mask.c',
 	'src/ddi/irq.c',
-	'src/debug/debug.c',
+	'src/debug/line.c',
+	'src/debug/names.c',
 	'src/debug/panic.c',
+	'src/debug/profile.c',
+	'src/debug/sections.c',
 	'src/debug/stacktrace.c',
 	'src/debug/symtab.c',
+	'src/debug/util.c',
 	'src/ipc/event.c',
 	'src/ipc/ipc.c',
@@ -66,13 +79,8 @@
 	'src/ipc/sysipc_ops.c',
 	'src/lib/elf.c',
-	'src/lib/gsort.c',
 	'src/lib/halt.c',
 	'src/lib/mem.c',
-	'src/lib/memfnc.c',
 	'src/lib/ra.c',
 	'src/lib/rd.c',
-	'src/lib/str.c',
-	'src/lib/strtol.c',
-	'src/lib/str_error.c',
 	'src/lib/ubsan.c',
 	'src/log/log.c',
@@ -87,7 +95,5 @@
 	'src/mm/malloc.c',
 	'src/mm/reserve.c',
-	'src/preempt/preemption.c',
 	'src/printf/printf.c',
-	'src/printf/printf_core.c',
 	'src/printf/snprintf.c',
 	'src/printf/vprintf.c',
@@ -101,4 +107,5 @@
 	'src/smp/smp.c',
 	'src/synch/condvar.c',
+	'src/synch/irq_spinlock.c',
 	'src/synch/mutex.c',
 	'src/synch/semaphore.c',
Index: rnel/generic/src/adt/bitmap.c
===================================================================
--- kernel/generic/src/adt/bitmap.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,338 +1,0 @@
-/*
- * Copyright (c) 2006 Jakub Jermar
- * 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 kernel_generic_adt
- * @{
- */
-/**
- * @file
- * @brief Implementation of bitmap ADT.
- *
- * This file implements bitmap ADT and provides functions for
- * setting and clearing ranges of bits and for finding ranges
- * of unset bits.
- */
-
-#include <adt/bitmap.h>
-#include <align.h>
-#include <assert.h>
-#include <macros.h>
-#include <typedefs.h>
-
-#define ALL_ONES    0xff
-#define ALL_ZEROES  0x00
-
-/** Unchecked version of bitmap_get()
- *
- * This version of bitmap_get() does not do any boundary checks.
- *
- * @param bitmap  Bitmap to access.
- * @param element Element to access.
- *
- * @return Bit value of the element in the bitmap.
- *
- */
-static unsigned int bitmap_get_fast(bitmap_t *bitmap, size_t element)
-{
-	size_t byte = element / BITMAP_ELEMENT;
-	uint8_t mask = 1 << (element & BITMAP_REMAINER);
-
-	return !!((bitmap->bits)[byte] & mask);
-}
-
-/** Get bitmap size
- *
- * Return the size (in bytes) required for the bitmap.
- *
- * @param elements   Number bits stored in bitmap.
- *
- * @return Size (in bytes) required for the bitmap.
- *
- */
-size_t bitmap_size(size_t elements)
-{
-	size_t size = elements / BITMAP_ELEMENT;
-
-	if ((elements % BITMAP_ELEMENT) != 0)
-		size++;
-
-	return size;
-}
-
-/** Initialize bitmap.
- *
- * No portion of the bitmap is set or cleared by this function.
- *
- * @param bitmap     Bitmap structure.
- * @param elements   Number of bits stored in bitmap.
- * @param data       Address of the memory used to hold the map.
- *                   The optional 2nd level bitmap follows the 1st
- *                   level bitmap.
- *
- */
-void bitmap_initialize(bitmap_t *bitmap, size_t elements, void *data)
-{
-	bitmap->elements = elements;
-	bitmap->bits = (uint8_t *) data;
-	bitmap->next_fit = 0;
-}
-
-/** Set range of bits.
- *
- * @param bitmap Bitmap structure.
- * @param start  Starting bit.
- * @param count  Number of bits to set.
- *
- */
-void bitmap_set_range(bitmap_t *bitmap, size_t start, size_t count)
-{
-	assert(start + count <= bitmap->elements);
-
-	if (count == 0)
-		return;
-
-	size_t start_byte = start / BITMAP_ELEMENT;
-	size_t aligned_start = ALIGN_UP(start, BITMAP_ELEMENT);
-
-	/* Leading unaligned bits */
-	size_t lub = min(aligned_start - start, count);
-
-	/* Aligned middle bits */
-	size_t amb = (count > lub) ? (count - lub) : 0;
-
-	/* Trailing aligned bits */
-	size_t tab = amb % BITMAP_ELEMENT;
-
-	if (start + count < aligned_start) {
-		/* Set bits in the middle of byte. */
-		bitmap->bits[start_byte] |=
-		    ((1 << lub) - 1) << (start & BITMAP_REMAINER);
-		return;
-	}
-
-	if (lub) {
-		/* Make sure to set any leading unaligned bits. */
-		bitmap->bits[start_byte] |=
-		    ~((1 << (BITMAP_ELEMENT - lub)) - 1);
-	}
-
-	size_t i;
-
-	for (i = 0; i < amb / BITMAP_ELEMENT; i++) {
-		/* The middle bits can be set byte by byte. */
-		bitmap->bits[aligned_start / BITMAP_ELEMENT + i] =
-		    ALL_ONES;
-	}
-
-	if (tab) {
-		/* Make sure to set any trailing aligned bits. */
-		bitmap->bits[aligned_start / BITMAP_ELEMENT + i] |=
-		    (1 << tab) - 1;
-	}
-}
-
-/** Clear range of bits.
- *
- * @param bitmap Bitmap structure.
- * @param start  Starting bit.
- * @param count  Number of bits to clear.
- *
- */
-void bitmap_clear_range(bitmap_t *bitmap, size_t start, size_t count)
-{
-	assert(start + count <= bitmap->elements);
-
-	if (count == 0)
-		return;
-
-	size_t start_byte = start / BITMAP_ELEMENT;
-	size_t aligned_start = ALIGN_UP(start, BITMAP_ELEMENT);
-
-	/* Leading unaligned bits */
-	size_t lub = min(aligned_start - start, count);
-
-	/* Aligned middle bits */
-	size_t amb = (count > lub) ? (count - lub) : 0;
-
-	/* Trailing aligned bits */
-	size_t tab = amb % BITMAP_ELEMENT;
-
-	if (start + count < aligned_start) {
-		/* Set bits in the middle of byte */
-		bitmap->bits[start_byte] &=
-		    ~(((1 << lub) - 1) << (start & BITMAP_REMAINER));
-		return;
-	}
-
-	if (lub) {
-		/* Make sure to clear any leading unaligned bits. */
-		bitmap->bits[start_byte] &=
-		    (1 << (BITMAP_ELEMENT - lub)) - 1;
-	}
-
-	size_t i;
-
-	for (i = 0; i < amb / BITMAP_ELEMENT; i++) {
-		/* The middle bits can be cleared byte by byte. */
-		bitmap->bits[aligned_start / BITMAP_ELEMENT + i] =
-		    ALL_ZEROES;
-	}
-
-	if (tab) {
-		/* Make sure to clear any trailing aligned bits. */
-		bitmap->bits[aligned_start / BITMAP_ELEMENT + i] &=
-		    ~((1 << tab) - 1);
-	}
-
-	bitmap->next_fit = start_byte;
-}
-
-/** Copy portion of one bitmap into another bitmap.
- *
- * @param dst   Destination bitmap.
- * @param src   Source bitmap.
- * @param count Number of bits to copy.
- *
- */
-void bitmap_copy(bitmap_t *dst, bitmap_t *src, size_t count)
-{
-	assert(count <= dst->elements);
-	assert(count <= src->elements);
-
-	size_t i;
-
-	for (i = 0; i < count / BITMAP_ELEMENT; i++)
-		dst->bits[i] = src->bits[i];
-
-	if (count % BITMAP_ELEMENT) {
-		bitmap_clear_range(dst, i * BITMAP_ELEMENT,
-		    count % BITMAP_ELEMENT);
-		dst->bits[i] |= src->bits[i] &
-		    ((1 << (count % BITMAP_ELEMENT)) - 1);
-	}
-}
-
-static int constraint_satisfy(size_t index, size_t base, size_t constraint)
-{
-	return (((base + index) & constraint) == 0);
-}
-
-/** Find a continuous zero bit range
- *
- * Find a continuous zero bit range in the bitmap. The address
- * computed as the sum of the index of the first zero bit and
- * the base argument needs to be compliant with the constraint
- * (those bits that are set in the constraint cannot be set in
- * the address).
- *
- * If the index argument is non-NULL, the continuous zero range
- * is set and the index of the first bit is stored to index.
- * Otherwise the bitmap stays untouched.
- *
- * @param bitmap     Bitmap structure.
- * @param count      Number of continuous zero bits to find.
- * @param base       Address of the first bit in the bitmap.
- * @param prefered   Prefered address to start searching from.
- * @param constraint Constraint for the address of the first zero bit.
- * @param index      Place to store the index of the first zero
- *                   bit. Can be NULL (in which case the bitmap
- *                   is not modified).
- *
- * @return Non-zero if a continuous range of zero bits satisfying
- *         the constraint has been found.
- * @return Zero otherwise.
- *
- */
-bool bitmap_allocate_range(bitmap_t *bitmap, size_t count, size_t base,
-    size_t prefered, size_t constraint, size_t *index)
-{
-	if (count == 0)
-		return false;
-
-	size_t size = bitmap_size(bitmap->elements);
-	size_t next_fit = bitmap->next_fit;
-
-	/*
-	 * Adjust the next-fit value according to the address
-	 * the caller prefers to start the search at.
-	 */
-	if ((prefered > base) && (prefered < base + bitmap->elements)) {
-		size_t prefered_fit = (prefered - base) / BITMAP_ELEMENT;
-
-		if (prefered_fit > next_fit)
-			next_fit = prefered_fit;
-	}
-
-	for (size_t pos = 0; pos < size; pos++) {
-		size_t byte = (next_fit + pos) % size;
-
-		/* Skip if the current byte has all bits set */
-		if (bitmap->bits[byte] == ALL_ONES)
-			continue;
-
-		size_t byte_bit = byte * BITMAP_ELEMENT;
-
-		for (size_t bit = 0; bit < BITMAP_ELEMENT; bit++) {
-			size_t i = byte_bit + bit;
-
-			if (i >= bitmap->elements)
-				break;
-
-			if (!constraint_satisfy(i, base, constraint))
-				continue;
-
-			if (!bitmap_get_fast(bitmap, i)) {
-				size_t continuous = 1;
-
-				for (size_t j = 1; j < count; j++) {
-					if ((i + j < bitmap->elements) &&
-					    (!bitmap_get_fast(bitmap, i + j)))
-						continuous++;
-					else
-						break;
-				}
-
-				if (continuous == count) {
-					if (index != NULL) {
-						bitmap_set_range(bitmap, i, count);
-						bitmap->next_fit = i / BITMAP_ELEMENT;
-						*index = i;
-					}
-
-					return true;
-				} else
-					i += continuous;
-			}
-		}
-	}
-
-	return false;
-}
-
-/** @}
- */
Index: rnel/generic/src/adt/hash_table.c
===================================================================
--- kernel/generic/src/adt/hash_table.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,471 +1,0 @@
-/*
- * Copyright (c) 2008 Jakub Jermar
- * Copyright (c) 2012 Adam Hraska
- *
- * 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 kernel_generic
- * @{
- */
-/** @file
- */
-
-/*
- * This is an implementation of a generic resizable chained hash table.
- *
- * The table grows to 2*n+1 buckets each time, starting at n == 89,
- * per Thomas Wang's recommendation:
- * http://www.concentric.net/~Ttwang/tech/hashsize.htm
- *
- * This policy produces prime table sizes for the first five resizes
- * and generally produces table sizes which are either prime or
- * have fairly large (prime/odd) divisors. Having a prime table size
- * mitigates the use of suboptimal hash functions and distributes
- * items over the whole table.
- */
-
-#include <adt/hash_table.h>
-#include <adt/list.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <str.h>
-
-/* Optimal initial bucket count. See comment above. */
-#define HT_MIN_BUCKETS  89
-/* The table is resized when the average load per bucket exceeds this number. */
-#define HT_MAX_LOAD     2
-
-static size_t round_up_size(size_t);
-static bool alloc_table(size_t, list_t **);
-static void clear_items(hash_table_t *);
-static void resize(hash_table_t *, size_t);
-static void grow_if_needed(hash_table_t *);
-static void shrink_if_needed(hash_table_t *);
-
-/* Dummy do nothing callback to invoke in place of remove_callback == NULL. */
-static void nop_remove_callback(ht_link_t *item)
-{
-	/* no-op */
-}
-
-/** Create chained hash table.
- *
- * @param h        Hash table structure. Will be initialized by this call.
- * @param init_size Initial desired number of hash table buckets. Pass zero
- *                 if you want the default initial size.
- * @param max_load The table is resized when the average load per bucket
- *                 exceeds this number. Pass zero if you want the default.
- * @param op       Hash table operations structure. remove_callback()
- *                 is optional and can be NULL if no action is to be taken
- *                 upon removal. equal() is optional if and only if
- *                 hash_table_insert_unique() will never be invoked.
- *                 All other operations are mandatory.
- *
- * @return True on success
- *
- */
-bool hash_table_create(hash_table_t *h, size_t init_size, size_t max_load,
-    hash_table_ops_t *op)
-{
-	assert(h);
-	assert(op && op->hash && op->key_hash && op->key_equal);
-
-	/* Check for compulsory ops. */
-	if (!op || !op->hash || !op->key_hash || !op->key_equal)
-		return false;
-
-	h->bucket_cnt = round_up_size(init_size);
-
-	if (!alloc_table(h->bucket_cnt, &h->bucket))
-		return false;
-
-	h->max_load = (max_load == 0) ? HT_MAX_LOAD : max_load;
-	h->item_cnt = 0;
-	h->op = op;
-	h->full_item_cnt = h->max_load * h->bucket_cnt;
-	h->apply_ongoing = false;
-
-	if (h->op->remove_callback == NULL) {
-		h->op->remove_callback = nop_remove_callback;
-	}
-
-	return true;
-}
-
-/** Destroy a hash table instance.
- *
- * @param h Hash table to be destroyed.
- *
- */
-void hash_table_destroy(hash_table_t *h)
-{
-	assert(h && h->bucket);
-	assert(!h->apply_ongoing);
-
-	clear_items(h);
-
-	free(h->bucket);
-
-	h->bucket = NULL;
-	h->bucket_cnt = 0;
-}
-
-/** Returns true if there are no items in the table. */
-bool hash_table_empty(hash_table_t *h)
-{
-	assert(h && h->bucket);
-	return h->item_cnt == 0;
-}
-
-/** Returns the number of items in the table. */
-size_t hash_table_size(hash_table_t *h)
-{
-	assert(h && h->bucket);
-	return h->item_cnt;
-}
-
-/** Remove all elements from the hash table
- *
- * @param h Hash table to be cleared
- */
-void hash_table_clear(hash_table_t *h)
-{
-	assert(h && h->bucket);
-	assert(!h->apply_ongoing);
-
-	clear_items(h);
-
-	/* Shrink the table to its minimum size if possible. */
-	if (HT_MIN_BUCKETS < h->bucket_cnt) {
-		resize(h, HT_MIN_BUCKETS);
-	}
-}
-
-/** Unlinks and removes all items but does not resize. */
-static void clear_items(hash_table_t *h)
-{
-	if (h->item_cnt == 0)
-		return;
-
-	for (size_t idx = 0; idx < h->bucket_cnt; ++idx) {
-		list_foreach_safe(h->bucket[idx], cur, next) {
-			assert(cur);
-			ht_link_t *cur_link = member_to_inst(cur, ht_link_t, link);
-
-			list_remove(cur);
-			h->op->remove_callback(cur_link);
-		}
-	}
-
-	h->item_cnt = 0;
-}
-
-/** Insert item into a hash table.
- *
- * @param h    Hash table.
- * @param item Item to be inserted into the hash table.
- */
-void hash_table_insert(hash_table_t *h, ht_link_t *item)
-{
-	assert(item);
-	assert(h && h->bucket);
-	assert(!h->apply_ongoing);
-
-	size_t idx = h->op->hash(item) % h->bucket_cnt;
-
-	list_append(&item->link, &h->bucket[idx]);
-	++h->item_cnt;
-	grow_if_needed(h);
-}
-
-/** Insert item into a hash table if not already present.
- *
- * @param h    Hash table.
- * @param item Item to be inserted into the hash table.
- *
- * @return False if such an item had already been inserted.
- * @return True if the inserted item was the only item with such a lookup key.
- */
-bool hash_table_insert_unique(hash_table_t *h, ht_link_t *item)
-{
-	assert(item);
-	assert(h && h->bucket && h->bucket_cnt);
-	assert(h->op && h->op->hash && h->op->equal);
-	assert(!h->apply_ongoing);
-
-	size_t idx = h->op->hash(item) % h->bucket_cnt;
-
-	/* Check for duplicates. */
-	list_foreach(h->bucket[idx], link, ht_link_t, cur_link) {
-		/*
-		 * We could filter out items using their hashes first, but
-		 * calling equal() might very well be just as fast.
-		 */
-		if (h->op->equal(cur_link, item))
-			return false;
-	}
-
-	list_append(&item->link, &h->bucket[idx]);
-	++h->item_cnt;
-	grow_if_needed(h);
-
-	return true;
-}
-
-/** Search hash table for an item matching keys.
- *
- * @param h   Hash table.
- * @param key Array of all keys needed to compute hash index.
- *
- * @return Matching item on success, NULL if there is no such item.
- *
- */
-ht_link_t *hash_table_find(const hash_table_t *h, const void *key)
-{
-	assert(h && h->bucket);
-
-	size_t idx = h->op->key_hash(key) % h->bucket_cnt;
-
-	list_foreach(h->bucket[idx], link, ht_link_t, cur_link) {
-		/*
-		 * Is this is the item we are looking for? We could have first
-		 * checked if the hashes match but op->key_equal() may very well be
-		 * just as fast as op->hash().
-		 */
-		if (h->op->key_equal(key, cur_link)) {
-			return cur_link;
-		}
-	}
-
-	return NULL;
-}
-
-/** Find the next item equal to item. */
-ht_link_t *
-hash_table_find_next(const hash_table_t *h, ht_link_t *first, ht_link_t *item)
-{
-	assert(item);
-	assert(h && h->bucket);
-
-	size_t idx = h->op->hash(item) % h->bucket_cnt;
-
-	/* Traverse the circular list until we reach the starting item again. */
-	for (link_t *cur = item->link.next; cur != &first->link;
-	    cur = cur->next) {
-		assert(cur);
-
-		if (cur == &h->bucket[idx].head)
-			continue;
-
-		ht_link_t *cur_link = member_to_inst(cur, ht_link_t, link);
-		/*
-		 * Is this is the item we are looking for? We could have first
-		 * checked if the hashes match but op->equal() may very well be
-		 * just as fast as op->hash().
-		 */
-		if (h->op->equal(cur_link, item)) {
-			return cur_link;
-		}
-	}
-
-	return NULL;
-}
-
-/** Remove all matching items from hash table.
- *
- * For each removed item, h->remove_callback() is called.
- *
- * @param h    Hash table.
- * @param key  Array of keys that will be compared against items of
- *             the hash table.
- *
- * @return Returns the number of removed items.
- */
-size_t hash_table_remove(hash_table_t *h, const void *key)
-{
-	assert(h && h->bucket);
-	assert(!h->apply_ongoing);
-
-	size_t idx = h->op->key_hash(key) % h->bucket_cnt;
-
-	size_t removed = 0;
-
-	list_foreach_safe(h->bucket[idx], cur, next) {
-		ht_link_t *cur_link = member_to_inst(cur, ht_link_t, link);
-
-		if (h->op->key_equal(key, cur_link)) {
-			++removed;
-			list_remove(cur);
-			h->op->remove_callback(cur_link);
-		}
-	}
-
-	h->item_cnt -= removed;
-	shrink_if_needed(h);
-
-	return removed;
-}
-
-/** Removes an item already present in the table. The item must be in the table. */
-void hash_table_remove_item(hash_table_t *h, ht_link_t *item)
-{
-	assert(item);
-	assert(h && h->bucket);
-	assert(link_in_use(&item->link));
-
-	list_remove(&item->link);
-	--h->item_cnt;
-	h->op->remove_callback(item);
-	shrink_if_needed(h);
-}
-
-/** Apply function to all items in hash table.
- *
- * @param h   Hash table.
- * @param f   Function to be applied. Return false if no more items
- *            should be visited. The functor may only delete the supplied
- *            item. It must not delete the successor of the item passed
- *            in the first argument.
- * @param arg Argument to be passed to the function.
- */
-void hash_table_apply(hash_table_t *h, bool (*f)(ht_link_t *, void *), void *arg)
-{
-	assert(f);
-	assert(h && h->bucket);
-
-	if (h->item_cnt == 0)
-		return;
-
-	h->apply_ongoing = true;
-
-	for (size_t idx = 0; idx < h->bucket_cnt; ++idx) {
-		list_foreach_safe(h->bucket[idx], cur, next) {
-			ht_link_t *cur_link = member_to_inst(cur, ht_link_t, link);
-			/*
-			 * The next pointer had already been saved. f() may safely
-			 * delete cur (but not next!).
-			 */
-			if (!f(cur_link, arg))
-				goto out;
-		}
-	}
-out:
-	h->apply_ongoing = false;
-
-	shrink_if_needed(h);
-	grow_if_needed(h);
-}
-
-/** Rounds up size to the nearest suitable table size. */
-static size_t round_up_size(size_t size)
-{
-	size_t rounded_size = HT_MIN_BUCKETS;
-
-	while (rounded_size < size) {
-		rounded_size = 2 * rounded_size + 1;
-	}
-
-	return rounded_size;
-}
-
-/** Allocates and initializes the desired number of buckets. True if successful. */
-static bool alloc_table(size_t bucket_cnt, list_t **pbuckets)
-{
-	assert(pbuckets && HT_MIN_BUCKETS <= bucket_cnt);
-
-	list_t *buckets = malloc(bucket_cnt * sizeof(list_t));
-	if (!buckets)
-		return false;
-
-	for (size_t i = 0; i < bucket_cnt; i++)
-		list_initialize(&buckets[i]);
-
-	*pbuckets = buckets;
-	return true;
-}
-
-/** Shrinks the table if the table is only sparely populated. */
-static inline void shrink_if_needed(hash_table_t *h)
-{
-	if (h->item_cnt <= h->full_item_cnt / 4 && HT_MIN_BUCKETS < h->bucket_cnt) {
-		/*
-		 * Keep the bucket_cnt odd (possibly also prime).
-		 * Shrink from 2n + 1 to n. Integer division discards the +1.
-		 */
-		size_t new_bucket_cnt = h->bucket_cnt / 2;
-		resize(h, new_bucket_cnt);
-	}
-}
-
-/** Grows the table if table load exceeds the maximum allowed. */
-static inline void grow_if_needed(hash_table_t *h)
-{
-	/* Grow the table if the average bucket load exceeds the maximum. */
-	if (h->full_item_cnt < h->item_cnt) {
-		/* Keep the bucket_cnt odd (possibly also prime). */
-		size_t new_bucket_cnt = 2 * h->bucket_cnt + 1;
-		resize(h, new_bucket_cnt);
-	}
-}
-
-/** Allocates and rehashes items to a new table. Frees the old table. */
-static void resize(hash_table_t *h, size_t new_bucket_cnt)
-{
-	assert(h && h->bucket);
-	assert(HT_MIN_BUCKETS <= new_bucket_cnt);
-
-	/* We are traversing the table and resizing would mess up the buckets. */
-	if (h->apply_ongoing)
-		return;
-
-	list_t *new_buckets;
-
-	/* Leave the table as is if we cannot resize. */
-	if (!alloc_table(new_bucket_cnt, &new_buckets))
-		return;
-
-	if (0 < h->item_cnt) {
-		/* Rehash all the items to the new table. */
-		for (size_t old_idx = 0; old_idx < h->bucket_cnt; ++old_idx) {
-			list_foreach_safe(h->bucket[old_idx], cur, next) {
-				ht_link_t *cur_link = member_to_inst(cur, ht_link_t, link);
-
-				size_t new_idx = h->op->hash(cur_link) % new_bucket_cnt;
-				list_remove(cur);
-				list_append(cur, &new_buckets[new_idx]);
-			}
-		}
-	}
-
-	free(h->bucket);
-	h->bucket = new_buckets;
-	h->bucket_cnt = new_bucket_cnt;
-	h->full_item_cnt = h->max_load * h->bucket_cnt;
-}
-
-/** @}
- */
Index: rnel/generic/src/adt/list.c
===================================================================
--- kernel/generic/src/adt/list.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,115 +1,0 @@
-/*
- * Copyright (c) 2004 Jakub Jermar
- * 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 kernel_generic_adt
- * @{
- */
-
-/**
- * @file
- * @brief	Functions completing doubly linked circular list implementation.
- *
- * This file contains some of the functions implementing doubly linked circular lists.
- * However, this ADT is mostly implemented in @ref list.h.
- */
-
-#include <adt/list.h>
-
-/** Check for membership
- *
- * Check whether link is contained in a list.
- * Membership is defined as pointer equivalence.
- *
- * @param link	Item to look for.
- * @param list	List to look in.
- *
- * @return true if link is contained in list, false otherwise.
- *
- */
-bool list_member(const link_t *link, const list_t *list)
-{
-	bool found = false;
-	link_t *hlp = list->head.next;
-
-	while (hlp != &list->head) {
-		if (hlp == link) {
-			found = true;
-			break;
-		}
-		hlp = hlp->next;
-	}
-
-	return found;
-}
-
-/** Moves items of one list into another after the specified item.
- *
- * Inserts all items of @a list after item at @a pos in another list.
- * Both lists may be empty.
- *
- * @param list Source list to move after pos. Empty afterwards.
- * @param pos Source items will be placed after this item.
- */
-void list_splice(list_t *list, link_t *pos)
-{
-	if (list_empty(list))
-		return;
-
-	/* Attach list to destination. */
-	list->head.next->prev = pos;
-	list->head.prev->next = pos->next;
-
-	/* Link destination list to the added list. */
-	pos->next->prev = list->head.prev;
-	pos->next = list->head.next;
-
-	list_initialize(list);
-}
-
-/** Count list items
- *
- * Return the number of items in the list.
- *
- * @param list		List to count.
- * @return		Number of items in the list.
- */
-unsigned long list_count(const list_t *list)
-{
-	unsigned long count = 0;
-
-	link_t *link = list_first(list);
-	while (link != NULL) {
-		count++;
-		link = list_next(link, list);
-	}
-
-	return count;
-}
-
-/** @}
- */
Index: rnel/generic/src/adt/odict.c
===================================================================
--- kernel/generic/src/adt/odict.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,1144 +1,0 @@
-/*
- * Copyright (c) 2016 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 kernel_generic_adt
- * @{
- */
-
-/** @file Ordered dictionary.
- *
- * Implementation based on red-black trees.
- * Note that non-data ('leaf') nodes are implemented as NULLs, not
- * as actual nodes.
- */
-
-#include <adt/list.h>
-#include <adt/odict.h>
-#include <assert.h>
-#include <errno.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdio.h>
-
-static void odict_pgu(odlink_t *, odlink_t **, odict_child_sel_t *,
-    odlink_t **, odict_child_sel_t *, odlink_t **);
-
-static void odict_rotate_left(odlink_t *);
-static void odict_rotate_right(odlink_t *);
-static void odict_swap_node(odlink_t *, odlink_t *);
-static void odict_replace_subtree(odlink_t *, odlink_t *);
-static void odict_unlink(odlink_t *);
-static void odict_link_child_a(odlink_t *, odlink_t *);
-static void odict_link_child_b(odlink_t *, odlink_t *);
-static void odict_sibling(odlink_t *, odlink_t *, odict_child_sel_t *,
-    odlink_t **);
-static odlink_t *odict_search_start_node(odict_t *, void *, odlink_t *);
-
-/** Print subtree.
- *
- * Print subtree rooted at @a cur
- *
- * @param cur Root of tree to print
- */
-static void odict_print_tree(odlink_t *cur)
-{
-	if (cur == NULL) {
-		printf("0");
-		return;
-	}
-
-	printf("[%p/%c", cur, cur->color == odc_red ? 'r' : 'b');
-	if (cur->a != NULL || cur->b != NULL) {
-		printf(" ");
-		odict_print_tree(cur->a);
-		printf(" ");
-		odict_print_tree(cur->b);
-	}
-	printf("]");
-}
-
-/** Validate ordered dictionary subtree.
- *
- * Verify that red-black tree properties are satisfied.
- *
- * @param cur Root of tree to verify
- * @param rbd Place to store black depth of the subtree
- *
- * @return EOK on success, EINVAL on failure
- */
-static errno_t odict_validate_tree(odlink_t *cur, int *rbd)
-{
-	errno_t rc;
-	int bd_a, bd_b;
-	int cur_d;
-
-	if (cur->up == NULL) {
-		/* Verify root pointer */
-		if (cur->odict->root != cur) {
-			printf("cur->up == NULL and yet cur != root\n");
-			return EINVAL;
-		}
-
-		/* Verify root color */
-		if (cur->color != odc_black) {
-			printf("Root is not black\n");
-			return EINVAL;
-		}
-	}
-
-	if (cur->a != NULL) {
-		/* Verify symmetry of a - up links */
-		if (cur->a->up != cur) {
-			printf("cur->a->up != cur\n");
-			return EINVAL;
-		}
-
-		/* Verify that if a node is red, its left child is red */
-		if (cur->a->color == odc_red && cur->color == odc_red) {
-			printf("cur->a is red, cur is red\n");
-			return EINVAL;
-		}
-
-		/* Recurse to left child */
-		rc = odict_validate_tree(cur->a, &bd_a);
-		if (rc != EOK)
-			return rc;
-	} else {
-		bd_a = -1;
-	}
-
-	if (cur->b != NULL) {
-		/* Verify symmetry of b - up links */
-		if (cur->b->up != cur) {
-			printf("cur->b->up != cur\n");
-			return EINVAL;
-		}
-
-		/* Verify that if a node is red, its right child is red */
-		if (cur->b->color == odc_red && cur->color == odc_red) {
-			printf("cur->b is red, cur is red\n");
-			return EINVAL;
-		}
-
-		/* Recurse to right child */
-		rc = odict_validate_tree(cur->b, &bd_b);
-		if (rc != EOK)
-			return rc;
-	} else {
-		bd_b = -1;
-	}
-
-	/* Verify that black depths of both children are equal */
-	if (bd_a >= 0 && bd_b >= 0) {
-		if (bd_a != bd_b) {
-			printf("Black depth %d != %d\n", bd_a, bd_b);
-			return EINVAL;
-		}
-	}
-
-	cur_d = cur->color == odc_black ? 1 : 0;
-	if (bd_a >= 0)
-		*rbd = bd_a + cur_d;
-	else if (bd_b >= 0)
-		*rbd = bd_b + cur_d;
-	else
-		*rbd = cur_d;
-
-	return EOK;
-}
-
-/** Validate ordered dictionary properties.
- *
- * @param odict Ordered dictionary
- */
-errno_t odict_validate(odict_t *odict)
-{
-	int bd;
-	errno_t rc;
-
-	if (odict->root == NULL)
-		return EOK;
-
-	rc = odict_validate_tree(odict->root, &bd);
-	if (rc != EOK)
-		odict_print_tree(odict->root);
-
-	return rc;
-}
-
-/** Initialize ordered dictionary.
- *
- * @param odict Ordered dictionary
- * @param getkey Function to get key
- * @param cmp Function to compare entries
- */
-void odict_initialize(odict_t *odict, odgetkey_t getkey, odcmp_t cmp)
-{
-	odict->root = NULL;
-	list_initialize(&odict->entries);
-	odict->getkey = getkey;
-	odict->cmp = cmp;
-}
-
-/** Finalize ordered dictionary.
- *
- * @param odict Ordered dictionary (must be empty)
- */
-void odict_finalize(odict_t *odict)
-{
-	assert(odict->root == NULL);
-}
-
-/** Initialize ordered dictionary link.
- *
- * @param odlink Ordered dictionary link
- */
-void odlink_initialize(odlink_t *odlink)
-{
-	odlink->odict = NULL;
-	odlink->up = NULL;
-	odlink->a = NULL;
-	odlink->b = NULL;
-	link_initialize(&odlink->lentries);
-}
-
-/** Insert entry in ordered dictionary.
- *
- * Insert entry in ordered dictionary, placing it after other entries
- * with the same key.
- *
- * @param odlink New entry
- * @param odict Ordered dictionary
- * @param hint An entry that might be near the new entry or @c NULL
- */
-void odict_insert(odlink_t *odlink, odict_t *odict, odlink_t *hint)
-{
-	int d;
-	odlink_t *cur;
-	odlink_t *p;
-	odlink_t *g;
-	odlink_t *u;
-	odict_child_sel_t pcs, gcs;
-
-	assert(!odlink_used(odlink));
-
-	if (odict->root == NULL) {
-		/* odlink is the root node */
-		odict->root = odlink;
-		odlink->odict = odict;
-		odlink->color = odc_black;
-		list_append(&odlink->lentries, &odict->entries);
-		return;
-	}
-
-	cur = odict_search_start_node(odict, odict->getkey(odlink), hint);
-	while (true) {
-		d = odict->cmp(odict->getkey(odlink), odict->getkey(cur));
-		if (d < 0) {
-			if (cur->a == NULL) {
-				odict_link_child_a(odlink, cur);
-				break;
-			}
-			cur = cur->a;
-		} else {
-			if (cur->b == NULL) {
-				odict_link_child_b(odlink, cur);
-				break;
-			}
-			cur = cur->b;
-		}
-	}
-
-	odlink->color = odc_red;
-
-	while (true) {
-		/* Fix up odlink and its parent potentially being red */
-		if (odlink->up == NULL) {
-			odlink->color = odc_black;
-			break;
-		}
-
-		if (odlink->up->color == odc_black)
-			break;
-
-		/* Get parent, grandparent, uncle */
-		odict_pgu(odlink, &p, &pcs, &g, &gcs, &u);
-
-		if (g == NULL) {
-			p->color = odc_black;
-			break;
-		}
-
-		if (p->color == odc_red && u != NULL && u->color == odc_red) {
-			/* Parent and uncle are both red */
-			p->color = odc_black;
-			u->color = odc_black;
-			g->color = odc_red;
-			odlink = g;
-			continue;
-		}
-
-		/* Parent is red but uncle is black, odlink-P-G is trans */
-		if (pcs != gcs) {
-			if (gcs == odcs_a) {
-				/* odlink is right child of P */
-				/* P is left child of G */
-				odict_rotate_left(p);
-			} else {
-				/* odlink is left child of P */
-				/* P is right child of G */
-				odict_rotate_right(p);
-			}
-
-			odlink = p;
-			odict_pgu(odlink, &p, &pcs, &g, &gcs, &u);
-		}
-
-		/* odlink-P-G is now cis */
-		assert(pcs == gcs);
-		if (pcs == odcs_a) {
-			/* odlink is left child of P */
-			/* P is left child of G */
-			odict_rotate_right(g);
-		} else {
-			/* odlink is right child of P */
-			/* P is right child of G */
-			odict_rotate_left(g);
-		}
-
-		p->color = odc_black;
-		g->color = odc_red;
-		break;
-	}
-}
-
-/** Remove entry from ordered dictionary.
- *
- * @param odlink Ordered dictionary link
- */
-void odict_remove(odlink_t *odlink)
-{
-	odlink_t *n;
-	odlink_t *c;
-	odlink_t *p;
-	odlink_t *s;
-	odlink_t *sc, *st;
-	odict_child_sel_t pcs;
-
-	if (odlink->a != NULL && odlink->b != NULL) {
-		n = odict_next(odlink, odlink->odict);
-		assert(n != NULL);
-
-		odict_swap_node(odlink, n);
-	}
-
-	/* odlink has at most one child */
-	if (odlink->a != NULL) {
-		assert(odlink->b == NULL);
-		c = odlink->a;
-	} else {
-		c = odlink->b;
-	}
-
-	if (odlink->color == odc_red) {
-		/* We can remove it harmlessly */
-		assert(c == NULL);
-		odict_unlink(odlink);
-		return;
-	}
-
-	/* odlink->color == odc_black */
-	if (c != NULL && c->color == odc_red) {
-		/* Child is red: swap colors of S and C */
-		c->color = odc_black;
-		odict_replace_subtree(c, odlink);
-		odlink->up = odlink->a = odlink->b = NULL;
-		odlink->odict = NULL;
-		list_remove(&odlink->lentries);
-		return;
-	}
-
-	/* There cannot be one black child */
-	assert(c == NULL);
-
-	n = NULL;
-	p = odlink->up;
-	odict_unlink(odlink);
-	/* We removed one black node, creating imbalance */
-again:
-	/* Case 1: N is the new root */
-	if (p == NULL)
-		return;
-
-	odict_sibling(n, p, &pcs, &s);
-
-	/* Paths through N have one less black node than paths through S */
-
-	/* Case 2: S is red */
-	if (s->color == odc_red) {
-		assert(p->color == odc_black);
-		p->color = odc_red;
-		s->color = odc_black;
-		if (n == p->a)
-			odict_rotate_left(p);
-		else
-			odict_rotate_right(p);
-		odict_sibling(n, p, &pcs, &s);
-		/* Now S is black */
-		assert(s->color == odc_black);
-	}
-
-	/* Case 3: P, S and S's children are black */
-	if (p->color == odc_black &&
-	    s->color == odc_black &&
-	    (s->a == NULL || s->a->color == odc_black) &&
-	    (s->b == NULL || s->b->color == odc_black)) {
-		/*
-		 * Changing S to red means all paths through S or N have one
-		 * less black node than they should. So redo the same for P.
-		 */
-		s->color = odc_red;
-		n = p;
-		p = n->up;
-		goto again;
-	}
-
-	/* Case 4: P is red, S and S's children are black */
-	if (p->color == odc_red &&
-	    s->color == odc_black &&
-	    (s->a == NULL || s->a->color == odc_black) &&
-	    (s->b == NULL || s->b->color == odc_black)) {
-		/* Swap colors of S and P */
-		s->color = odc_red;
-		p->color = odc_black;
-		return;
-	}
-
-	/* N is the left child */
-	if (pcs == odcs_a) {
-		st = s->a;
-		sc = s->b;
-	} else {
-		st = s->b;
-		sc = s->a;
-	}
-
-	/* Case 5: S is black and S's trans child is red, S's cis child is black */
-	if (s->color == odc_black &&
-	    (st != NULL && st->color == odc_red) &&
-	    (sc == NULL || sc->color == odc_black)) {
-		/* N is the left child */
-		if (pcs == odcs_a)
-			odict_rotate_right(s);
-		else
-			odict_rotate_left(s);
-		s->color = odc_red;
-		s->up->color = odc_black;
-		/* Now N has a black sibling whose cis child is red */
-		odict_sibling(n, p, &pcs, &s);
-		/* N is the left child */
-		if (pcs == odcs_a) {
-			st = s->a;
-			sc = s->b;
-		} else {
-			st = s->b;
-			sc = s->a;
-		}
-	}
-
-	/* Case 6: S is black, S's cis child is red */
-	assert(s->color == odc_black);
-	assert(sc != NULL);
-	assert(sc->color == odc_red);
-
-	if (pcs == odcs_a)
-		odict_rotate_left(p);
-	else
-		odict_rotate_right(p);
-
-	s->color = p->color;
-	p->color = odc_black;
-	sc->color = odc_black;
-}
-
-/** Update dictionary after entry key has been changed.
- *
- * After the caller modifies the key of an entry, they need to call
- * this function so that the dictionary can update itself accordingly.
- *
- * @param odlink Ordered dictionary entry
- * @param odict Ordered dictionary
- */
-void odict_key_update(odlink_t *odlink, odict_t *odict)
-{
-	odlink_t *n;
-
-	n = odict_next(odlink, odict);
-	odict_remove(odlink);
-	odict_insert(odlink, odict, n);
-}
-
-/** Return true if entry is in a dictionary.
- *
- * @param odlink Ordered dictionary entry
- * @return @c true if entry is in a dictionary, @c false otherwise
- */
-bool odlink_used(odlink_t *odlink)
-{
-	return odlink->odict != NULL;
-}
-
-/** Return true if ordered dictionary is empty.
- *
- * @param odict Ordered dictionary
- * @return @c true if @a odict is emptry, @c false otherwise
- */
-bool odict_empty(odict_t *odict)
-{
-	return odict->root == NULL;
-}
-
-/** Return the number of entries in @a odict.
- *
- * @param odict Ordered dictionary
- */
-unsigned long odict_count(odict_t *odict)
-{
-	unsigned long cnt;
-	odlink_t *cur;
-
-	cnt = 0;
-	cur = odict_first(odict);
-	while (cur != NULL) {
-		++cnt;
-		cur = odict_next(cur, odict);
-	}
-
-	return cnt;
-}
-
-/** Return first entry in a list or @c NULL if list is empty.
- *
- * @param odict Ordered dictionary
- * @return First entry
- */
-odlink_t *odict_first(odict_t *odict)
-{
-	link_t *link;
-
-	link = list_first(&odict->entries);
-	if (link == NULL)
-		return NULL;
-
-	return list_get_instance(link, odlink_t, lentries);
-}
-
-/** Return last entry in a list or @c NULL if list is empty
- *
- * @param odict Ordered dictionary
- * @return Last entry
- */
-odlink_t *odict_last(odict_t *odict)
-{
-	link_t *link;
-
-	link = list_last(&odict->entries);
-	if (link == NULL)
-		return NULL;
-
-	return list_get_instance(link, odlink_t, lentries);
-}
-
-/** Return previous entry in list or @c NULL if @a link is the first one.
- *
- * @param odlink Entry
- * @param odict Ordered dictionary
- * @return Previous entry
- */
-odlink_t *odict_prev(odlink_t *odlink, odict_t *odict)
-{
-	link_t *link;
-
-	link = list_prev(&odlink->lentries, &odlink->odict->entries);
-	if (link == NULL)
-		return NULL;
-
-	return list_get_instance(link, odlink_t, lentries);
-}
-
-/** Return next entry in dictionary or @c NULL if @a odlink is the last one
- *
- * @param odlink Entry
- * @param odict Ordered dictionary
- * @return Next entry
- */
-odlink_t *odict_next(odlink_t *odlink, odict_t *odict)
-{
-	link_t *link;
-
-	link = list_next(&odlink->lentries, &odlink->odict->entries);
-	if (link == NULL)
-		return NULL;
-
-	return list_get_instance(link, odlink_t, lentries);
-}
-
-/** Find first entry whose key is equal to @a key/
- *
- * @param odict Ordered dictionary
- * @param key Key
- * @param hint Nearby entry
- * @return Pointer to entry on success, @c NULL on failure
- */
-odlink_t *odict_find_eq(odict_t *odict, void *key, odlink_t *hint)
-{
-	odlink_t *geq;
-
-	geq = odict_find_geq(odict, key, hint);
-	if (geq == NULL)
-		return NULL;
-
-	if (odict->cmp(odict->getkey(geq), key) == 0)
-		return geq;
-	else
-		return NULL;
-}
-
-/** Find last entry whose key is equal to @a key/
- *
- * @param odict Ordered dictionary
- * @param key Key
- * @param hint Nearby entry
- * @return Pointer to entry on success, @c NULL on failure
- */
-odlink_t *odict_find_eq_last(odict_t *odict, void *key, odlink_t *hint)
-{
-	odlink_t *leq;
-
-	leq = odict_find_leq(odict, key, hint);
-	if (leq == NULL)
-		return NULL;
-
-	if (odict->cmp(odict->getkey(leq), key) == 0)
-		return leq;
-	else
-		return NULL;
-}
-
-/** Find first entry whose key is greater than or equal to @a key
- *
- * @param odict Ordered dictionary
- * @param key Key
- * @param hint Nearby entry
- * @return Pointer to entry on success, @c NULL on failure
- */
-odlink_t *odict_find_geq(odict_t *odict, void *key, odlink_t *hint)
-{
-	odlink_t *cur;
-	odlink_t *next;
-	int d;
-
-	cur = odict_search_start_node(odict, key, hint);
-	if (cur == NULL)
-		return NULL;
-
-	while (true) {
-		d = odict->cmp(odict->getkey(cur), key);
-		if (d >= 0)
-			next = cur->a;
-		else
-			next = cur->b;
-
-		if (next == NULL)
-			break;
-
-		cur = next;
-	}
-
-	if (d >= 0) {
-		return cur;
-	} else {
-		return odict_next(cur, odict);
-	}
-}
-
-/** Find last entry whose key is greater than @a key.
- *
- * @param odict Ordered dictionary
- * @param key Key
- * @param hint Nearby entry
- * @return Pointer to entry on success, @c NULL on failure
- */
-odlink_t *odict_find_gt(odict_t *odict, void *key, odlink_t *hint)
-{
-	odlink_t *leq;
-
-	leq = odict_find_leq(odict, key, hint);
-	if (leq != NULL)
-		return odict_next(leq, odict);
-	else
-		return odict_first(odict);
-}
-
-/** Find last entry whose key is less than or equal to @a key
- *
- * @param odict Ordered dictionary
- * @param key Key
- * @param hint Nearby entry
- * @return Pointer to entry on success, @c NULL on failure
- */
-odlink_t *odict_find_leq(odict_t *odict, void *key, odlink_t *hint)
-{
-	odlink_t *cur;
-	odlink_t *next;
-	int d;
-
-	cur = odict_search_start_node(odict, key, hint);
-	if (cur == NULL)
-		return NULL;
-
-	while (true) {
-		d = odict->cmp(key, odict->getkey(cur));
-		if (d >= 0)
-			next = cur->b;
-		else
-			next = cur->a;
-
-		if (next == NULL)
-			break;
-
-		cur = next;
-	}
-
-	if (d >= 0) {
-		return cur;
-	} else {
-		return odict_prev(cur, odict);
-	}
-}
-
-/** Find last entry whose key is less than @a key.
- *
- * @param odict Ordered dictionary
- * @param key Key
- * @param hint Nearby entry
- * @return Pointer to entry on success, @c NULL on failure
- */
-odlink_t *odict_find_lt(odict_t *odict, void *key, odlink_t *hint)
-{
-	odlink_t *geq;
-
-	geq = odict_find_geq(odict, key, hint);
-	if (geq != NULL)
-		return odict_prev(geq, odict);
-	else
-		return odict_last(odict);
-}
-
-/** Return parent, grandparent and uncle.
- *
- * @param n Node
- * @param p Place to store pointer to parent of @a n
- * @param pcs Place to store position of @a n w.r.t. @a p
- * @param g Place to store pointer to grandparent of @a n
- * @param gcs Place to store position of @a p w.r.t. @a g
- * @param u Place to store pointer to uncle of @a n
- */
-static void odict_pgu(odlink_t *n, odlink_t **p, odict_child_sel_t *pcs,
-    odlink_t **g, odict_child_sel_t *gcs, odlink_t **u)
-{
-	*p = n->up;
-
-	if (*p == NULL) {
-		/* No parent */
-		*g = NULL;
-		*u = NULL;
-		return;
-	}
-
-	if ((*p)->a == n) {
-		*pcs = odcs_a;
-	} else {
-		assert((*p)->b == n);
-		*pcs = odcs_b;
-	}
-
-	*g = (*p)->up;
-	if (*g == NULL) {
-		/* No grandparent */
-		*u = NULL;
-		return;
-	}
-
-	if ((*g)->a == *p) {
-		*gcs = odcs_a;
-		*u = (*g)->b;
-	} else {
-		assert((*g)->b == *p);
-		*gcs = odcs_b;
-		*u = (*g)->a;
-	}
-}
-
-/** Return sibling and parent w.r.t. parent.
- *
- * @param n Node
- * @param p Parent of @ an
- * @param pcs Place to store position of @a n w.r.t. @a p.
- * @param rs Place to strore pointer to sibling
- */
-static void odict_sibling(odlink_t *n, odlink_t *p, odict_child_sel_t *pcs,
-    odlink_t **rs)
-{
-	if (p->a == n) {
-		*pcs = odcs_a;
-		*rs = p->b;
-	} else {
-		*pcs = odcs_b;
-		*rs = p->a;
-	}
-}
-
-/** Ordered dictionary left rotation.
- *
- *    Q           P
- *  P   C   <- A    Q
- * A B             B C
- *
- */
-static void odict_rotate_left(odlink_t *p)
-{
-	odlink_t *q;
-
-	q = p->b;
-	assert(q != NULL);
-
-	/* Replace P with Q as the root of the subtree */
-	odict_replace_subtree(q, p);
-
-	/* Relink P under Q, B under P */
-	p->up = q;
-	p->b = q->a;
-	if (p->b != NULL)
-		p->b->up = p;
-	q->a = p;
-
-	/* Fix odict root */
-	if (p->odict->root == p)
-		p->odict->root = q;
-}
-
-/** Ordered dictionary right rotation.
- *
- *    Q           P
- *  P   C   -> A    Q
- * A B             B C
- *
- */
-static void odict_rotate_right(odlink_t *q)
-{
-	odlink_t *p;
-
-	p = q->a;
-	assert(p != NULL);
-
-	/* Replace Q with P as the root of the subtree */
-	odict_replace_subtree(p, q);
-
-	/* Relink Q under P, B under Q */
-	q->up = p;
-	q->a = p->b;
-	if (q->a != NULL)
-		q->a->up = q;
-	p->b = q;
-
-	/* Fix odict root */
-	if (q->odict->root == q)
-		q->odict->root = p;
-}
-
-/** Swap two nodes.
- *
- * Swap position of two nodes in the tree, keeping their identity.
- * This means we don't copy the contents, instead we shuffle around pointers
- * from and to the nodes.
- *
- * @param a First node
- * @param b Second node
- */
-static void odict_swap_node(odlink_t *a, odlink_t *b)
-{
-	odlink_t *n;
-	odict_color_t c;
-
-	/* Backlink from A's parent */
-	if (a->up != NULL && a->up != b) {
-		if (a->up->a == a) {
-			a->up->a = b;
-		} else {
-			assert(a->up->b == a);
-			a->up->b = b;
-		}
-	}
-
-	/* Backlink from A's left child */
-	if (a->a != NULL && a->a != b)
-		a->a->up = b;
-	/* Backling from A's right child */
-	if (a->b != NULL && a->b != b)
-		a->b->up = b;
-
-	/* Backlink from B's parent */
-	if (b->up != NULL && b->up != a) {
-		if (b->up->a == b) {
-			b->up->a = a;
-		} else {
-			assert(b->up->b == b);
-			b->up->b = a;
-		}
-	}
-
-	/* Backlink from B's left child */
-	if (b->a != NULL && b->a != a)
-		b->a->up = a;
-	/* Backling from B's right child */
-	if (b->b != NULL && b->b != a)
-		b->b->up = a;
-
-	/*
-	 * Swap links going out of A and out of B
-	 */
-	n = a->up;
-	a->up = b->up;
-	b->up = n;
-
-	n = a->a;
-	a->a = b->a;
-	b->a = n;
-
-	n = a->b;
-	a->b = b->b;
-	b->b = n;
-
-	c = a->color;
-	a->color = b->color;
-	b->color = c;
-
-	/* When A and B are adjacent, fix self-loops that might have arisen */
-	if (a->up == a)
-		a->up = b;
-	if (a->a == a)
-		a->a = b;
-	if (a->b == a)
-		a->b = b;
-	if (b->up == b)
-		b->up = a;
-	if (b->a == b)
-		b->a = a;
-	if (b->b == b)
-		b->b = a;
-
-	/* Fix odict root */
-	if (a == a->odict->root)
-		a->odict->root = b;
-	else if (b == a->odict->root)
-		a->odict->root = a;
-}
-
-/** Replace subtree.
- *
- * Replace subtree @a old with another subtree @a n. This makes the parent
- * point to the new subtree root and the up pointer of @a n to point to
- * the parent.
- *
- * @param old Subtree to be replaced
- * @param n New subtree
- */
-static void odict_replace_subtree(odlink_t *n, odlink_t *old)
-{
-	if (old->up != NULL) {
-		if (old->up->a == old) {
-			old->up->a = n;
-		} else {
-			assert(old->up->b == old);
-			old->up->b = n;
-		}
-	} else {
-		assert(old->odict->root == old);
-		old->odict->root = n;
-	}
-
-	n->up = old->up;
-}
-
-/** Unlink node.
- *
- * @param n Ordered dictionary node
- */
-static void odict_unlink(odlink_t *n)
-{
-	if (n->up != NULL) {
-		if (n->up->a == n) {
-			n->up->a = NULL;
-		} else {
-			assert(n->up->b == n);
-			n->up->b = NULL;
-		}
-
-		n->up = NULL;
-	} else {
-		assert(n->odict->root == n);
-		n->odict->root = NULL;
-	}
-
-	if (n->a != NULL) {
-		n->a->up = NULL;
-		n->a = NULL;
-	}
-
-	if (n->b != NULL) {
-		n->b->up = NULL;
-		n->b = NULL;
-	}
-
-	n->odict = NULL;
-	list_remove(&n->lentries);
-}
-
-/** Link node as left child.
- *
- * Append new node @a n as left child of existing node @a old.
- *
- * @param n New node
- * @param old Old node
- */
-static void odict_link_child_a(odlink_t *n, odlink_t *old)
-{
-	old->a = n;
-	n->up = old;
-	n->odict = old->odict;
-	list_insert_before(&n->lentries, &old->lentries);
-}
-
-/** Link node as right child.
- *
- * Append new node @a n as right child of existing node @a old.
- *
- * @param n New node
- * @param old Old node
- */
-static void odict_link_child_b(odlink_t *n, odlink_t *old)
-{
-	old->b = n;
-	n->up = old;
-	n->odict = old->odict;
-	list_insert_after(&n->lentries, &old->lentries);
-}
-
-/** Get node where search should be started.
- *
- * @param odict Ordered dictionary
- * @param key Key being searched for
- * @param hint Node that might be near the search target or @c NULL
- *
- * @return Node from where search should be started
- */
-static odlink_t *odict_search_start_node(odict_t *odict, void *key,
-    odlink_t *hint)
-{
-	odlink_t *a;
-	odlink_t *b;
-	odlink_t *cur;
-	int d, da, db;
-
-	assert(hint == NULL || hint->odict == odict);
-
-	/* If the key is greater than the maximum, start search in the maximum */
-	b = odict_last(odict);
-	if (b != NULL) {
-		d = odict->cmp(odict->getkey(b), key);
-		if (d < 0)
-			return b;
-	}
-
-	/* If the key is less tna the minimum, start search in the minimum */
-	a = odict_first(odict);
-	if (a != NULL) {
-		d = odict->cmp(key, odict->getkey(a));
-		if (d < 0)
-			return a;
-	}
-
-	/*
-	 * Proposition: Let A, B be two BST nodes such that B is a descendant
-	 * of A. Let N be a node such that either key(A) < key(N) < key(B)
-	 * Then N is a descendant of A.
-	 * Corollary: We can start searching for N from A, instead from
-	 * the root.
-	 *
-	 * Proof: When walking the BST in order, visit_tree(A) does a
-	 * visit_tree(A->a), visit(A), visit(A->b). If key(A) < key(B),
-	 * we will first visit A, then while visiting all nodes with values
-	 * between A and B we will not leave subtree A->b.
-	 */
-
-	/* If there is no hint, start search from the root */
-	if (hint == NULL)
-		return odict->root;
-
-	/*
-	 * Start from hint and walk up to the root, keeping track of
-	 * minimum and maximum. Once key is strictly between them,
-	 * we can return the current node, which we've proven to be
-	 * an ancestor of a potential node with the given key
-	 */
-	a = b = cur = hint;
-	while (cur->up != NULL) {
-		cur = cur->up;
-
-		d = odict->cmp(odict->getkey(cur), odict->getkey(a));
-		if (d < 0)
-			a = cur;
-
-		d = odict->cmp(odict->getkey(b), odict->getkey(cur));
-		if (d < 0)
-			b = cur;
-
-		da = odict->cmp(odict->getkey(a), key);
-		db = odict->cmp(key, odict->getkey(b));
-		if (da < 0 && db < 0) {
-			/* Both a and b are descendants of cur */
-			return cur;
-		}
-	}
-
-	return odict->root;
-}
-
-/** @}
- */
Index: kernel/generic/src/cap/cap.c
===================================================================
--- kernel/generic/src/cap/cap.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/cap/cap.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -125,5 +125,5 @@
 }
 
-static hash_table_ops_t caps_ops = {
+static const hash_table_ops_t caps_ops = {
 	.hash = caps_hash,
 	.key_hash = caps_key_hash,
@@ -347,5 +347,5 @@
 			/* Hand over cap's reference to kobj */
 			kobj = cap->kobject;
-			if (!mutex_trylock(&kobj->caps_list_lock)) {
+			if (mutex_trylock(&kobj->caps_list_lock) != EOK) {
 				mutex_unlock(&task->cap_info->lock);
 				kobj = NULL;
Index: kernel/generic/src/console/chardev.c
===================================================================
--- kernel/generic/src/console/chardev.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/console/chardev.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -52,5 +52,5 @@
 {
 	indev->name = name;
-	waitq_initialize(&indev->wq);
+	semaphore_initialize(&indev->wq, 0);
 	irq_spinlock_initialize(&indev->lock, "chardev.indev.lock");
 	indev->counter = 0;
@@ -81,5 +81,5 @@
 	/* Index modulo size of buffer */
 	indev->index = indev->index % INDEV_BUFLEN;
-	waitq_wakeup(&indev->wq, WAKEUP_FIRST);
+	semaphore_up(&indev->wq);
 	irq_spinlock_unlock(&indev->lock, true);
 }
@@ -115,5 +115,5 @@
 	}
 
-	waitq_sleep(&indev->wq);
+	semaphore_down(&indev->wq);
 	irq_spinlock_lock(&indev->lock, true);
 	char32_t ch = indev->buffer[(indev->index - indev->counter) %
Index: kernel/generic/src/console/cmd.c
===================================================================
--- kernel/generic/src/console/cmd.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/console/cmd.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -229,4 +229,14 @@
 };
 
+/* Data and methods for 'printbench' command. */
+static int cmd_printbench(cmd_arg_t *argv);
+
+static cmd_info_t printbench_info = {
+	.name = "printbench",
+	.description = "Run a printing benchmark.",
+	.func = cmd_printbench,
+	.argc = 0,
+};
+
 #endif /* CONFIG_TEST */
 
@@ -613,4 +623,5 @@
 	&test_info,
 	&bench_info,
+	&printbench_info,
 #endif
 #ifdef CONFIG_UDEBUG
@@ -993,7 +1004,6 @@
 			printf("cpu%u: ", i);
 			thread_wire(thread, &cpus[i]);
-			thread_ready(thread);
+			thread_start(thread);
 			thread_join(thread);
-			thread_detach(thread);
 		} else
 			printf("Unable to create thread for cpu%u\n", i);
@@ -1582,4 +1592,58 @@
 }
 
+int cmd_printbench(cmd_arg_t *argv)
+{
+	int cnt = 20;
+
+	uint64_t *data = malloc(sizeof(uint64_t) * cnt);
+	if (data == NULL) {
+		printf("Error allocating memory for statistics\n");
+		return false;
+	}
+
+	for (int i = 0; i < cnt; i++) {
+		/*
+		 * Update and read thread accounting
+		 * for benchmarking
+		 */
+		irq_spinlock_lock(&TASK->lock, true);
+		uint64_t ucycles0, kcycles0;
+		task_get_accounting(TASK, &ucycles0, &kcycles0);
+		irq_spinlock_unlock(&TASK->lock, true);
+
+		/* Execute the test */
+		for (int j = 0; j < 20; j++) {
+			printf("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ěščřžýáíéú!@#$%%^&*(){}+\n");
+			printf("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ěščřžýáíéú!@#$%%^&*(){}+abcdefghijklmnopqrstuvwxyz\n");
+			printf("0123456789ěščřžýáíéú!@#$%%^&*(){}+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n");
+		}
+
+		/* Update and read thread accounting */
+		irq_spinlock_lock(&TASK->lock, true);
+		uint64_t ucycles1, kcycles1;
+		task_get_accounting(TASK, &ucycles1, &kcycles1);
+		irq_spinlock_unlock(&TASK->lock, true);
+
+		data[i] = ucycles1 - ucycles0 + kcycles1 - kcycles0;
+	}
+
+	printf("\n");
+
+	uint64_t cycles;
+	char suffix;
+	uint64_t sum = 0;
+
+	for (int i = 0; i < cnt; i++) {
+		sum += data[i];
+	}
+
+	order_suffix(sum / (uint64_t) cnt, &cycles, &suffix);
+	printf("Average\t\t%" PRIu64 "%c\n", cycles, suffix);
+
+	free(data);
+
+	return true;
+}
+
 #endif
 
Index: kernel/generic/src/console/console.c
===================================================================
--- kernel/generic/src/console/console.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/console/console.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -243,55 +243,4 @@
 }
 
-/** Get string from input character device.
- *
- * Read characters from input character device until first occurrence
- * of newline character.
- *
- * @param indev  Input character device.
- * @param buf    Buffer where to store string terminated by NULL.
- * @param buflen Size of the buffer.
- *
- * @return Number of characters read.
- *
- */
-size_t gets(indev_t *indev, char *buf, size_t buflen)
-{
-	size_t offset = 0;
-	size_t count = 0;
-	buf[offset] = 0;
-
-	char32_t ch;
-	while ((ch = indev_pop_character(indev)) != '\n') {
-		if (ch == '\b') {
-			if (count > 0) {
-				/* Space, backspace, space */
-				putuchar('\b');
-				putuchar(' ');
-				putuchar('\b');
-
-				count--;
-				offset = str_lsize(buf, count);
-				buf[offset] = 0;
-			}
-		}
-
-		if (chr_encode(ch, buf, &offset, buflen - 1) == EOK) {
-			putuchar(ch);
-			count++;
-			buf[offset] = 0;
-		}
-	}
-
-	return count;
-}
-
-/** Get character from input device & echo it to screen */
-char32_t getc(indev_t *indev)
-{
-	char32_t ch = indev_pop_character(indev);
-	putuchar(ch);
-	return ch;
-}
-
 void kio_update(void *event)
 {
Index: kernel/generic/src/console/kconsole.c
===================================================================
--- kernel/generic/src/console/kconsole.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/console/kconsole.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -85,4 +85,6 @@
 SPINLOCK_INITIALIZE(cmd_lock);  /**< Lock protecting command list. */
 LIST_INITIALIZE(cmd_list);      /**< Command list. */
+
+#define MAX_SYMBOL_NAME 64
 
 static char32_t history[KCONSOLE_HISTORY][MAX_CMDLINE] = { };
@@ -597,5 +599,5 @@
 		/* It's a number - convert it */
 		uint64_t value;
-		char *end;
+		const char *end;
 		errno_t rc = str_uint64_t(text, &end, 0, false, &value);
 		if (end != text + len)
Index: kernel/generic/src/cpu/cpu.c
===================================================================
--- kernel/generic/src/cpu/cpu.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/cpu/cpu.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -45,5 +45,5 @@
 #include <config.h>
 #include <panic.h>
-#include <mem.h>
+#include <memw.h>
 #include <adt/list.h>
 #include <stdio.h>
@@ -81,8 +81,11 @@
 				panic("Cannot allocate CPU stack.");
 
-			cpus[i].stack = (uint8_t *) PA2KA(stack_phys);
+			cpus[i].local.stack = (uint8_t *) PA2KA(stack_phys);
 			cpus[i].id = i;
 
-			irq_spinlock_initialize(&cpus[i].lock, "cpus[].lock");
+#ifdef CONFIG_FPU_LAZY
+			irq_spinlock_initialize(&cpus[i].fpu_lock, "cpus[].fpu_lock");
+#endif
+			irq_spinlock_initialize(&cpus[i].tlb_lock, "cpus[].tlb_lock");
 
 			for (unsigned int j = 0; j < RQ_COUNT; j++) {
@@ -101,8 +104,8 @@
 	CPU->tlb_active = true;
 
-	CPU->idle = false;
-	CPU->last_cycle = get_cycle();
-	CPU->idle_cycles = 0;
-	CPU->busy_cycles = 0;
+	CPU_LOCAL->idle = false;
+	CPU_LOCAL->last_cycle = get_cycle();
+	CPU->idle_cycles = ATOMIC_TIME_INITIALIZER();
+	CPU->busy_cycles = ATOMIC_TIME_INITIALIZER();
 
 	cpu_identify();
Index: kernel/generic/src/ddi/ddi.c
===================================================================
--- kernel/generic/src/ddi/ddi.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/ddi/ddi.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -54,5 +54,5 @@
 #include <align.h>
 #include <errno.h>
-#include <mem.h>
+#include <memw.h>
 #include <trace.h>
 #include <bitops.h>
@@ -336,23 +336,19 @@
 		return EPERM;
 
-	irq_spinlock_lock(&tasks_lock, true);
-
 	task_t *task = task_find_by_id(id);
 
-	if ((!task) || (!container_check(CONTAINER, task->container))) {
-		/*
-		 * There is no task with the specified ID
-		 * or the task belongs to a different security
-		 * context.
-		 */
-		irq_spinlock_unlock(&tasks_lock, true);
+	if (!task)
 		return ENOENT;
-	}
-
-	/* Lock the task and release the lock protecting tasks dictionary. */
-	irq_spinlock_exchange(&tasks_lock, &task->lock);
-	errno_t rc = ddi_iospace_enable_arch(task, ioaddr, size);
+
+	errno_t rc = ENOENT;
+
+	irq_spinlock_lock(&task->lock, true);
+
+	/* Check that the task belongs to the correct security context. */
+	if (container_check(CONTAINER, task->container))
+		rc = ddi_iospace_enable_arch(task, ioaddr, size);
+
 	irq_spinlock_unlock(&task->lock, true);
-
+	task_release(task);
 	return rc;
 }
@@ -377,23 +373,19 @@
 		return EPERM;
 
-	irq_spinlock_lock(&tasks_lock, true);
-
 	task_t *task = task_find_by_id(id);
 
-	if ((!task) || (!container_check(CONTAINER, task->container))) {
-		/*
-		 * There is no task with the specified ID
-		 * or the task belongs to a different security
-		 * context.
-		 */
-		irq_spinlock_unlock(&tasks_lock, true);
+	if (!task)
 		return ENOENT;
-	}
-
-	/* Lock the task and release the lock protecting tasks dictionary. */
-	irq_spinlock_exchange(&tasks_lock, &task->lock);
-	errno_t rc = ddi_iospace_disable_arch(task, ioaddr, size);
+
+	errno_t rc = ENOENT;
+
+	irq_spinlock_lock(&task->lock, true);
+
+	/* Check that the task belongs to the correct security context. */
+	if (container_check(CONTAINER, task->container))
+		rc = ddi_iospace_disable_arch(task, ioaddr, size);
+
 	irq_spinlock_unlock(&task->lock, true);
-
+	task_release(task);
 	return rc;
 }
Index: kernel/generic/src/ddi/irq.c
===================================================================
--- kernel/generic/src/ddi/irq.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/ddi/irq.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -47,5 +47,5 @@
 #include <console/console.h>
 #include <interrupt.h>
-#include <mem.h>
+#include <memw.h>
 #include <arch.h>
 
@@ -77,5 +77,5 @@
 static bool irq_ht_key_equal(const void *, const ht_link_t *);
 
-static hash_table_ops_t irq_ht_ops = {
+static const hash_table_ops_t irq_ht_ops = {
 	.hash = irq_ht_hash,
 	.key_hash = irq_ht_key_hash,
Index: rnel/generic/src/debug/debug.c
===================================================================
--- kernel/generic/src/debug/debug.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,78 +1,0 @@
-/*
- * Copyright (c) 2010 Martin Decky
- * 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 kernel_generic_debug
- * @{
- */
-
-/**
- * @file
- * @brief Kernel instrumentation functions.
- */
-
-#ifdef CONFIG_TRACE
-
-#include <debug.h>
-#include <symtab.h>
-#include <errno.h>
-#include <stdio.h>
-
-void __cyg_profile_func_enter(void *fn, void *call_site)
-{
-	const char *fn_sym = symtab_fmt_name_lookup((uintptr_t) fn);
-
-	const char *call_site_sym;
-	uintptr_t call_site_off;
-
-	if (symtab_name_lookup((uintptr_t) call_site, &call_site_sym,
-	    &call_site_off) == EOK)
-		printf("%s()+%p->%s()\n", call_site_sym,
-		    (void *) call_site_off, fn_sym);
-	else
-		printf("->%s()\n", fn_sym);
-}
-
-void __cyg_profile_func_exit(void *fn, void *call_site)
-{
-	const char *fn_sym = symtab_fmt_name_lookup((uintptr_t) fn);
-
-	const char *call_site_sym;
-	uintptr_t call_site_off;
-
-	if (symtab_name_lookup((uintptr_t) call_site, &call_site_sym,
-	    &call_site_off) == EOK)
-		printf("%s()+%p<-%s()\n", call_site_sym,
-		    (void *) call_site_off, fn_sym);
-	else
-		printf("<-%s()\n", fn_sym);
-}
-
-#endif /* CONFIG_TRACE */
-
-/** @}
- */
Index: kernel/generic/src/debug/line.c
===================================================================
--- kernel/generic/src/debug/line.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ kernel/generic/src/debug/line.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,739 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdatomic.h>
+
+#include <stdlib.h>
+
+#include <debug/constants.h>
+#include <debug/line.h>
+#include <debug/names.h>
+#include <debug/sections.h>
+
+#include "util.h"
+
+static inline void line_program_reset(struct debug_line_program *lp)
+{
+	lp->address = 0;
+	lp->op_advance = 0;
+	lp->file = 1;
+	lp->line = 1;
+	lp->column = 0;
+	lp->end_sequence = false;
+}
+
+/**
+ * Quickly advance program just past the next sequence end, without processing
+ * anything on the way. This is useful for when we just want to get start
+ * addresses of every range to build a table.
+ *
+ * @param lp
+ */
+static void debug_line_program_skip_to_sequence_end(struct debug_line_program *lp)
+{
+	const uint8_t opcode_base = lp->hdr->opcode_base;
+
+	const uint8_t *program = lp->program;
+	const uint8_t *const program_end = lp->program_end;
+
+	if (lp->end_sequence)
+		line_program_reset(lp);
+
+	while (program < program_end) {
+		uint8_t opcode = read_byte(&program, program_end);
+
+		if (opcode >= opcode_base) {
+			// Special opcode.
+			continue;
+		}
+
+		switch (opcode) {
+		case DW_LNS_fixed_advance_pc:
+			safe_increment(&program, program_end, 2);
+			break;
+
+		case DW_LNS_copy:
+		case DW_LNS_negate_stmt:
+		case DW_LNS_set_basic_block:
+		case DW_LNS_set_prologue_end:
+		case DW_LNS_set_epilogue_begin:
+		case DW_LNS_const_add_pc:
+			break;
+
+		case DW_LNS_advance_pc:
+		case DW_LNS_advance_line:
+		case DW_LNS_set_file:
+		case DW_LNS_set_column:
+		case DW_LNS_set_isa:
+			skip_leb128(&program, program_end);
+			break;
+
+		case 0:
+			// Extended opcodes.
+			size_t length = read_uleb128(&program, program_end);
+
+			if (read_byte(&program, program_end) == DW_LNE_end_sequence) {
+				lp->program = program;
+				lp->end_sequence = true;
+				return;
+			}
+
+			safe_increment(&program, program_end, length - 1);
+			break;
+
+		default:
+			// Unknown standard opcode.
+
+			// If the opcode length array is truncated, there is already
+			// something wrong with the parse, so we don't care if we misparse
+			// the remainder.
+			if (opcode < lp->hdr->standard_opcode_lengths_size) {
+				int len = lp->hdr->standard_opcode_lengths[opcode];
+				for (int i = 0; i < len; i++) {
+					// Drop arguments.
+					skip_leb128(&program, program_end);
+				}
+			}
+		}
+	}
+
+	lp->program = program;
+	lp->truncated = true;
+}
+
+static void transfer_op_advance(struct debug_line_program *lp)
+{
+	if (lp->hdr->version < 5) {
+		lp->address += lp->op_advance * lp->hdr->minimum_instruction_length;
+		lp->op_advance = 0;
+	} else {
+		int maxops = lp->hdr->v5.maximum_operations_per_instruction;
+
+		// For branch prediction, tell GCC maxops is likely to be 1, because
+		// I don't want to punish sensible architectures for VLIW madness.
+		if (__builtin_expect(maxops == 1, 1)) {
+			lp->address += lp->op_advance * lp->hdr->minimum_instruction_length;
+			lp->op_advance = 0;
+		} else {
+			int adv = lp->op_advance;
+			int d = adv / maxops;
+			int r = adv % maxops;
+
+			lp->address += d * lp->hdr->minimum_instruction_length;
+			lp->op_advance = r;
+		}
+	}
+}
+
+static void debug_line_program_advance(struct debug_line_program *lp)
+{
+	const uint8_t opcode_base = lp->hdr->opcode_base;
+	const int8_t line_base = lp->hdr->line_base;
+	const uint8_t line_range = lp->hdr->line_range;
+	const int const_advance = (255 - opcode_base) / lp->hdr->line_range;
+
+	const uint8_t *program = lp->program;
+	const uint8_t *const program_end = lp->program_end;
+
+	if (lp->end_sequence)
+		line_program_reset(lp);
+
+	while (program < program_end) {
+		uint8_t opcode = read_byte(&program, program_end);
+
+		if (opcode >= opcode_base) {
+			// Special opcode.
+			int adjusted = opcode - opcode_base;
+			DEBUGF("DW_LNS_special(%d)\n", adjusted);
+
+			lp->op_advance += adjusted / line_range;
+			lp->line += line_base + (adjusted % line_range);
+
+			transfer_op_advance(lp);
+			lp->program = program;
+			return;
+		}
+
+		const char *opname = dw_lns_name(opcode);
+
+		uint64_t arg;
+		uint16_t arg16;
+
+		switch (opcode) {
+		case DW_LNS_copy:
+			DEBUGF("%s()\n", opname);
+
+			transfer_op_advance(lp);
+			lp->program = program;
+			return;
+
+		case DW_LNS_advance_pc:
+			arg = read_uleb128(&program, program_end);
+			DEBUGF("%s(%" PRIu64 ")\n", opname, arg);
+			lp->op_advance += arg;
+			break;
+
+		case DW_LNS_advance_line:
+			lp->line += read_sleb128(&program, program_end);
+			DEBUGF("%s(line = %d)\n", opname, lp->line);
+			break;
+
+		case DW_LNS_set_file:
+			lp->file = read_uleb128(&program, program_end);
+			DEBUGF("%s(%d)\n", opname, lp->file);
+			break;
+
+		case DW_LNS_set_column:
+			lp->column = read_uleb128(&program, program_end);
+			DEBUGF("%s(%d)\n", opname, lp->column);
+			break;
+
+		case DW_LNS_negate_stmt:
+		case DW_LNS_set_basic_block:
+		case DW_LNS_set_prologue_end:
+		case DW_LNS_set_epilogue_begin:
+			DEBUGF("%s()\n", opname);
+			// ignore
+			break;
+
+		case DW_LNS_set_isa:
+			// discard argument and ignore
+			arg = read_uleb128(&program, program_end);
+			DEBUGF("%s(%" PRIu64 ")\n", opname, arg);
+			break;
+
+		case DW_LNS_const_add_pc:
+			DEBUGF("%s(%d)\n", opname, const_advance);
+			lp->op_advance += const_advance;
+			break;
+
+		case DW_LNS_fixed_advance_pc:
+			arg16 = read_uint16(&program, program_end);
+			DEBUGF("%s(%u)\n", opname, arg16);
+
+			transfer_op_advance(lp);
+			lp->address += arg16;
+			lp->op_advance = 0;
+			break;
+
+		case 0:
+			/* Extended opcodes. */
+			size_t length = read_uleb128(&program, program_end);
+
+			opcode = read_byte(&program, program_end);
+			const char *opname = dw_lne_name(opcode);
+
+			switch (opcode) {
+			case DW_LNE_end_sequence:
+				DEBUGF("%s:%zu()\n", opname, length);
+
+				transfer_op_advance(lp);
+				lp->program = program;
+				lp->end_sequence = true;
+				return;
+
+			case DW_LNE_set_address:
+				lp->address = read_uint(&program, program_end, sizeof(uintptr_t));
+				lp->op_advance = 0;
+
+				DEBUGF("%s:%zu(0x%zx)\n", opname, length, lp->address);
+				break;
+
+			case DW_LNE_set_discriminator:
+				uint64_t arg = read_uleb128(&program, program_end);
+				DEBUGF("%s:%zu(%" PRIu64 ")\n", opname, length, arg);
+				break;
+
+			default:
+				DEBUGF("unknown extended opcode %d:%zu\n", opcode, length);
+				safe_increment(&program, program_end, length - 1);
+			}
+			break;
+
+		default:
+			DEBUGF("unknown standard opcode %d\n", opcode);
+
+			// If the opcode length array is truncated, there is already
+			// something wrong with the parse, so we don't care if we misparse
+			// the remainder.
+			if (opcode < lp->hdr->standard_opcode_lengths_size) {
+				int len = lp->hdr->standard_opcode_lengths[opcode];
+				for (int i = 0; i < len; i++) {
+					// Drop arguments.
+					skip_leb128(&program, program_end);
+				}
+			}
+		}
+	}
+
+	transfer_op_advance(lp);
+	lp->program = program;
+	lp->truncated = true;
+}
+
+static void debug_line_program_header_parse(debug_sections_t *scs, const uint8_t *data, const uint8_t *data_end, struct debug_line_program_header *hdr)
+{
+	const uint8_t *unit_start = data;
+
+#define FIELD(name, spec, expr) (hdr->name = expr, DEBUGF(#name ": %" spec "\n", hdr->name))
+
+	unsigned width;
+
+	FIELD(unit_length, PRId64, read_initial_length(&data, data_end, &width));
+	FIELD(width, "d", width);
+
+	hdr->unit_end = data;
+	safe_increment(&hdr->unit_end, data_end, hdr->unit_length);
+	DEBUGF("unit size: %zu\n", hdr->unit_end - unit_start);
+
+	data_end = hdr->unit_end;
+
+	FIELD(version, "u", read_uint16(&data, data_end));
+
+	if (hdr->version < 3 || hdr->version > 5) {
+		// Not supported.
+		hdr->header_end = hdr->unit_end;
+		return;
+	}
+
+	if (hdr->version >= 5) {
+		FIELD(v5.address_size, "u", read_byte(&data, data_end));
+		FIELD(v5.segment_selector_size, "u", read_byte(&data, data_end));
+	}
+
+	FIELD(header_length, PRIu64, read_uint(&data, data_end, width));
+
+	hdr->header_end = data;
+	safe_increment(&hdr->header_end, data_end, hdr->header_length);
+
+	data_end = hdr->header_end;
+
+	FIELD(minimum_instruction_length, "u", read_byte(&data, data_end));
+	if (hdr->version >= 5)
+		FIELD(v5.maximum_operations_per_instruction, "u", read_byte(&data, data_end));
+	FIELD(default_is_stmt, "u", read_byte(&data, data_end));
+	FIELD(line_base, "d", (int8_t) read_byte(&data, data_end));
+	FIELD(line_range, "u", read_byte(&data, data_end));
+	FIELD(opcode_base, "u", read_byte(&data, data_end));
+
+	hdr->standard_opcode_lengths = data;
+	safe_increment(&data, data_end, hdr->opcode_base - 1);
+	hdr->standard_opcode_lengths_size = data - hdr->standard_opcode_lengths;
+
+	if (hdr->version < 5) {
+		hdr->v3.include_directories = data;
+		while (data < data_end && *data != 0)
+			skip_string(&data, data_end);
+		hdr->v3.include_directories_end = data;
+
+		// Sanitize the list a little.
+		while (hdr->v3.include_directories < hdr->v3.include_directories_end && hdr->v3.include_directories_end[-1] != 0)
+			hdr->v3.include_directories_end--;
+
+		safe_increment(&data, data_end, 1);
+
+		hdr->v3.file_names = data;
+	}
+
+	if (hdr->version >= 5) {
+		FIELD(v5.directory_entry_format_count, "u", read_byte(&data, data_end));
+
+		hdr->v5.directory_entry_format = data;
+		skip_format(&data, data_end, hdr->v5.directory_entry_format_count);
+		hdr->v5.directory_entry_format_end = data;
+
+		print_format("directory_entry_format",
+		    hdr->v5.directory_entry_format, hdr->v5.directory_entry_format_end);
+
+		FIELD(v5.directories_count, PRIu64, read_uleb128(&data, data_end));
+
+		hdr->v5.directories = data;
+		skip_formatted_list(&data, data_end, hdr->v5.directories_count,
+		    hdr->v5.directory_entry_format, hdr->v5.directory_entry_format_end, width);
+		hdr->v5.directories_end = data;
+
+		print_formatted_list(scs, "directories", hdr->v5.directories, hdr->v5.directories_end,
+		    hdr->v5.directory_entry_format, hdr->v5.directory_entry_format_end, width);
+
+		FIELD(v5.file_name_entry_format_count, "u", read_byte(&data, data_end));
+
+		hdr->v5.file_name_entry_format = data;
+		skip_format(&data, data_end, hdr->v5.file_name_entry_format_count);
+		hdr->v5.file_name_entry_format_end = data;
+
+		print_format("file_name_entry_format",
+		    hdr->v5.file_name_entry_format, hdr->v5.file_name_entry_format_end);
+
+		FIELD(v5.file_names_count, PRIu64, read_uleb128(&data, data_end));
+
+		hdr->v5.file_names = data;
+		skip_formatted_list(&data, data_end, hdr->v5.file_names_count,
+		    hdr->v5.file_name_entry_format, hdr->v5.file_name_entry_format_end, width);
+		hdr->v5.file_names_end = data;
+
+		print_formatted_list(scs, "file_names", hdr->v5.file_names, hdr->v5.file_names_end,
+		    hdr->v5.file_name_entry_format, hdr->v5.file_name_entry_format_end, width);
+	}
+}
+
+static bool has_usable_name(const uint8_t *format, const uint8_t *format_end, unsigned width)
+{
+	// Check if there is an appropriate entry we can use.
+	bool has_usable_name = false;
+	const uint8_t *dummy = NULL;
+
+	while (format < format_end) {
+		uint64_t type = read_uleb128(&format, format_end);
+		uint64_t form = read_uleb128(&format, format_end);
+
+		if (type == DW_LNCT_path) {
+			if (form == DW_FORM_string || form == DW_FORM_line_strp) {
+				has_usable_name = true;
+			}
+		}
+
+		if (!skip_data(form, &dummy, NULL, width)) {
+			// Encountered DW_FORM that we don't understand,
+			// which means we can't skip it.
+			return false;
+		}
+	}
+
+	return has_usable_name;
+}
+
+static const char *get_file_name_v3(struct debug_line_program_header *hdr, int file, int *dir)
+{
+	if (file == 0) {
+		// We'd have to find and read the compilation unit header for this one,
+		// and we don't wanna.
+		return NULL;
+	}
+
+	file--;
+
+	const uint8_t *files = hdr->v3.file_names;
+	const uint8_t *files_end = hdr->header_end;
+
+	for (int i = 0; i < file; i++) {
+		if (files >= files_end || *files == 0) {
+			// End of list.
+			return NULL;
+		}
+
+		// Skip an entry.
+		skip_string(&files, files_end);
+		skip_leb128(&files, files_end);
+		skip_leb128(&files, files_end);
+		skip_leb128(&files, files_end);
+	}
+
+	if (files >= files_end || *files == 0)
+		return NULL;
+
+	const char *fn = (const char *) files;
+	skip_string(&files, files_end);
+	*dir = read_uleb128(&files, files_end);
+	return fn;
+}
+
+static const char *get_file_name_v5(debug_sections_t *scs, struct debug_line_program_header *hdr, int file, int *dir)
+{
+	// DWARF5 has dynamic layout for file information, which is why
+	// this is so horrible to decode. Enjoy.
+
+	if (!has_usable_name(hdr->v5.file_name_entry_format, hdr->v5.file_name_entry_format_end, hdr->width))
+		return NULL;
+
+	const uint8_t *fns = hdr->v5.file_names;
+	const uint8_t *fns_end = hdr->v5.file_names_end;
+
+	const char *filename = NULL;
+
+	for (uint8_t i = 0; i < hdr->v5.file_names_count; i++) {
+
+		const uint8_t *fnef = hdr->v5.file_name_entry_format;
+		const uint8_t *fnef_end = hdr->v5.file_name_entry_format_end;
+
+		for (uint8_t j = 0; j < hdr->v5.file_name_entry_format_count; j++) {
+			uint64_t type = read_uleb128(&fnef, fnef_end);
+			uint64_t form = read_uleb128(&fnef, fnef_end);
+
+			if (i == file && type == DW_LNCT_path) {
+				if (form == DW_FORM_string) {
+					filename = read_string(&fns, fns_end);
+					continue;
+				}
+
+				if (form == DW_FORM_line_strp) {
+					uint64_t offset = read_uint(&fns, fns_end, hdr->width);
+					if (offset < scs->debug_line_str_size) {
+						filename = scs->debug_line_str + offset;
+					}
+					continue;
+				}
+			}
+
+			if (i == file && type == DW_LNCT_directory_index) {
+				if (form == DW_FORM_data1) {
+					*dir = read_byte(&fns, fns_end);
+					continue;
+				}
+
+				if (form == DW_FORM_data2) {
+					*dir = read_uint16(&fns, fns_end);
+					continue;
+				}
+
+				if (form == DW_FORM_udata) {
+					*dir = read_uleb128(&fns, fns_end);
+					continue;
+				}
+			}
+
+			skip_data(form, &fns, fns_end, hdr->width);
+		}
+
+		if (i == file)
+			break;
+	}
+
+	return filename;
+}
+
+static const char *get_file_name(debug_sections_t *scs, struct debug_line_program_header *hdr, int file, int *dir)
+{
+	switch (hdr->version) {
+	case 3:
+	case 4:
+		return get_file_name_v3(hdr, file, dir);
+	case 5:
+		return get_file_name_v5(scs, hdr, file, dir);
+	default:
+		return NULL;
+	}
+}
+
+static const char *get_dir_name_v3(struct debug_line_program_header *hdr, int dir)
+{
+	if (dir == 0)
+		return ".";
+
+	const uint8_t *dirs = hdr->v3.include_directories;
+	const uint8_t *dirs_end = hdr->v3.include_directories_end;
+
+	for (int i = 1; i < dir; i++) {
+		skip_string(&dirs, dirs_end);
+	}
+
+	if (dirs < dirs_end)
+		return (const char *) dirs;
+	else
+		return NULL;
+}
+
+static const char *get_dir_name_v5(debug_sections_t *scs, struct debug_line_program_header *hdr, int dir)
+{
+	// TODO: basically a copypaste of get_file_name(). Try to deduplicate it.
+
+	if (!has_usable_name(hdr->v5.directory_entry_format, hdr->v5.directory_entry_format_end, hdr->width))
+		return NULL;
+
+	const uint8_t *fns = hdr->v5.directories;
+	const uint8_t *fns_end = hdr->v5.directories_end;
+
+	const char *filename = NULL;
+
+	for (uint8_t i = 0; i < hdr->v5.directories_count; i++) {
+
+		const uint8_t *fnef = hdr->v5.directory_entry_format;
+		const uint8_t *fnef_end = hdr->v5.directory_entry_format_end;
+
+		for (uint8_t j = 0; j < hdr->v5.directory_entry_format_count; j++) {
+			uint64_t type = read_uleb128(&fnef, fnef_end);
+			uint64_t form = read_uleb128(&fnef, fnef_end);
+
+			if (i == dir && type == DW_LNCT_path) {
+				if (form == DW_FORM_string) {
+					filename = read_string(&fns, fns_end);
+					continue;
+				}
+
+				if (form == DW_FORM_line_strp) {
+					uint64_t offset = read_uint(&fns, fns_end, hdr->width);
+					if (offset < scs->debug_line_str_size) {
+						filename = scs->debug_line_str + offset;
+					}
+					continue;
+				}
+			}
+
+			skip_data(form, &fns, fns_end, hdr->width);
+		}
+
+		if (i == dir)
+			break;
+	}
+
+	return filename;
+}
+
+static const char *get_dir_name(debug_sections_t *scs, struct debug_line_program_header *hdr, int dir)
+{
+	switch (hdr->version) {
+	case 3:
+	case 4:
+		return get_dir_name_v3(hdr, dir);
+	case 5:
+		return get_dir_name_v5(scs, hdr, dir);
+	default:
+		return NULL;
+	}
+}
+
+static const uint8_t *find_line_program(debug_sections_t *scs, uintptr_t addr)
+{
+	// TODO: use .debug_aranges to find the data quickly
+	// This implementation just iterates through the entire .debug_line
+
+	uintptr_t closest_addr = 0;
+	const uint8_t *closest_prog = NULL;
+
+	const uint8_t *debug_line_ptr = scs->debug_line;
+	const uint8_t *const debug_line_end = scs->debug_line + scs->debug_line_size;
+
+	while (debug_line_ptr < debug_line_end) {
+		const uint8_t *prog = debug_line_ptr;
+
+		// Parse header
+		struct debug_line_program_header hdr = { };
+		debug_line_program_header_parse(scs, prog, debug_line_end, &hdr);
+		assert(hdr.unit_end > debug_line_ptr);
+		assert(hdr.unit_end <= debug_line_end);
+		debug_line_ptr = hdr.unit_end;
+
+		struct debug_line_program lp = debug_line_program_create(
+		    hdr.header_end, hdr.unit_end, &hdr);
+
+		while (lp.program < lp.program_end) {
+			// Find the start address of every sequence
+
+			debug_line_program_advance(&lp);
+			DEBUGF("<< address: %zx, line: %d, column: %d >>\n", lp.address, lp.line, lp.column);
+
+			if (!lp.truncated) {
+				if (lp.address <= addr && lp.address > closest_addr) {
+					closest_addr = lp.address;
+					closest_prog = prog;
+				}
+			}
+
+			if (!lp.end_sequence) {
+				debug_line_program_skip_to_sequence_end(&lp);
+				assert(lp.truncated || lp.end_sequence);
+			}
+		}
+	}
+
+	return closest_prog;
+}
+
+static bool get_info(const struct debug_line_program_header *hdr, uintptr_t addr, int op_index, int *file, int *line, int *column)
+{
+	struct debug_line_program lp = debug_line_program_create(
+	    hdr->header_end, hdr->unit_end, hdr);
+
+	int last_file = 0;
+	int last_line = 0;
+	int last_column = 0;
+
+	while (lp.program < lp.program_end) {
+		bool first = lp.end_sequence;
+
+		debug_line_program_advance(&lp);
+
+		if (lp.truncated)
+			continue;
+
+		/*
+		 * Return previous entry when we pass the target address, because
+		 * the address may not be aligned perfectly on instruction boundary.
+		 */
+		if (lp.address > addr || (lp.address == addr && lp.op_advance > op_index)) {
+			if (first) {
+				// First address is already too large, skip to the next sequence.
+				debug_line_program_skip_to_sequence_end(&lp);
+				continue;
+			}
+
+			*file = last_file;
+			*line = last_line;
+			*column = last_column;
+			return true;
+		}
+
+		last_file = lp.file;
+		last_line = lp.line;
+		last_column = lp.column;
+	}
+
+	return false;
+}
+
+bool debug_line_get_address_info(debug_sections_t *scs, uintptr_t addr, int op_index, const char **file_name, const char **dir_name, int *line, int *column)
+{
+	const uint8_t *data = find_line_program(scs, addr);
+	if (data == NULL) {
+		return false;
+	}
+
+	const uint8_t *const debug_line_end = scs->debug_line + scs->debug_line_size;
+
+	struct debug_line_program_header hdr = { };
+	debug_line_program_header_parse(scs, data, debug_line_end, &hdr);
+	assert(hdr.unit_end > data);
+	assert(hdr.unit_end <= debug_line_end);
+
+	int dir = -1;
+	int file = -1;
+
+	if (!get_info(&hdr, addr, op_index, &file, line, column)) {
+		// printf("no info for 0x%zx: prog offset 0x%zx\n", addr, (void *) data - debug_line);
+		return false;
+	}
+
+	// printf("got info for 0x%zx: file %d, line %d, column %d\n", addr, file, *line, *column);
+
+	if (file >= 0)
+		*file_name = get_file_name(scs, &hdr, file, &dir);
+
+	if (dir >= 0)
+		*dir_name = get_dir_name(scs, &hdr, dir);
+
+	return true;
+}
Index: kernel/generic/src/debug/names.c
===================================================================
--- kernel/generic/src/debug/names.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ kernel/generic/src/debug/names.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,801 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <debug/names.h>
+#include <debug/constants.h>
+#include <stddef.h>
+
+#define ENTRY(name) [name] = #name
+
+static const char *const dw_access_names[] = {
+	ENTRY(DW_ACCESS_public),
+	ENTRY(DW_ACCESS_protected),
+	ENTRY(DW_ACCESS_private),
+};
+
+_Static_assert(DW_ACCESS_MAX == DW_ACCESS_private + 1);
+
+static const char *const dw_at_names[] = {
+	ENTRY(DW_AT_sibling),
+	ENTRY(DW_AT_location),
+	ENTRY(DW_AT_name),
+	ENTRY(DW_AT_reserved_0x04),
+	ENTRY(DW_AT_fund_type),
+	ENTRY(DW_AT_mod_fund_type),
+	ENTRY(DW_AT_user_def_type),
+	ENTRY(DW_AT_mod_u_d_type),
+	ENTRY(DW_AT_ordering),
+	ENTRY(DW_AT_subscr_data),
+	ENTRY(DW_AT_byte_size),
+	ENTRY(DW_AT_bit_offset),
+	ENTRY(DW_AT_bit_size),
+	ENTRY(DW_AT_reserved_0x0e),
+	ENTRY(DW_AT_unused8),
+	ENTRY(DW_AT_stmt_list),
+	ENTRY(DW_AT_low_pc),
+	ENTRY(DW_AT_high_pc),
+	ENTRY(DW_AT_language),
+	ENTRY(DW_AT_member),
+	ENTRY(DW_AT_discr),
+	ENTRY(DW_AT_discr_value),
+	ENTRY(DW_AT_visibility),
+	ENTRY(DW_AT_import),
+	ENTRY(DW_AT_string_length),
+	ENTRY(DW_AT_common_reference),
+	ENTRY(DW_AT_comp_dir),
+	ENTRY(DW_AT_const_value),
+	ENTRY(DW_AT_containing_type),
+	ENTRY(DW_AT_default_value),
+	ENTRY(DW_AT_reserved_0x1f),
+	ENTRY(DW_AT_inline),
+	ENTRY(DW_AT_is_optional),
+	ENTRY(DW_AT_lower_bound),
+	ENTRY(DW_AT_reserved_0x23),
+	ENTRY(DW_AT_reserved_0x24),
+	ENTRY(DW_AT_producer),
+	ENTRY(DW_AT_reserved_0x26),
+	ENTRY(DW_AT_prototyped),
+	ENTRY(DW_AT_reserved_0x28),
+	ENTRY(DW_AT_reserved_0x29),
+	ENTRY(DW_AT_return_addr),
+	ENTRY(DW_AT_reserved_0x2b),
+	ENTRY(DW_AT_start_scope),
+	ENTRY(DW_AT_reserved_0x2d),
+	ENTRY(DW_AT_bit_stride),
+	ENTRY(DW_AT_upper_bound),
+	ENTRY(DW_AT_virtual),
+	ENTRY(DW_AT_abstract_origin),
+	ENTRY(DW_AT_accessibility),
+	ENTRY(DW_AT_address_class),
+	ENTRY(DW_AT_artificial),
+	ENTRY(DW_AT_base_types),
+	ENTRY(DW_AT_calling_convention),
+	ENTRY(DW_AT_count),
+	ENTRY(DW_AT_data_member_location),
+	ENTRY(DW_AT_decl_column),
+	ENTRY(DW_AT_decl_file),
+	ENTRY(DW_AT_decl_line),
+	ENTRY(DW_AT_declaration),
+	ENTRY(DW_AT_discr_list),
+	ENTRY(DW_AT_encoding),
+	ENTRY(DW_AT_external),
+	ENTRY(DW_AT_frame_base),
+	ENTRY(DW_AT_friend),
+	ENTRY(DW_AT_identifier_case),
+	ENTRY(DW_AT_macro_info),
+	ENTRY(DW_AT_namelist_item),
+	ENTRY(DW_AT_priority),
+	ENTRY(DW_AT_segment),
+	ENTRY(DW_AT_specification),
+	ENTRY(DW_AT_static_link),
+	ENTRY(DW_AT_type),
+	ENTRY(DW_AT_use_location),
+	ENTRY(DW_AT_variable_parameter),
+	ENTRY(DW_AT_virtuality),
+	ENTRY(DW_AT_vtable_elem_location),
+	ENTRY(DW_AT_allocated),
+	ENTRY(DW_AT_associated),
+	ENTRY(DW_AT_data_location),
+	ENTRY(DW_AT_byte_stride),
+	ENTRY(DW_AT_entry_pc),
+	ENTRY(DW_AT_use_UTF8),
+	ENTRY(DW_AT_extension),
+	ENTRY(DW_AT_ranges),
+	ENTRY(DW_AT_trampoline),
+	ENTRY(DW_AT_call_column),
+	ENTRY(DW_AT_call_file),
+	ENTRY(DW_AT_call_line),
+	ENTRY(DW_AT_description),
+	ENTRY(DW_AT_binary_scale),
+	ENTRY(DW_AT_decimal_scale),
+	ENTRY(DW_AT_small),
+	ENTRY(DW_AT_decimal_sign),
+	ENTRY(DW_AT_digit_count),
+	ENTRY(DW_AT_picture_string),
+	ENTRY(DW_AT_mutable),
+	ENTRY(DW_AT_threads_scaled),
+	ENTRY(DW_AT_explicit),
+	ENTRY(DW_AT_object_pointer),
+	ENTRY(DW_AT_endianity),
+	ENTRY(DW_AT_elemental),
+	ENTRY(DW_AT_pure),
+	ENTRY(DW_AT_recursive),
+	ENTRY(DW_AT_signature),
+	ENTRY(DW_AT_main_subprogram),
+	ENTRY(DW_AT_data_bit_offset),
+	ENTRY(DW_AT_const_expr),
+	ENTRY(DW_AT_enum_class),
+	ENTRY(DW_AT_linkage_name),
+	ENTRY(DW_AT_string_length_bit_size),
+	ENTRY(DW_AT_string_length_byte_size),
+	ENTRY(DW_AT_rank),
+	ENTRY(DW_AT_str_offsets_base),
+	ENTRY(DW_AT_addr_base),
+	ENTRY(DW_AT_rnglists_base),
+	ENTRY(DW_AT_dwo_id),
+	ENTRY(DW_AT_dwo_name),
+	ENTRY(DW_AT_reference),
+	ENTRY(DW_AT_rvalue_reference),
+	ENTRY(DW_AT_macros),
+	ENTRY(DW_AT_call_all_calls),
+	ENTRY(DW_AT_call_all_source_calls),
+	ENTRY(DW_AT_call_all_tail_calls),
+	ENTRY(DW_AT_call_return_pc),
+	ENTRY(DW_AT_call_value),
+	ENTRY(DW_AT_call_origin),
+	ENTRY(DW_AT_call_parameter),
+	ENTRY(DW_AT_call_pc),
+	ENTRY(DW_AT_call_tail_call),
+	ENTRY(DW_AT_call_target),
+	ENTRY(DW_AT_call_target_clobbered),
+	ENTRY(DW_AT_call_data_location),
+	ENTRY(DW_AT_call_data_value),
+	ENTRY(DW_AT_noreturn),
+	ENTRY(DW_AT_alignment),
+	ENTRY(DW_AT_export_symbols),
+	ENTRY(DW_AT_deleted),
+	ENTRY(DW_AT_defaulted),
+	ENTRY(DW_AT_loclists_base),
+};
+
+_Static_assert(DW_AT_MAX == DW_AT_loclists_base + 1);
+
+static const char *const dw_ate_names[] = {
+	ENTRY(DW_ATE_address),
+	ENTRY(DW_ATE_boolean),
+	ENTRY(DW_ATE_complex_float),
+	ENTRY(DW_ATE_float),
+	ENTRY(DW_ATE_signed),
+	ENTRY(DW_ATE_signed_char),
+	ENTRY(DW_ATE_unsigned),
+	ENTRY(DW_ATE_unsigned_char),
+	ENTRY(DW_ATE_imaginary_float),
+	ENTRY(DW_ATE_packed_decimal),
+	ENTRY(DW_ATE_numeric_string),
+	ENTRY(DW_ATE_edited),
+	ENTRY(DW_ATE_signed_fixed),
+	ENTRY(DW_ATE_unsigned_fixed),
+	ENTRY(DW_ATE_decimal_float),
+	ENTRY(DW_ATE_UTF),
+	ENTRY(DW_ATE_UCS),
+	ENTRY(DW_ATE_ASCII),
+};
+
+_Static_assert(DW_ATE_MAX == DW_ATE_ASCII + 1);
+
+static const char *const dw_cc_names[] = {
+	ENTRY(DW_CC_normal),
+	ENTRY(DW_CC_program),
+	ENTRY(DW_CC_nocall),
+	ENTRY(DW_CC_pass_by_reference),
+	ENTRY(DW_CC_pass_by_value),
+};
+
+_Static_assert(DW_CC_MAX == DW_CC_pass_by_value + 1);
+
+static const char *const dw_ut_names[] = {
+	ENTRY(DW_UT_compile),
+	ENTRY(DW_UT_type),
+	ENTRY(DW_UT_partial),
+	ENTRY(DW_UT_skeleton),
+	ENTRY(DW_UT_split_compile),
+	ENTRY(DW_UT_split_type),
+};
+
+_Static_assert(DW_UT_MAX == DW_UT_split_type + 1);
+
+static const char *const dw_tag_names[] = {
+	ENTRY(DW_TAG_array_type),
+	ENTRY(DW_TAG_class_type),
+	ENTRY(DW_TAG_entry_point),
+	ENTRY(DW_TAG_enumeration_type),
+	ENTRY(DW_TAG_formal_parameter),
+	ENTRY(DW_TAG_global_subroutine),
+	ENTRY(DW_TAG_global_variable),
+	ENTRY(DW_TAG_imported_declaration),
+	ENTRY(DW_TAG_reserved_0x09),
+	ENTRY(DW_TAG_label),
+	ENTRY(DW_TAG_lexical_block),
+	ENTRY(DW_TAG_local_variable),
+	ENTRY(DW_TAG_member),
+	ENTRY(DW_TAG_reserved_0x0e),
+	ENTRY(DW_TAG_pointer_type),
+	ENTRY(DW_TAG_reference_type),
+	ENTRY(DW_TAG_compile_unit),
+	ENTRY(DW_TAG_string_type),
+	ENTRY(DW_TAG_structure_type),
+	ENTRY(DW_TAG_subroutine),
+	ENTRY(DW_TAG_subroutine_type),
+	ENTRY(DW_TAG_typedef),
+	ENTRY(DW_TAG_union_type),
+	ENTRY(DW_TAG_unspecified_parameters),
+	ENTRY(DW_TAG_variant),
+	ENTRY(DW_TAG_common_block),
+	ENTRY(DW_TAG_common_inclusion),
+	ENTRY(DW_TAG_inheritance),
+	ENTRY(DW_TAG_inlined_subroutine),
+	ENTRY(DW_TAG_module),
+	ENTRY(DW_TAG_ptr_to_member_type),
+	ENTRY(DW_TAG_set_type),
+	ENTRY(DW_TAG_subrange_type),
+	ENTRY(DW_TAG_with_stmt),
+	ENTRY(DW_TAG_access_declaration),
+	ENTRY(DW_TAG_base_type),
+	ENTRY(DW_TAG_catch_block),
+	ENTRY(DW_TAG_const_type),
+	ENTRY(DW_TAG_constant),
+	ENTRY(DW_TAG_enumerator),
+	ENTRY(DW_TAG_file_type),
+	ENTRY(DW_TAG_friend),
+	ENTRY(DW_TAG_namelist),
+	ENTRY(DW_TAG_namelist_item),
+	ENTRY(DW_TAG_packed_type),
+	ENTRY(DW_TAG_subprogram),
+	ENTRY(DW_TAG_template_type_parameter),
+	ENTRY(DW_TAG_template_value_parameter),
+	ENTRY(DW_TAG_thrown_type),
+	ENTRY(DW_TAG_try_block),
+	ENTRY(DW_TAG_variant_part),
+	ENTRY(DW_TAG_variable),
+	ENTRY(DW_TAG_volatile_type),
+	ENTRY(DW_TAG_dwarf_procedure),
+	ENTRY(DW_TAG_restrict_type),
+	ENTRY(DW_TAG_interface_type),
+	ENTRY(DW_TAG_namespace),
+	ENTRY(DW_TAG_imported_module),
+	ENTRY(DW_TAG_unspecified_type),
+	ENTRY(DW_TAG_partial_unit),
+	ENTRY(DW_TAG_imported_unit),
+	ENTRY(DW_TAG_mutable_type),
+	ENTRY(DW_TAG_condition),
+	ENTRY(DW_TAG_shared_type),
+	ENTRY(DW_TAG_type_unit),
+	ENTRY(DW_TAG_rvalue_reference_type),
+	ENTRY(DW_TAG_template_alias),
+	ENTRY(DW_TAG_coarray_type),
+	ENTRY(DW_TAG_generic_subrange),
+	ENTRY(DW_TAG_dynamic_type),
+	ENTRY(DW_TAG_atomic_type),
+	ENTRY(DW_TAG_call_site),
+	ENTRY(DW_TAG_call_site_parameter),
+	ENTRY(DW_TAG_skeleton_unit),
+	ENTRY(DW_TAG_immutable_type),
+};
+
+_Static_assert(DW_TAG_MAX == DW_TAG_immutable_type + 1);
+
+static const char *const dw_form_names[] = {
+	ENTRY(DW_FORM_addr),
+	ENTRY(DW_FORM_reserved_0x02),
+	ENTRY(DW_FORM_block2),
+	ENTRY(DW_FORM_block4),
+	ENTRY(DW_FORM_data2),
+	ENTRY(DW_FORM_data4),
+	ENTRY(DW_FORM_data8),
+	ENTRY(DW_FORM_string),
+	ENTRY(DW_FORM_block),
+	ENTRY(DW_FORM_block1),
+	ENTRY(DW_FORM_data1),
+	ENTRY(DW_FORM_flag),
+	ENTRY(DW_FORM_sdata),
+	ENTRY(DW_FORM_strp),
+	ENTRY(DW_FORM_udata),
+	ENTRY(DW_FORM_ref_addr),
+	ENTRY(DW_FORM_ref1),
+	ENTRY(DW_FORM_ref2),
+	ENTRY(DW_FORM_ref4),
+	ENTRY(DW_FORM_ref8),
+	ENTRY(DW_FORM_ref_udata),
+	ENTRY(DW_FORM_indirect),
+	ENTRY(DW_FORM_sec_offset),
+	ENTRY(DW_FORM_exprloc),
+	ENTRY(DW_FORM_flag_present),
+	ENTRY(DW_FORM_strx),
+	ENTRY(DW_FORM_addrx),
+	ENTRY(DW_FORM_ref_sup4),
+	ENTRY(DW_FORM_strp_sup),
+	ENTRY(DW_FORM_data16),
+	ENTRY(DW_FORM_line_strp),
+	ENTRY(DW_FORM_ref_sig8),
+	ENTRY(DW_FORM_implicit_const),
+	ENTRY(DW_FORM_loclistx),
+	ENTRY(DW_FORM_rnglistx),
+	ENTRY(DW_FORM_ref_sup8),
+	ENTRY(DW_FORM_strx1),
+	ENTRY(DW_FORM_strx2),
+	ENTRY(DW_FORM_strx3),
+	ENTRY(DW_FORM_strx4),
+	ENTRY(DW_FORM_addrx1),
+	ENTRY(DW_FORM_addrx2),
+	ENTRY(DW_FORM_addrx3),
+	ENTRY(DW_FORM_addrx4),
+};
+
+_Static_assert(DW_FORM_MAX == DW_FORM_addrx4 + 1);
+
+static const char *const dw_op_names[] = {
+	ENTRY(DW_OP_reg),
+	ENTRY(DW_OP_basereg),
+	ENTRY(DW_OP_addr),
+	ENTRY(DW_OP_const),
+	ENTRY(DW_OP_deref2),
+	ENTRY(DW_OP_deref),
+	ENTRY(DW_OP_add),
+	ENTRY(DW_OP_const1u),
+	ENTRY(DW_OP_const1s),
+	ENTRY(DW_OP_const2u),
+	ENTRY(DW_OP_const2s),
+	ENTRY(DW_OP_const4u),
+	ENTRY(DW_OP_const4s),
+	ENTRY(DW_OP_const8u),
+	ENTRY(DW_OP_const8s),
+	ENTRY(DW_OP_constu),
+	ENTRY(DW_OP_consts),
+	ENTRY(DW_OP_dup),
+	ENTRY(DW_OP_drop),
+	ENTRY(DW_OP_over),
+	ENTRY(DW_OP_pick),
+	ENTRY(DW_OP_swap),
+	ENTRY(DW_OP_rot),
+	ENTRY(DW_OP_xderef),
+	ENTRY(DW_OP_abs),
+	ENTRY(DW_OP_and),
+	ENTRY(DW_OP_div),
+	ENTRY(DW_OP_minus),
+	ENTRY(DW_OP_mod),
+	ENTRY(DW_OP_mul),
+	ENTRY(DW_OP_neg),
+	ENTRY(DW_OP_not),
+	ENTRY(DW_OP_or),
+	ENTRY(DW_OP_plus),
+	ENTRY(DW_OP_plus_uconst),
+	ENTRY(DW_OP_shl),
+	ENTRY(DW_OP_shr),
+	ENTRY(DW_OP_shra),
+	ENTRY(DW_OP_xor),
+	ENTRY(DW_OP_bra),
+	ENTRY(DW_OP_eq),
+	ENTRY(DW_OP_ge),
+	ENTRY(DW_OP_gt),
+	ENTRY(DW_OP_le),
+	ENTRY(DW_OP_lt),
+	ENTRY(DW_OP_ne),
+	ENTRY(DW_OP_skip),
+	ENTRY(DW_OP_lit0),
+	ENTRY(DW_OP_lit1),
+	ENTRY(DW_OP_lit2),
+	ENTRY(DW_OP_lit3),
+	ENTRY(DW_OP_lit4),
+	ENTRY(DW_OP_lit5),
+	ENTRY(DW_OP_lit6),
+	ENTRY(DW_OP_lit7),
+	ENTRY(DW_OP_lit8),
+	ENTRY(DW_OP_lit9),
+	ENTRY(DW_OP_lit10),
+	ENTRY(DW_OP_lit11),
+	ENTRY(DW_OP_lit12),
+	ENTRY(DW_OP_lit13),
+	ENTRY(DW_OP_lit14),
+	ENTRY(DW_OP_lit15),
+	ENTRY(DW_OP_lit16),
+	ENTRY(DW_OP_lit17),
+	ENTRY(DW_OP_lit18),
+	ENTRY(DW_OP_lit19),
+	ENTRY(DW_OP_lit20),
+	ENTRY(DW_OP_lit21),
+	ENTRY(DW_OP_lit22),
+	ENTRY(DW_OP_lit23),
+	ENTRY(DW_OP_lit24),
+	ENTRY(DW_OP_lit25),
+	ENTRY(DW_OP_lit26),
+	ENTRY(DW_OP_lit27),
+	ENTRY(DW_OP_lit28),
+	ENTRY(DW_OP_lit29),
+	ENTRY(DW_OP_lit30),
+	ENTRY(DW_OP_lit31),
+	ENTRY(DW_OP_reg0),
+	ENTRY(DW_OP_reg1),
+	ENTRY(DW_OP_reg2),
+	ENTRY(DW_OP_reg3),
+	ENTRY(DW_OP_reg4),
+	ENTRY(DW_OP_reg5),
+	ENTRY(DW_OP_reg6),
+	ENTRY(DW_OP_reg7),
+	ENTRY(DW_OP_reg8),
+	ENTRY(DW_OP_reg9),
+	ENTRY(DW_OP_reg10),
+	ENTRY(DW_OP_reg11),
+	ENTRY(DW_OP_reg12),
+	ENTRY(DW_OP_reg13),
+	ENTRY(DW_OP_reg14),
+	ENTRY(DW_OP_reg15),
+	ENTRY(DW_OP_reg16),
+	ENTRY(DW_OP_reg17),
+	ENTRY(DW_OP_reg18),
+	ENTRY(DW_OP_reg19),
+	ENTRY(DW_OP_reg20),
+	ENTRY(DW_OP_reg21),
+	ENTRY(DW_OP_reg22),
+	ENTRY(DW_OP_reg23),
+	ENTRY(DW_OP_reg24),
+	ENTRY(DW_OP_reg25),
+	ENTRY(DW_OP_reg26),
+	ENTRY(DW_OP_reg27),
+	ENTRY(DW_OP_reg28),
+	ENTRY(DW_OP_reg29),
+	ENTRY(DW_OP_reg30),
+	ENTRY(DW_OP_reg31),
+	ENTRY(DW_OP_breg0),
+	ENTRY(DW_OP_breg1),
+	ENTRY(DW_OP_breg2),
+	ENTRY(DW_OP_breg3),
+	ENTRY(DW_OP_breg4),
+	ENTRY(DW_OP_breg5),
+	ENTRY(DW_OP_breg6),
+	ENTRY(DW_OP_breg7),
+	ENTRY(DW_OP_breg8),
+	ENTRY(DW_OP_breg9),
+	ENTRY(DW_OP_breg10),
+	ENTRY(DW_OP_breg11),
+	ENTRY(DW_OP_breg12),
+	ENTRY(DW_OP_breg13),
+	ENTRY(DW_OP_breg14),
+	ENTRY(DW_OP_breg15),
+	ENTRY(DW_OP_breg16),
+	ENTRY(DW_OP_breg17),
+	ENTRY(DW_OP_breg18),
+	ENTRY(DW_OP_breg19),
+	ENTRY(DW_OP_breg20),
+	ENTRY(DW_OP_breg21),
+	ENTRY(DW_OP_breg22),
+	ENTRY(DW_OP_breg23),
+	ENTRY(DW_OP_breg24),
+	ENTRY(DW_OP_breg25),
+	ENTRY(DW_OP_breg26),
+	ENTRY(DW_OP_breg27),
+	ENTRY(DW_OP_breg28),
+	ENTRY(DW_OP_breg29),
+	ENTRY(DW_OP_breg30),
+	ENTRY(DW_OP_breg31),
+	ENTRY(DW_OP_regx),
+	ENTRY(DW_OP_fbreg),
+	ENTRY(DW_OP_bregx),
+	ENTRY(DW_OP_piece),
+	ENTRY(DW_OP_deref_size),
+	ENTRY(DW_OP_xderef_size),
+	ENTRY(DW_OP_nop),
+	ENTRY(DW_OP_push_object_address),
+	ENTRY(DW_OP_call2),
+	ENTRY(DW_OP_call4),
+	ENTRY(DW_OP_call_ref),
+	ENTRY(DW_OP_form_tls_address),
+	ENTRY(DW_OP_call_frame_cfa),
+	ENTRY(DW_OP_bit_piece),
+	ENTRY(DW_OP_implicit_value),
+	ENTRY(DW_OP_stack_value),
+	ENTRY(DW_OP_implicit_pointer),
+	ENTRY(DW_OP_addrx),
+	ENTRY(DW_OP_constx),
+	ENTRY(DW_OP_entry_value),
+	ENTRY(DW_OP_const_type),
+	ENTRY(DW_OP_regval_type),
+	ENTRY(DW_OP_deref_type),
+	ENTRY(DW_OP_xderef_type),
+	ENTRY(DW_OP_convert),
+	ENTRY(DW_OP_reinterpret),
+};
+
+_Static_assert(DW_OP_MAX == DW_OP_reinterpret + 1);
+
+static const char *const dw_lle_names[] = {
+	ENTRY(DW_LLE_end_of_list),
+	ENTRY(DW_LLE_base_addressx),
+	ENTRY(DW_LLE_startx_endx),
+	ENTRY(DW_LLE_startx_length),
+	ENTRY(DW_LLE_offset_pair),
+	ENTRY(DW_LLE_default_location),
+	ENTRY(DW_LLE_base_address),
+	ENTRY(DW_LLE_start_end),
+	ENTRY(DW_LLE_start_length),
+};
+
+_Static_assert(DW_LLE_MAX == DW_LLE_start_length + 1);
+
+static const char *const dw_ds_names[] = {
+	ENTRY(DW_DS_unsigned),
+	ENTRY(DW_DS_leading_overpunch),
+	ENTRY(DW_DS_trailing_overpunch),
+	ENTRY(DW_DS_leading_separate),
+	ENTRY(DW_DS_trailing_separate),
+};
+
+_Static_assert(DW_DS_MAX == DW_DS_trailing_separate + 1);
+
+static const char *const dw_end_names[] = {
+	ENTRY(DW_END_default),
+	ENTRY(DW_END_big),
+	ENTRY(DW_END_little),
+};
+
+_Static_assert(DW_END_MAX == DW_END_little + 1);
+
+static const char *const dw_vis_names[] = {
+	ENTRY(DW_VIS_local),
+	ENTRY(DW_VIS_exported),
+	ENTRY(DW_VIS_qualified),
+};
+
+_Static_assert(DW_VIS_MAX == DW_VIS_qualified + 1);
+
+static const char *const dw_virtuality_names[] = {
+	ENTRY(DW_VIRTUALITY_none),
+	ENTRY(DW_VIRTUALITY_virtual),
+	ENTRY(DW_VIRTUALITY_pure_virtual),
+};
+
+_Static_assert(DW_VIRTUALITY_MAX == DW_VIRTUALITY_pure_virtual + 1);
+
+static const char *const dw_lang_names[] = {
+	ENTRY(DW_LANG_C89),
+	ENTRY(DW_LANG_C),
+	ENTRY(DW_LANG_Ada83),
+	ENTRY(DW_LANG_C_plus_plus),
+	ENTRY(DW_LANG_Cobol74),
+	ENTRY(DW_LANG_Cobol85),
+	ENTRY(DW_LANG_Fortran77),
+	ENTRY(DW_LANG_Fortran90),
+	ENTRY(DW_LANG_Pascal83),
+	ENTRY(DW_LANG_Modula2),
+	ENTRY(DW_LANG_Java),
+	ENTRY(DW_LANG_C99),
+	ENTRY(DW_LANG_Ada95),
+	ENTRY(DW_LANG_Fortran95),
+	ENTRY(DW_LANG_PLI),
+	ENTRY(DW_LANG_ObjC),
+	ENTRY(DW_LANG_ObjC_plus_plus),
+	ENTRY(DW_LANG_UPC),
+	ENTRY(DW_LANG_D),
+	ENTRY(DW_LANG_Python),
+	ENTRY(DW_LANG_OpenCL),
+	ENTRY(DW_LANG_Go),
+	ENTRY(DW_LANG_Modula3),
+	ENTRY(DW_LANG_Haskell),
+	ENTRY(DW_LANG_C_plus_plus_03),
+	ENTRY(DW_LANG_C_plus_plus_11),
+	ENTRY(DW_LANG_OCaml),
+	ENTRY(DW_LANG_Rust),
+	ENTRY(DW_LANG_C11),
+	ENTRY(DW_LANG_Swift),
+	ENTRY(DW_LANG_Julia),
+	ENTRY(DW_LANG_Dylan),
+	ENTRY(DW_LANG_C_plus_plus_14),
+	ENTRY(DW_LANG_Fortran03),
+	ENTRY(DW_LANG_Fortran08),
+	ENTRY(DW_LANG_RenderScript),
+	ENTRY(DW_LANG_BLISS),
+};
+
+_Static_assert(DW_LANG_MAX == DW_LANG_BLISS + 1);
+
+static const char *const dw_id_names[] = {
+	ENTRY(DW_ID_case_sensitive),
+	ENTRY(DW_ID_up_case),
+	ENTRY(DW_ID_down_case),
+	ENTRY(DW_ID_case_insensitive),
+};
+
+_Static_assert(DW_ID_MAX == DW_ID_case_insensitive + 1);
+
+static const char *const dw_lns_names[] = {
+	ENTRY(DW_LNS_copy),
+	ENTRY(DW_LNS_advance_pc),
+	ENTRY(DW_LNS_advance_line),
+	ENTRY(DW_LNS_set_file),
+	ENTRY(DW_LNS_set_column),
+	ENTRY(DW_LNS_negate_stmt),
+	ENTRY(DW_LNS_set_basic_block),
+	ENTRY(DW_LNS_const_add_pc),
+	ENTRY(DW_LNS_fixed_advance_pc),
+	ENTRY(DW_LNS_set_prologue_end),
+	ENTRY(DW_LNS_set_epilogue_begin),
+	ENTRY(DW_LNS_set_isa),
+};
+
+_Static_assert(DW_LNS_MAX == DW_LNS_set_isa + 1);
+
+static const char *const dw_lne_names[] = {
+	ENTRY(DW_LNE_end_sequence),
+	ENTRY(DW_LNE_set_address),
+	ENTRY(DW_LNE_define_file),
+	ENTRY(DW_LNE_set_discriminator),
+};
+
+_Static_assert(DW_LNE_MAX == DW_LNE_set_discriminator + 1);
+
+static const char *const dw_lnct_names[] = {
+	ENTRY(DW_LNCT_path),
+	ENTRY(DW_LNCT_directory_index),
+	ENTRY(DW_LNCT_timestamp),
+	ENTRY(DW_LNCT_size),
+	ENTRY(DW_LNCT_MD5),
+};
+
+_Static_assert(DW_LNCT_MAX == DW_LNCT_MD5 + 1);
+
+static const char *const dw_inl_names[] = {
+	ENTRY(DW_INL_not_inlined),
+	ENTRY(DW_INL_inlined),
+	ENTRY(DW_INL_declared_not_inlined),
+	ENTRY(DW_INL_declared_inlined),
+};
+
+_Static_assert(DW_INL_MAX == DW_INL_declared_inlined + 1);
+
+static const char *const dw_ord_names[] = {
+	ENTRY(DW_ORD_row_major),
+	ENTRY(DW_ORD_col_major),
+};
+
+_Static_assert(DW_ORD_MAX == DW_ORD_col_major + 1);
+
+static const char *const dw_dsc_names[] = {
+	ENTRY(DW_DSC_label),
+	ENTRY(DW_DSC_range),
+};
+
+_Static_assert(DW_DSC_MAX == DW_DSC_range + 1);
+
+static const char *const dw_idx_names[] = {
+	ENTRY(DW_IDX_compile_unit),
+	ENTRY(DW_IDX_type_unit),
+	ENTRY(DW_IDX_die_offset),
+	ENTRY(DW_IDX_parent),
+	ENTRY(DW_IDX_type_hash),
+};
+
+_Static_assert(DW_IDX_MAX == DW_IDX_type_hash + 1);
+
+static const char *const dw_defaulted_names[] = {
+	ENTRY(DW_DEFAULTED_no),
+	ENTRY(DW_DEFAULTED_in_class),
+	ENTRY(DW_DEFAULTED_out_of_class),
+};
+
+_Static_assert(DW_DEFAULTED_MAX == DW_DEFAULTED_out_of_class + 1);
+
+static const char *const dw_macro_names[] = {
+	ENTRY(DW_MACRO_define),
+	ENTRY(DW_MACRO_undef),
+	ENTRY(DW_MACRO_start_file),
+	ENTRY(DW_MACRO_end_file),
+	ENTRY(DW_MACRO_define_strp),
+	ENTRY(DW_MACRO_undef_strp),
+	ENTRY(DW_MACRO_import),
+	ENTRY(DW_MACRO_define_sup),
+	ENTRY(DW_MACRO_undef_sup),
+	ENTRY(DW_MACRO_import_sup),
+	ENTRY(DW_MACRO_define_strx),
+	ENTRY(DW_MACRO_undef_strx),
+};
+
+_Static_assert(DW_MACRO_MAX == DW_MACRO_undef_strx + 1);
+
+static const char *const dw_cfa_names[] = {
+	ENTRY(DW_CFA_nop),
+	ENTRY(DW_CFA_set_loc),
+	ENTRY(DW_CFA_advance_loc1),
+	ENTRY(DW_CFA_advance_loc2),
+	ENTRY(DW_CFA_advance_loc4),
+	ENTRY(DW_CFA_offset_extended),
+	ENTRY(DW_CFA_restore_extended),
+	ENTRY(DW_CFA_undefined),
+	ENTRY(DW_CFA_same_value),
+	ENTRY(DW_CFA_register),
+	ENTRY(DW_CFA_remember_state),
+	ENTRY(DW_CFA_restore_state),
+	ENTRY(DW_CFA_def_cfa),
+	ENTRY(DW_CFA_def_cfa_register),
+	ENTRY(DW_CFA_def_cfa_offset),
+	ENTRY(DW_CFA_def_cfa_expression),
+	ENTRY(DW_CFA_expression),
+	ENTRY(DW_CFA_offset_extended_sf),
+	ENTRY(DW_CFA_def_cfa_sf),
+	ENTRY(DW_CFA_def_cfa_offset_sf),
+	ENTRY(DW_CFA_val_offset),
+	ENTRY(DW_CFA_val_offset_sf),
+	ENTRY(DW_CFA_val_expression),
+};
+
+_Static_assert(DW_CFA_MAX == DW_CFA_val_expression + 1);
+
+static const char *const dw_rle_names[] = {
+	ENTRY(DW_RLE_end_of_list),
+	ENTRY(DW_RLE_base_addressx),
+	ENTRY(DW_RLE_startx_endx),
+	ENTRY(DW_RLE_startx_length),
+	ENTRY(DW_RLE_offset_pair),
+	ENTRY(DW_RLE_base_address),
+	ENTRY(DW_RLE_start_end),
+	ENTRY(DW_RLE_start_length),
+};
+
+_Static_assert(DW_RLE_MAX == DW_RLE_start_length + 1);
+
+#define NAME_FUNC(prefix) \
+	const char *prefix##_name(prefix##_t val) \
+	{ \
+		if (val >= sizeof(prefix##_names) / sizeof(const char *)) return NULL; \
+		return prefix##_names[val]; \
+	}
+
+NAME_FUNC(dw_access);
+NAME_FUNC(dw_at);
+NAME_FUNC(dw_ate);
+NAME_FUNC(dw_cc);
+NAME_FUNC(dw_cfa);
+NAME_FUNC(dw_defaulted);
+NAME_FUNC(dw_ds);
+NAME_FUNC(dw_dsc);
+NAME_FUNC(dw_end);
+NAME_FUNC(dw_form);
+NAME_FUNC(dw_id);
+NAME_FUNC(dw_idx);
+NAME_FUNC(dw_inl);
+NAME_FUNC(dw_lang);
+NAME_FUNC(dw_lle);
+NAME_FUNC(dw_lnct);
+NAME_FUNC(dw_lne);
+NAME_FUNC(dw_lns);
+NAME_FUNC(dw_macro);
+NAME_FUNC(dw_op);
+NAME_FUNC(dw_ord);
+NAME_FUNC(dw_rle);
+NAME_FUNC(dw_tag);
+NAME_FUNC(dw_ut);
+NAME_FUNC(dw_virtuality);
+NAME_FUNC(dw_vis);
Index: kernel/generic/src/debug/profile.c
===================================================================
--- kernel/generic/src/debug/profile.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ kernel/generic/src/debug/profile.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2010 Martin Decky
+ * 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 kernel_generic_debug
+ * @{
+ */
+
+/**
+ * @file
+ * @brief Kernel instrumentation functions.
+ */
+
+#ifdef CONFIG_TRACE
+
+#include <debug.h>
+#include <symtab.h>
+#include <errno.h>
+#include <stdio.h>
+
+void __cyg_profile_func_enter(void *fn, void *call_site)
+{
+	const char *fn_sym = symtab_fmt_name_lookup((uintptr_t) fn);
+
+	const char *call_site_sym;
+	uintptr_t call_site_off;
+
+	if (symtab_name_lookup((uintptr_t) call_site, &call_site_sym,
+	    &call_site_off) == EOK)
+		printf("%s()+%p->%s()\n", call_site_sym,
+		    (void *) call_site_off, fn_sym);
+	else
+		printf("->%s()\n", fn_sym);
+}
+
+void __cyg_profile_func_exit(void *fn, void *call_site)
+{
+	const char *fn_sym = symtab_fmt_name_lookup((uintptr_t) fn);
+
+	const char *call_site_sym;
+	uintptr_t call_site_off;
+
+	if (symtab_name_lookup((uintptr_t) call_site, &call_site_sym,
+	    &call_site_off) == EOK)
+		printf("%s()+%p<-%s()\n", call_site_sym,
+		    (void *) call_site_off, fn_sym);
+	else
+		printf("<-%s()\n", fn_sym);
+}
+
+#endif /* CONFIG_TRACE */
+
+/** @}
+ */
Index: kernel/generic/src/debug/sections.c
===================================================================
--- kernel/generic/src/debug/sections.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ kernel/generic/src/debug/sections.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <debug/sections.h>
+#include <debug/register.h>
+#include <debug.h>
+
+#include <stdio.h>
+#include <str.h>
+
+#define DEBUGF dummy_printf
+
+debug_sections_t kernel_sections;
+
+struct section_manifest {
+	const char *name;
+	const void **address_field;
+	size_t *size_field;
+};
+
+static void _check_string_section(const char *section, size_t *section_size)
+{
+	while (*section_size > 0 && section[*section_size - 1] != 0) {
+		(*section_size)--;
+	}
+}
+
+debug_sections_t get_debug_sections(const void *elf, size_t elf_size)
+{
+	debug_sections_t out = { };
+
+#define section(name) { "." #name, (const void **)&out.name, &out.name##_size }
+
+	const struct section_manifest manifest[] = {
+		section(debug_aranges),
+		section(debug_info),
+		section(debug_abbrev),
+		section(debug_line),
+		section(debug_str),
+		section(debug_line_str),
+		section(debug_rnglists),
+		section(eh_frame_hdr),
+		section(eh_frame),
+		section(symtab),
+		section(strtab),
+	};
+
+	const size_t manifest_len = sizeof(manifest) / sizeof(struct section_manifest);
+
+	const void *data_end = elf + elf_size;
+
+	/*
+	 * While this data is technically "trusted", insofar as it is provided
+	 * by the bootloader, it's not critical, so we try to make sure malformed or
+	 * misconfigured debug data doesn't crash the kernel.
+	 */
+
+	if (elf_size < sizeof(elf_header_t)) {
+		printf("bad debug data: too short\n");
+		return out;
+	}
+
+	if (((uintptr_t) elf) % 8 != 0) {
+		printf("bad debug data: unaligned input\n");
+		return out;
+	}
+
+	const elf_header_t *hdr = elf;
+
+	if (hdr->e_ident[0] != ELFMAG0 || hdr->e_ident[1] != ELFMAG1 ||
+	    hdr->e_ident[2] != ELFMAG2 || hdr->e_ident[3] != ELFMAG3) {
+		printf("bad debug data: wrong ELF magic bytes\n");
+		return out;
+	}
+
+	if (hdr->e_shentsize != sizeof(elf_section_header_t)) {
+		printf("bad debug data: wrong e_shentsize\n");
+		return out;
+	}
+
+	/* Get section header table. */
+	const elf_section_header_t *shdr = elf + hdr->e_shoff;
+	size_t shdr_len = hdr->e_shnum;
+
+	if ((void *) &shdr[shdr_len] > data_end) {
+		printf("bad debug data: truncated section header table\n");
+		return out;
+	}
+
+	if (hdr->e_shstrndx >= shdr_len) {
+		printf("bad debug data: string table index out of range\n");
+		return out;
+	}
+
+	/* Get data on section name string table. */
+	const elf_section_header_t *shstr = &shdr[hdr->e_shstrndx];
+	const char *shstrtab = elf + shstr->sh_offset;
+	size_t shstrtab_size = shstr->sh_size;
+
+	if ((void *) shstrtab + shstrtab_size > data_end) {
+		printf("bad debug data: truncated string table\n");
+		return out;
+	}
+
+	/* Check NUL-terminator. */
+	_check_string_section(shstrtab, &shstrtab_size);
+
+	if (shstrtab_size == 0) {
+		printf("bad debug data: empty or non-null-terminated string table\n");
+		return out;
+	}
+
+	/* List all present sections. */
+	for (size_t i = 0; i < shdr_len; i++) {
+		if (shdr[i].sh_type == SHT_NULL || shdr[i].sh_type == SHT_NOBITS)
+			continue;
+
+		if (shdr[i].sh_name >= shstrtab_size) {
+			printf("bad debug data: string table index out of range\n");
+			continue;
+		}
+
+		const char *name = shstrtab + shdr[i].sh_name;
+		size_t offset = shdr[i].sh_offset;
+		size_t size = shdr[i].sh_size;
+
+		DEBUGF("section '%s': offset %zd (%zd bytes)\n", name, offset, size);
+
+		if (elf + offset + size > data_end) {
+			printf("bad debug data: truncated section %s\n", name);
+			continue;
+		}
+
+		for (size_t i = 0; i < manifest_len; i++) {
+			if (str_cmp(manifest[i].name, name) == 0) {
+				*manifest[i].address_field = elf + offset;
+				*manifest[i].size_field = size;
+				break;
+			}
+		}
+	}
+
+	/* Check NUL-terminator in .strtab, .debug_str and .debug_line_str */
+	_check_string_section(out.strtab, &out.strtab_size);
+	_check_string_section(out.debug_str, &out.debug_str_size);
+	_check_string_section(out.debug_line_str, &out.debug_line_str_size);
+
+	return out;
+}
+
+void register_debug_data(const void *elf, size_t elf_size)
+{
+	kernel_sections = get_debug_sections(elf, elf_size);
+}
Index: kernel/generic/src/debug/stacktrace.c
===================================================================
--- kernel/generic/src/debug/stacktrace.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/debug/stacktrace.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -39,4 +39,6 @@
 #include <stdio.h>
 
+#include <debug/line.h>
+
 #define STACK_FRAMES_MAX  20
 
@@ -44,6 +46,5 @@
 {
 	int cnt = 0;
-	const char *symbol;
-	uintptr_t offset;
+
 	uintptr_t fp;
 	uintptr_t pc;
@@ -51,11 +52,36 @@
 	while ((cnt++ < STACK_FRAMES_MAX) &&
 	    (ops->stack_trace_context_validate(ctx))) {
+
+		const char *symbol = NULL;
+		uintptr_t symbol_addr = 0;
+		const char *file_name = NULL;
+		const char *dir_name = NULL;
+		int line = 0;
+		int column = 0;
+
+		/*
+		 * If this isn't the first frame, move pc back by one byte to read the
+		 * position of the call instruction, not the return address.
+		 */
+		pc = cnt == 1 ? ctx->pc : ctx->pc - 1;
+
 		if (ops->symbol_resolve &&
-		    ops->symbol_resolve(ctx->pc, &symbol, &offset)) {
-			if (offset)
-				printf("%p: %s()+%p\n", (void *) ctx->fp,
-				    symbol, (void *) offset);
-			else
-				printf("%p: %s()\n", (void *) ctx->fp, symbol);
+		    ops->symbol_resolve(pc, 0, &symbol, &symbol_addr, &file_name, &dir_name, &line, &column)) {
+
+			if (symbol == NULL)
+				symbol = "<unknown>";
+
+			if (file_name == NULL && line == 0) {
+				printf("%p: %24s()+%zu\n", (void *) ctx->fp, symbol, ctx->pc - symbol_addr);
+			} else {
+				if (file_name == NULL)
+					file_name = "<unknown>";
+				if (dir_name == NULL)
+					dir_name = "<unknown>";
+
+				printf("%p: %20s()+%zu\t %s/%s:%d:%d\n",
+				    (void *) ctx->fp, symbol, ctx->pc - symbol_addr,
+				    dir_name, file_name, line, column);
+			}
 		} else
 			printf("%p: %p()\n", (void *) ctx->fp, (void *) ctx->pc);
@@ -104,7 +130,30 @@
 
 static bool
-kernel_symbol_resolve(uintptr_t addr, const char **sp, uintptr_t *op)
+resolve_kernel_address(uintptr_t addr, int op_index,
+    const char **symbol, uintptr_t *symbol_addr,
+    const char **filename, const char **dirname,
+    int *line, int *column)
 {
-	return (symtab_name_lookup(addr, sp, op) == 0);
+	*symbol_addr = 0;
+	*symbol = symtab_name_lookup(addr, symbol_addr, &kernel_sections);
+
+	return debug_line_get_address_info(&kernel_sections, addr, op_index, filename, dirname, line, column) || *symbol_addr != 0;
+}
+
+static bool
+resolve_uspace_address(uintptr_t addr, int op_index,
+    const char **symbol, uintptr_t *symbol_addr,
+    const char **filename, const char **dirname,
+    int *line, int *column)
+{
+	if (TASK->debug_sections == NULL)
+		return false;
+
+	debug_sections_t *scs = TASK->debug_sections;
+
+	*symbol_addr = 0;
+	*symbol = symtab_name_lookup(addr, symbol_addr, scs);
+
+	return debug_line_get_address_info(scs, addr, op_index, filename, dirname, line, column) || *symbol_addr != 0;
 }
 
@@ -113,5 +162,5 @@
 	.frame_pointer_prev = kernel_frame_pointer_prev,
 	.return_address_get = kernel_return_address_get,
-	.symbol_resolve = kernel_symbol_resolve
+	.symbol_resolve = resolve_kernel_address,
 };
 
@@ -120,5 +169,5 @@
 	.frame_pointer_prev = uspace_frame_pointer_prev,
 	.return_address_get = uspace_return_address_get,
-	.symbol_resolve = NULL
+	.symbol_resolve = resolve_uspace_address,
 };
 
Index: kernel/generic/src/debug/symtab.c
===================================================================
--- kernel/generic/src/debug/symtab.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/debug/symtab.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,47 +36,86 @@
  */
 
+#include <abi/elf.h>
+#include <byteorder.h>
+#include <console/prompt.h>
+#include <debug/sections.h>
+#include <errno.h>
+#include <proc/task.h>
+#include <stdio.h>
+#include <str.h>
 #include <symtab.h>
-#include <byteorder.h>
-#include <str.h>
-#include <stdio.h>
 #include <typedefs.h>
-#include <errno.h>
-#include <console/prompt.h>
-
-/** Get name of a symbol that seems most likely to correspond to address.
- *
- * @param addr   Address.
- * @param name   Place to store pointer to the symbol name.
- * @param offset Place to store offset from the symbol address.
- *
- * @return Zero on success or an error code, ENOENT if not found,
- *         ENOTSUP if symbol table not available.
- *
- */
-errno_t symtab_name_lookup(uintptr_t addr, const char **name, uintptr_t *offset)
-{
-#ifdef CONFIG_SYMTAB
-	size_t i;
-
-	for (i = 1; symbol_table[i].address_le; i++) {
-		if (addr < uint64_t_le2host(symbol_table[i].address_le))
+
+static inline const char *symtab_entry_name(debug_sections_t *scs, int entry)
+{
+	size_t index = scs->symtab[entry].st_name;
+
+	if (index >= scs->strtab_size)
+		return NULL;
+
+	return scs->strtab + index;
+}
+
+static inline size_t symtab_next(debug_sections_t *scs, size_t i)
+{
+	size_t symtab_len = scs->symtab_size / sizeof(elf_symbol_t);
+
+	for (; i < symtab_len; i++) {
+		const char *name = symtab_entry_name(scs, i);
+		int st_bind = elf_st_bind(scs->symtab[i].st_info);
+		int st_type = elf_st_type(scs->symtab[i].st_info);
+
+		if (st_bind == STB_LOCAL)
+			continue;
+
+		if (name == NULL || *name == '\0')
+			continue;
+
+		if (st_type == STT_FUNC || st_type == STT_OBJECT)
 			break;
 	}
 
-	if (addr >= uint64_t_le2host(symbol_table[i - 1].address_le)) {
-		*name = symbol_table[i - 1].symbol_name;
-		if (offset)
-			*offset = addr -
-			    uint64_t_le2host(symbol_table[i - 1].address_le);
-		return EOK;
-	}
-
-	*name = NULL;
-	return ENOENT;
-
-#else
-	*name = NULL;
-	return ENOTSUP;
-#endif
+	return i;
+}
+
+const char *symtab_name_lookup(uintptr_t addr, uintptr_t *symbol_addr, debug_sections_t *scs)
+{
+	const elf_symbol_t *symtab = scs->symtab;
+	size_t symtab_len = scs->symtab_size / sizeof(elf_symbol_t);
+	const char *strtab = scs->strtab;
+	size_t strtab_size = scs->strtab_size;
+
+	if (symtab == NULL || strtab == NULL)
+		return NULL;
+
+	uintptr_t closest_symbol_addr = 0;
+	uintptr_t closest_symbol_name = 0;
+
+	for (size_t i = symtab_next(scs, 0); i < symtab_len; i = symtab_next(scs, i + 1)) {
+		if (symtab[i].st_value > addr)
+			continue;
+
+		if (symtab[i].st_value + symtab[i].st_size > addr) {
+			closest_symbol_addr = symtab[i].st_value;
+			closest_symbol_name = symtab[i].st_name;
+			break;
+		}
+
+		if (symtab[i].st_value > closest_symbol_addr) {
+			closest_symbol_addr = symtab[i].st_value;
+			closest_symbol_name = symtab[i].st_name;
+		}
+	}
+
+	if (closest_symbol_addr == 0)
+		return NULL;
+
+	if (symbol_addr)
+		*symbol_addr = closest_symbol_addr;
+
+	if (closest_symbol_name >= strtab_size)
+		return NULL;
+
+	return strtab + closest_symbol_name;
 }
 
@@ -95,53 +134,9 @@
 const char *symtab_fmt_name_lookup(uintptr_t addr)
 {
-	const char *name;
-	errno_t rc = symtab_name_lookup(addr, &name, NULL);
-
-	switch (rc) {
-	case EOK:
-		return name;
-	case ENOENT:
-		return "unknown";
-	default:
-		return "N/A";
-	}
-}
-
-#ifdef CONFIG_SYMTAB
-
-/** Find symbols that match the parameter forward and print them.
- *
- * @param name     Search string
- * @param startpos Starting position, changes to found position
- *
- * @return Pointer to the part of string that should be completed or NULL.
- *
- */
-static const char *symtab_search_one(const char *name, size_t *startpos)
-{
-	size_t namelen = str_length(name);
-
-	size_t pos;
-	for (pos = *startpos; symbol_table[pos].address_le; pos++) {
-		const char *curname = symbol_table[pos].symbol_name;
-
-		/* Find a ':' in curname */
-		const char *colon = str_chr(curname, ':');
-		if (colon == NULL)
-			continue;
-
-		if (str_length(curname) < namelen)
-			continue;
-
-		if (str_lcmp(name, curname, namelen) == 0) {
-			*startpos = pos;
-			return (curname + str_lsize(curname, namelen));
-		}
-	}
-
-	return NULL;
-}
-
-#endif
+	const char *name = symtab_name_lookup(addr, NULL, &kernel_sections);
+	if (name == NULL)
+		name = "<unknown>";
+	return name;
+}
 
 /** Return address that corresponds to the entry.
@@ -152,34 +147,20 @@
  * @param addr Place to store symbol address
  *
- * @return Zero on success, ENOENT - not found, EOVERFLOW - duplicate
- *         symbol, ENOTSUP - no symbol information available.
+ * @return Zero on success, ENOENT - not found
  *
  */
 errno_t symtab_addr_lookup(const char *name, uintptr_t *addr)
 {
-#ifdef CONFIG_SYMTAB
-	size_t found = 0;
-	size_t pos = 0;
-	const char *hint;
-
-	while ((hint = symtab_search_one(name, &pos))) {
-		if (str_length(hint) == 0) {
-			*addr = uint64_t_le2host(symbol_table[pos].address_le);
-			found++;
-		}
-		pos++;
-	}
-
-	if (found > 1)
-		return EOVERFLOW;
-
-	if (found < 1)
-		return ENOENT;
-
-	return EOK;
-
-#else
-	return ENOTSUP;
-#endif
+	debug_sections_t *scs = &kernel_sections;
+	size_t symtab_len = scs->symtab_size / sizeof(elf_symbol_t);
+
+	for (size_t i = symtab_next(scs, 0); i < symtab_len; i = symtab_next(scs, i + 1)) {
+		if (str_cmp(name, symtab_entry_name(scs, i)) == 0) {
+			*addr = scs->symtab[i].st_value;
+			return EOK;
+		}
+	}
+
+	return ENOENT;
 }
 
@@ -187,44 +168,41 @@
 void symtab_print_search(const char *name)
 {
-#ifdef CONFIG_SYMTAB
-	size_t pos = 0;
-	while (symtab_search_one(name, &pos)) {
-		uintptr_t addr = uint64_t_le2host(symbol_table[pos].address_le);
-		char *realname = symbol_table[pos].symbol_name;
-		printf("%p: %s\n", (void *) addr, realname);
-		pos++;
-	}
-
-#else
-	printf("No symbol information available.\n");
-#endif
+	debug_sections_t *scs = &kernel_sections;
+	size_t symtab_len = scs->symtab_size / sizeof(elf_symbol_t);
+
+	if (scs->symtab == NULL || scs->strtab == NULL) {
+		printf("No symbol information available.\n");
+		return;
+	}
+
+	size_t namelen = str_length(name);
+
+	for (size_t i = symtab_next(scs, 0); i < symtab_len; i = symtab_next(scs, i + 1)) {
+		const char *n = symtab_entry_name(scs, i);
+
+		if (str_lcmp(name, n, namelen) == 0) {
+			printf("%p: %s\n", (void *) scs->symtab[i].st_value, n);
+		}
+	}
 }
 
 /** Symtab completion enum, see kernel/generic/include/kconsole.h */
-const char *symtab_hints_enum(const char *input, const char **help,
-    void **ctx)
-{
-#ifdef CONFIG_SYMTAB
+const char *symtab_hints_enum(const char *input, const char **help, void **ctx)
+{
+	debug_sections_t *scs = &kernel_sections;
+	size_t symtab_len = scs->symtab_size / sizeof(elf_symbol_t);
+
+	if (scs->symtab == NULL || scs->strtab == NULL)
+		return NULL;
+
+	if (help)
+		*help = NULL;
+
 	size_t len = str_length(input);
-	struct symtab_entry **entry = (struct symtab_entry **)ctx;
-
-	if (*entry == NULL)
-		*entry = symbol_table;
-
-	for (; (*entry)->address_le; (*entry)++) {
-		const char *curname = (*entry)->symbol_name;
-
-		/* Find a ':' in curname */
-		const char *colon = str_chr(curname, ':');
-		if (colon == NULL)
-			continue;
-
-		if (str_length(curname) < len)
-			continue;
+	for (size_t i = symtab_next(scs, (size_t) *ctx); i < symtab_len; i = symtab_next(scs, i + 1)) {
+		const char *curname = symtab_entry_name(scs, i);
 
 		if (str_lcmp(input, curname, len) == 0) {
-			(*entry)++;
-			if (help)
-				*help = NULL;
+			*ctx = (void *) (i + 1);
 			return (curname + str_lsize(curname, len));
 		}
@@ -232,8 +210,4 @@
 
 	return NULL;
-
-#else
-	return NULL;
-#endif
 }
 
Index: kernel/generic/src/debug/util.c
===================================================================
--- kernel/generic/src/debug/util.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ kernel/generic/src/debug/util.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "util.h"
+
+#include <stdio.h>
+#include <debug/sections.h>
+#include <debug/names.h>
+
+bool skip_data(unsigned form, const uint8_t **const data,
+    const uint8_t *data_end, unsigned width)
+{
+	// Skip data we don't care about reading.
+
+	size_t len;
+
+	switch (form) {
+	case DW_FORM_string:
+		skip_string(data, data_end);
+		break;
+
+	case DW_FORM_strp:
+	case DW_FORM_line_strp:
+	case DW_FORM_strp_sup:
+	case DW_FORM_sec_offset:
+		safe_increment(data, data_end, width);
+		break;
+
+	case DW_FORM_strx:
+	case DW_FORM_sdata:
+	case DW_FORM_udata:
+		skip_leb128(data, data_end);
+		break;
+
+	case DW_FORM_strx1:
+	case DW_FORM_data1:
+	case DW_FORM_flag:
+		safe_increment(data, data_end, 1);
+		break;
+
+	case DW_FORM_strx2:
+	case DW_FORM_data2:
+		safe_increment(data, data_end, 2);
+		break;
+
+	case DW_FORM_strx3:
+		safe_increment(data, data_end, 3);
+		break;
+
+	case DW_FORM_strx4:
+	case DW_FORM_data4:
+		safe_increment(data, data_end, 4);
+		break;
+
+	case DW_FORM_data8:
+		safe_increment(data, data_end, 8);
+		break;
+
+	case DW_FORM_data16:
+		safe_increment(data, data_end, 16);
+		break;
+
+	case DW_FORM_block:
+		len = read_uleb128(data, data_end);
+		safe_increment(data, data_end, len);
+		break;
+
+	case DW_FORM_block1:
+		len = read_byte(data, data_end);
+		safe_increment(data, data_end, len);
+		break;
+
+	case DW_FORM_block2:
+		len = read_uint16(data, data_end);
+		safe_increment(data, data_end, len);
+		break;
+
+	case DW_FORM_block4:
+		len = read_uint32(data, data_end);
+		safe_increment(data, data_end, len);
+		break;
+
+	default:
+		// Unknown FORM.
+		*data = data_end;
+		return false;
+	}
+
+	return true;
+}
+
+void print_format(const char *name, const uint8_t *format, const uint8_t *format_end)
+{
+	DEBUGF("%s: ", name);
+
+	while (format < format_end) {
+		uint64_t lnct = read_uleb128(&format, format_end);
+		uint64_t form = read_uleb128(&format, format_end);
+
+		const char *fname = dw_form_name(form);
+
+		if (fname)
+			DEBUGF("%s:%s, ", dw_lnct_name(lnct), fname);
+		else
+			DEBUGF("%s:unknown DW_FORM_(%" PRIu64 "), ", dw_lnct_name(lnct), form);
+	}
+
+	DEBUGF("\n");
+}
+
+void print_formatted_list(debug_sections_t *scs, const char *name,
+    const uint8_t *data, const uint8_t *const data_end,
+    const uint8_t *format, const uint8_t *format_end,
+    unsigned width)
+{
+	DEBUGF("%s: ", name);
+
+	while (data < data_end) {
+		const uint8_t *old_data = data;
+		const uint8_t *format_ptr = format;
+
+		while (format_ptr < format_end) {
+			uint64_t lnct = read_uleb128(&format_ptr, format_end);
+			uint64_t form = read_uleb128(&format_ptr, format_end);
+
+			DEBUGF("%s:%s:", dw_lnct_name(lnct), dw_form_name(form));
+			print_formed_data(scs, form, &data, data_end, width);
+			DEBUGF("\n");
+		}
+
+		if (data <= old_data)
+			break;
+	}
+
+	DEBUGF("\n");
+}
+
+void print_block(const uint8_t **const data,
+    const uint8_t *data_end, unsigned bytes)
+{
+	while (bytes > 0 && *data < data_end) {
+		DEBUGF("%02x ", **data);
+		(*data)++;
+		bytes--;
+	}
+}
+
+void print_formed_data(debug_sections_t *scs, unsigned form, const uint8_t **const data,
+    const uint8_t *data_end, unsigned width)
+{
+	size_t len;
+	uint64_t offset;
+
+	switch (form) {
+	case DW_FORM_string:
+		DEBUGF("\"%s\"", read_string(data, data_end));
+		break;
+
+	case DW_FORM_strp:
+	case DW_FORM_strp_sup:
+		offset = read_uint(data, data_end, width);
+		if (offset >= scs->debug_str_size)
+			DEBUGF("<out of range>");
+		else
+			DEBUGF("\"%s\"", scs->debug_str + offset);
+		break;
+
+	case DW_FORM_line_strp:
+		offset = read_uint(data, data_end, width);
+		if (offset >= scs->debug_line_str_size)
+			DEBUGF("<out of range>");
+		else
+			DEBUGF("\"%s\"", scs->debug_line_str + offset);
+		break;
+
+	case DW_FORM_sec_offset:
+		if (width == 4)
+			DEBUGF("0x%08" PRIx64, read_uint(data, data_end, width));
+		else
+			DEBUGF("0x%016" PRIx64, read_uint(data, data_end, width));
+		break;
+
+	case DW_FORM_strx:
+	case DW_FORM_udata:
+		DEBUGF("%" PRIu64, read_uleb128(data, data_end));
+		break;
+
+	case DW_FORM_sdata:
+		DEBUGF("%" PRId64, read_sleb128(data, data_end));
+		break;
+
+	case DW_FORM_strx1:
+	case DW_FORM_data1:
+	case DW_FORM_flag:
+		DEBUGF("%u", read_byte(data, data_end));
+		break;
+
+	case DW_FORM_strx2:
+	case DW_FORM_data2:
+		DEBUGF("%u", read_uint16(data, data_end));
+		break;
+
+	case DW_FORM_strx3:
+		DEBUGF("%u", read_uint24(data, data_end));
+		break;
+
+	case DW_FORM_strx4:
+	case DW_FORM_data4:
+		DEBUGF("%u", read_uint32(data, data_end));
+		break;
+
+	case DW_FORM_data8:
+		DEBUGF("%" PRIu64, read_uint64(data, data_end));
+		break;
+
+	case DW_FORM_data16:
+		uint64_t data1 = read_uint64(data, data_end);
+		uint64_t data2 = read_uint64(data, data_end);
+		DEBUGF("0x%016" PRIx64 "%016" PRIx64, data2, data1);
+		break;
+
+	case DW_FORM_block:
+		len = read_uleb128(data, data_end);
+		print_block(data, data_end, len);
+		break;
+
+	case DW_FORM_block1:
+		len = read_byte(data, data_end);
+		print_block(data, data_end, len);
+		break;
+
+	case DW_FORM_block2:
+		len = read_uint16(data, data_end);
+		print_block(data, data_end, len);
+		break;
+
+	case DW_FORM_block4:
+		len = read_uint32(data, data_end);
+		print_block(data, data_end, len);
+		break;
+
+	default:
+		DEBUGF("unexpected form");
+		*data = data_end;
+	}
+}
Index: kernel/generic/src/debug/util.h
===================================================================
--- kernel/generic/src/debug/util.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ kernel/generic/src/debug/util.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * 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.
+ */
+
+#ifndef DEBUG_UTIL_H_
+#define DEBUG_UTIL_H_
+
+#include <assert.h>
+#include <stdint.h>
+#include <debug/constants.h>
+#include <debug/sections.h>
+#include <debug.h>
+
+#define DEBUGF dummy_printf
+
+extern bool skip_data(unsigned, const uint8_t **const, const uint8_t *, unsigned);
+extern void print_format(const char *, const uint8_t *, const uint8_t *);
+extern void print_formatted_list(debug_sections_t *, const char *,
+    const uint8_t *, const uint8_t *, const uint8_t *, const uint8_t *, unsigned);
+
+extern void print_block(const uint8_t **, const uint8_t *, unsigned);
+extern void print_formed_data(debug_sections_t *scs, unsigned, const uint8_t **const, const uint8_t *, unsigned);
+
+static inline uint8_t read_byte(const uint8_t **data, const uint8_t *data_end)
+{
+	if (*data >= data_end) {
+		return 0;
+	} else {
+		return *((*data)++);
+	}
+}
+
+/* Casting to these structures allows us to read unaligned integers safely. */
+struct u16 {
+	uint16_t val;
+} __attribute__((packed));
+
+struct u32 {
+	uint32_t val;
+} __attribute__((packed));
+
+struct u64 {
+	uint64_t val;
+} __attribute__((packed));
+
+static inline uint16_t read_uint16(const uint8_t **data, const uint8_t *data_end)
+{
+	if (*data + 2 > data_end) {
+		/* Safe exit path for malformed input. */
+		*data = data_end;
+		return 0;
+	}
+
+	uint16_t v = ((struct u16 *) *data)->val;
+	*data += 2;
+	return v;
+}
+
+static inline uint32_t read_uint24(const uint8_t **data, const uint8_t *data_end)
+{
+	if (*data + 3 > data_end) {
+		/* Safe exit path for malformed input. */
+		*data = data_end;
+		return 0;
+	}
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+	uint32_t v = (*data)[0] | (*data)[1] << 8 | (*data)[2] << 16;
+#else
+	uint32_t v = (*data)[2] | (*data)[1] << 8 | (*data)[0] << 16;
+#endif
+
+	*data += 3;
+	return v;
+}
+
+static inline uint32_t read_uint32(const uint8_t **data, const uint8_t *data_end)
+{
+	if (*data + 4 > data_end) {
+		/* Safe exit path for malformed input. */
+		*data = data_end;
+		return 0;
+	}
+
+	uint32_t v = ((struct u32 *) *data)->val;
+	*data += 4;
+	return v;
+}
+
+static inline uint64_t read_uint64(const uint8_t **data, const uint8_t *data_end)
+{
+	if (*data + 8 > data_end) {
+		/* Safe exit path for malformed input. */
+		*data = data_end;
+		return 0;
+	}
+
+	uint64_t v = ((struct u64 *) *data)->val;
+	*data += 8;
+	return v;
+}
+
+static inline uint64_t read_uint(const uint8_t **data, const uint8_t *data_end, unsigned bytes)
+{
+	switch (bytes) {
+	case 1:
+		return read_byte(data, data_end);
+	case 2:
+		return read_uint16(data, data_end);
+	case 4:
+		return read_uint32(data, data_end);
+	case 8:
+		return read_uint64(data, data_end);
+	default:
+		panic("unimplemented");
+	}
+}
+
+static inline uint64_t read_uleb128(const uint8_t **data, const uint8_t *data_end)
+{
+	uint64_t result = 0;
+	unsigned shift = 0;
+
+	while (*data < data_end) {
+		uint8_t byte = *((*data)++);
+		result |= (byte & 0x7f) << shift;
+		shift += 7;
+
+		if ((byte & 0x80) == 0)
+			break;
+	}
+
+	return result;
+}
+
+static inline int64_t read_sleb128(const uint8_t **data, const uint8_t *data_end)
+{
+	uint64_t result = 0;
+	unsigned shift = 0;
+
+	while (*data < data_end) {
+		uint8_t byte = *((*data)++);
+		result |= (byte & 0x7f) << shift;
+		shift += 7;
+
+		if ((byte & 0x80) == 0) {
+			if (shift < 64 && (byte & 0x40) != 0) {
+				/* sign extend */
+				result |= -(1 << shift);
+			}
+			break;
+		}
+	}
+
+	return (int64_t) result;
+}
+
+static inline void skip_leb128(const uint8_t **data, const uint8_t *data_end)
+{
+	while (*data < data_end) {
+		uint8_t byte = *((*data)++);
+		if ((byte & 0x80) == 0)
+			break;
+	}
+}
+
+static inline uint64_t read_initial_length(const uint8_t **data, const uint8_t *data_end, unsigned *width)
+{
+	uint32_t initial = read_uint32(data, data_end);
+	if (initial == 0xffffffffu) {
+		*width = 8;
+		return read_uint64(data, data_end);
+	} else {
+		*width = 4;
+		return initial;
+	}
+}
+
+static inline const char *read_string(const uint8_t **data, const uint8_t *data_end)
+{
+	const char *start = (const char *) *data;
+
+	// NUL-terminated string.
+	while (*data < data_end && **data != 0)
+		(*data)++;
+
+	if (*data < data_end) {
+		// Skip the terminating zero.
+		(*data)++;
+		return start;
+	} else {
+		// No terminating zero, we can't use this.
+		return NULL;
+	}
+}
+
+static inline void skip_string(const uint8_t **data, const uint8_t *data_end)
+{
+	(void) read_string(data, data_end);
+}
+
+static inline void safe_increment(const uint8_t **data,
+    const uint8_t *data_end, ptrdiff_t increment)
+{
+	assert(data_end >= *data);
+
+	if (increment >= data_end - *data) {
+		*data = data_end;
+	} else {
+		(*data) += increment;
+	}
+}
+
+static inline void skip_format(const uint8_t **data, const uint8_t *const data_end,
+    unsigned count)
+{
+	for (unsigned i = 0; i < count; i++) {
+		(void) read_uleb128(data, data_end);
+		(void) read_uleb128(data, data_end);
+	}
+}
+
+static inline void skip_formatted_entry(const uint8_t **data, const uint8_t *const data_end,
+    const uint8_t *format, const uint8_t *format_end, unsigned width)
+{
+	while (format < format_end) {
+		/* Ignore content type code */
+		(void) read_uleb128(&format, format_end);
+
+		uint64_t form = read_uleb128(&format, format_end);
+		skip_data(form, data, data_end, width);
+	}
+}
+
+static inline void skip_formatted_list(const uint8_t **data, const uint8_t *const data_end,
+    unsigned count,	const uint8_t *format, const uint8_t *format_end,
+    unsigned width)
+{
+	for (unsigned i = 0; i < count; i++) {
+		skip_formatted_entry(data, data_end, format, format_end, width);
+	}
+}
+
+#endif /* DEBUG_UTIL_H_ */
Index: kernel/generic/src/interrupt/interrupt.c
===================================================================
--- kernel/generic/src/interrupt/interrupt.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/interrupt/interrupt.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -58,4 +58,11 @@
 #include <trace.h>
 
+/*
+ * If IVT_ITEMS is zero (e.g. for special/abs32le) we hide completely any
+ * access to the exception table array and panic if the function is called
+ * at all. It also silences (correct) compiler warnings about possible
+ * out-of-bound array access.
+ */
+
 exc_table_t exc_table[IVT_ITEMS];
 IRQ_SPINLOCK_INITIALIZE(exctbl_lock);
@@ -77,5 +84,4 @@
 #if (IVT_ITEMS > 0)
 	assert(n < IVT_ITEMS);
-#endif
 
 	irq_spinlock_lock(&exctbl_lock, true);
@@ -91,4 +97,7 @@
 
 	return old;
+#else
+	panic("No space for any exception handler, cannot register.");
+#endif
 }
 
@@ -103,21 +112,15 @@
 #if (IVT_ITEMS > 0)
 	assert(n < IVT_ITEMS);
-#endif
 
 	/* Account user cycles */
-	if (THREAD) {
-		irq_spinlock_lock(&THREAD->lock, false);
+	if (THREAD)
 		thread_update_accounting(true);
-		irq_spinlock_unlock(&THREAD->lock, false);
-	}
 
 	/* Account CPU usage if it woke up from sleep */
-	if (CPU && CPU->idle) {
-		irq_spinlock_lock(&CPU->lock, false);
+	if (CPU && CPU_LOCAL->idle) {
 		uint64_t now = get_cycle();
-		CPU->idle_cycles += now - CPU->last_cycle;
-		CPU->last_cycle = now;
-		CPU->idle = false;
-		irq_spinlock_unlock(&CPU->lock, false);
+		atomic_time_increment(&CPU->idle_cycles, now - CPU_LOCAL->last_cycle);
+		CPU_LOCAL->last_cycle = now;
+		CPU_LOCAL->idle = false;
 	}
 
@@ -149,9 +152,9 @@
 
 	/* Do not charge THREAD for exception cycles */
-	if (THREAD) {
-		irq_spinlock_lock(&THREAD->lock, false);
+	if (THREAD)
 		THREAD->last_cycle = end_cycle;
-		irq_spinlock_unlock(&THREAD->lock, false);
-	}
+#else
+	panic("No space for any exception handler, yet we want to handle some exception.");
+#endif
 }
 
Index: kernel/generic/src/ipc/event.c
===================================================================
--- kernel/generic/src/ipc/event.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/ipc/event.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -169,6 +169,5 @@
 				    true);
 
-				waitq_wakeup(&event->answerbox->wq,
-				    WAKEUP_FIRST);
+				waitq_wake_one(&event->answerbox->wq);
 
 				if (mask)
Index: kernel/generic/src/ipc/ipc.c
===================================================================
--- kernel/generic/src/ipc/ipc.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/ipc/ipc.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -54,5 +54,5 @@
 #include <arch.h>
 #include <proc/task.h>
-#include <mem.h>
+#include <memw.h>
 #include <stdio.h>
 #include <console/console.h>
@@ -326,5 +326,5 @@
 		irq_spinlock_unlock(&callerbox->lock, true);
 
-	waitq_wakeup(&callerbox->wq, WAKEUP_FIRST);
+	waitq_wake_one(&callerbox->wq);
 }
 
@@ -416,5 +416,5 @@
 	irq_spinlock_unlock(&box->lock, true);
 
-	waitq_wakeup(&box->wq, WAKEUP_FIRST);
+	waitq_wake_one(&box->wq);
 }
 
@@ -555,5 +555,5 @@
 	errno_t rc;
 
-	rc = waitq_sleep_timeout(&box->wq, usec, flags, NULL);
+	rc = _waitq_sleep_timeout(&box->wq, usec, flags);
 	if (rc != EOK)
 		return rc;
@@ -967,12 +967,7 @@
 void ipc_print_task(task_id_t taskid)
 {
-	irq_spinlock_lock(&tasks_lock, true);
 	task_t *task = task_find_by_id(taskid);
-	if (!task) {
-		irq_spinlock_unlock(&tasks_lock, true);
+	if (!task)
 		return;
-	}
-	task_hold(task);
-	irq_spinlock_unlock(&tasks_lock, true);
 
 	printf("[phone cap] [calls] [state\n");
Index: kernel/generic/src/ipc/irq.c
===================================================================
--- kernel/generic/src/ipc/irq.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/ipc/irq.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -429,5 +429,5 @@
 	irq_spinlock_unlock(&irq->notif_cfg.answerbox->irq_lock, false);
 
-	waitq_wakeup(&irq->notif_cfg.answerbox->wq, WAKEUP_FIRST);
+	waitq_wake_one(&irq->notif_cfg.answerbox->wq);
 }
 
Index: kernel/generic/src/ipc/kbox.c
===================================================================
--- kernel/generic/src/ipc/kbox.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/ipc/kbox.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -90,5 +90,4 @@
 		LOG("Join kb.thread.");
 		thread_join(TASK->kb.thread);
-		thread_detach(TASK->kb.thread);
 		LOG("...join done.");
 		TASK->kb.thread = NULL;
@@ -136,6 +135,6 @@
 		/* Only detach kbox thread unless already terminating. */
 		if (TASK->kb.finished == false) {
-			/* Detach kbox thread so it gets freed from memory. */
-			thread_detach(TASK->kb.thread);
+			/* Release kbox thread so it gets freed from memory. */
+			thread_put(TASK->kb.thread);
 			TASK->kb.thread = NULL;
 		}
@@ -201,9 +200,4 @@
 /** Connect phone to a task kernel-box specified by id.
  *
- * Note that this is not completely atomic. For optimisation reasons, the task
- * might start cleaning up kbox after the phone has been connected and before
- * a kbox thread has been created. This must be taken into account in the
- * cleanup code.
- *
  * @param[out] out_phone  Phone capability handle on success.
  * @return Error code.
@@ -212,26 +206,13 @@
 errno_t ipc_connect_kbox(task_id_t taskid, cap_phone_handle_t *out_phone)
 {
-	irq_spinlock_lock(&tasks_lock, true);
-
 	task_t *task = task_find_by_id(taskid);
-	if (task == NULL) {
-		irq_spinlock_unlock(&tasks_lock, true);
+	if (!task)
 		return ENOENT;
-	}
-
-	atomic_inc(&task->refcount);
-
-	irq_spinlock_unlock(&tasks_lock, true);
 
 	mutex_lock(&task->kb.cleanup_lock);
-
-	if (atomic_predec(&task->refcount) == 0) {
-		mutex_unlock(&task->kb.cleanup_lock);
-		task_destroy(task);
-		return ENOENT;
-	}
 
 	if (task->kb.finished) {
 		mutex_unlock(&task->kb.cleanup_lock);
+		task_release(task);
 		return EINVAL;
 	}
@@ -244,9 +225,10 @@
 		if (!kb_thread) {
 			mutex_unlock(&task->kb.cleanup_lock);
+			task_release(task);
 			return ENOMEM;
 		}
 
 		task->kb.thread = kb_thread;
-		thread_ready(kb_thread);
+		thread_start(kb_thread);
 	}
 
@@ -256,4 +238,5 @@
 	if (rc != EOK) {
 		mutex_unlock(&task->kb.cleanup_lock);
+		task_release(task);
 		return rc;
 	}
@@ -266,4 +249,5 @@
 
 	mutex_unlock(&task->kb.cleanup_lock);
+	task_release(task);
 	*out_phone = phone_handle;
 	return EOK;
Index: kernel/generic/src/ipc/sysipc.c
===================================================================
--- kernel/generic/src/ipc/sysipc.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/ipc/sysipc.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,5 +36,5 @@
 #include <assert.h>
 #include <errno.h>
-#include <mem.h>
+#include <memw.h>
 #include <ipc/ipc.h>
 #include <abi/ipc/methods.h>
@@ -871,5 +871,5 @@
 sys_errno_t sys_ipc_poke(void)
 {
-	waitq_wakeup(&TASK->answerbox.wq, WAKEUP_FIRST);
+	waitq_wake_one(&TASK->answerbox.wq);
 	return EOK;
 }
Index: rnel/generic/src/lib/gsort.c
===================================================================
--- kernel/generic/src/lib/gsort.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,128 +1,0 @@
-/*
- * Copyright (c) 2005 Sergey Bondari
- * 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 kernel_generic
- * @{
- */
-
-/**
- * @file
- * @brief Sorting functions.
- *
- * This files contains functions implementing several sorting
- * algorithms (e.g. quick sort and gnome sort).
- *
- */
-
-#include <gsort.h>
-#include <mem.h>
-#include <stdlib.h>
-
-/** Immediate buffer size.
- *
- * For small buffer sizes avoid doing malloc()
- * and use the stack.
- *
- */
-#define IBUF_SIZE  32
-
-/** Array accessor.
- *
- */
-#define INDEX(buf, i, elem_size)  ((buf) + (i) * (elem_size))
-
-/** Gnome sort
- *
- * Apply generic gnome sort algorithm on supplied data,
- * using pre-allocated buffer.
- *
- * @param data      Pointer to data to be sorted.
- * @param cnt       Number of elements to be sorted.
- * @param elem_size Size of one element.
- * @param cmp       Comparator function.
- * @param arg       3rd argument passed to cmp.
- * @param slot      Pointer to scratch memory buffer
- *                  elem_size bytes long.
- *
- */
-static void _gsort(void *data, size_t cnt, size_t elem_size, sort_cmp_t cmp,
-    void *arg, void *slot)
-{
-	size_t i = 0;
-
-	while (i < cnt) {
-		if ((i != 0) &&
-		    (cmp(INDEX(data, i, elem_size),
-		    INDEX(data, i - 1, elem_size), arg) == -1)) {
-			memcpy(slot, INDEX(data, i, elem_size), elem_size);
-			memcpy(INDEX(data, i, elem_size), INDEX(data, i - 1, elem_size),
-			    elem_size);
-			memcpy(INDEX(data, i - 1, elem_size), slot, elem_size);
-			i--;
-		} else
-			i++;
-	}
-}
-
-/** Gnome sort wrapper
- *
- * This is only a wrapper that takes care of memory
- * allocations for storing the slot element for generic
- * gnome sort algorithm.
- *
- * @param data      Pointer to data to be sorted.
- * @param cnt       Number of elements to be sorted.
- * @param elem_size Size of one element.
- * @param cmp       Comparator function.
- * @param arg       3rd argument passed to cmp.
- *
- * @return True if sorting succeeded.
- *
- */
-bool gsort(void *data, size_t cnt, size_t elem_size, sort_cmp_t cmp, void *arg)
-{
-	uint8_t ibuf_slot[IBUF_SIZE];
-	void *slot;
-
-	if (elem_size > IBUF_SIZE) {
-		slot = (void *) malloc(elem_size);
-		if (!slot)
-			return false;
-	} else
-		slot = (void *) ibuf_slot;
-
-	_gsort(data, cnt, elem_size, cmp, arg, slot);
-
-	if (elem_size > IBUF_SIZE)
-		free(slot);
-
-	return true;
-}
-
-/** @}
- */
Index: kernel/generic/src/lib/mem.c
===================================================================
--- kernel/generic/src/lib/mem.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/lib/mem.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -41,5 +41,5 @@
  */
 
-#include <mem.h>
+#include <memw.h>
 #include <typedefs.h>
 
@@ -77,49 +77,4 @@
 }
 
-/** Move memory block with possible overlapping.
- *
- * Copy cnt bytes from src address to dst address. The source
- * and destination memory areas may overlap.
- *
- * @param dst Destination address to copy to.
- * @param src Source address to copy from.
- * @param cnt Number of bytes to copy.
- *
- * @return Destination address.
- *
- */
-void *memmove(void *dst, const void *src, size_t cnt)
-{
-	/* Nothing to do? */
-	if (src == dst)
-		return dst;
-
-	/* Non-overlapping? */
-	if ((dst >= src + cnt) || (src >= dst + cnt))
-		return memcpy(dst, src, cnt);
-
-	uint8_t *dp;
-	const uint8_t *sp;
-
-	/* Which direction? */
-	if (src > dst) {
-		/* Forwards. */
-		dp = dst;
-		sp = src;
-
-		while (cnt-- != 0)
-			*dp++ = *sp++;
-	} else {
-		/* Backwards. */
-		dp = dst + (cnt - 1);
-		sp = src + (cnt - 1);
-
-		while (cnt-- != 0)
-			*dp-- = *sp--;
-	}
-
-	return dst;
-}
-
 /** @}
  */
Index: rnel/generic/src/lib/memfnc.c
===================================================================
--- kernel/generic/src/lib/memfnc.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,117 +1,0 @@
-/*
- * Copyright (c) 2011 Martin Decky
- * 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 kernel_generic
- * @{
- */
-
-/**
- * @file
- * @brief Memory string functions.
- *
- * This file provides architecture independent functions to manipulate blocks
- * of memory. These functions are optimized as much as generic functions of
- * this type can be.
- */
-
-#include <lib/memfnc.h>
-#include <typedefs.h>
-
-/** Fill block of memory.
- *
- * Fill cnt bytes at dst address with the value val.
- *
- * @param dst Destination address to fill.
- * @param val Value to fill.
- * @param cnt Number of bytes to fill.
- *
- * @return Destination address.
- *
- */
-void *memset(void *dst, int val, size_t cnt)
-{
-	uint8_t *dp = (uint8_t *) dst;
-
-	while (cnt-- != 0)
-		*dp++ = val;
-
-	return dst;
-}
-
-/** Move memory block without overlapping.
- *
- * Copy cnt bytes from src address to dst address. The source
- * and destination memory areas cannot overlap.
- *
- * @param dst Destination address to copy to.
- * @param src Source address to copy from.
- * @param cnt Number of bytes to copy.
- *
- * @return Destination address.
- *
- */
-void *memcpy(void *dst, const void *src, size_t cnt)
-{
-	uint8_t *dp = (uint8_t *) dst;
-	const uint8_t *sp = (uint8_t *) src;
-
-	while (cnt-- != 0)
-		*dp++ = *sp++;
-
-	return dst;
-}
-
-/** Compare two memory areas.
- *
- * @param s1  Pointer to the first area to compare.
- * @param s2  Pointer to the second area to compare.
- * @param len Size of the areas in bytes.
- *
- * @return Zero if areas have the same contents. If they differ,
- *	   the sign of the result is the same as the sign of the
- *	   difference of the first pair of different bytes.
- *
- */
-int memcmp(const void *s1, const void *s2, size_t len)
-{
-	uint8_t *u1 = (uint8_t *) s1;
-	uint8_t *u2 = (uint8_t *) s2;
-	size_t i;
-
-	for (i = 0; i < len; i++) {
-		if (*u1 != *u2)
-			return (int)(*u1) - (int)(*u2);
-		++u1;
-		++u2;
-	}
-
-	return 0;
-}
-
-/** @}
- */
Index: kernel/generic/src/lib/ra.c
===================================================================
--- kernel/generic/src/lib/ra.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/lib/ra.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -81,5 +81,5 @@
 }
 
-static hash_table_ops_t used_ops = {
+static const hash_table_ops_t used_ops = {
 	.hash = used_hash,
 	.key_hash = used_key_hash,
Index: rnel/generic/src/lib/str.c
===================================================================
--- kernel/generic/src/lib/str.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,840 +1,0 @@
-/*
- * Copyright (c) 2001-2004 Jakub Jermar
- * Copyright (c) 2005 Martin Decky
- * Copyright (c) 2008 Jiri Svoboda
- * Copyright (c) 2011 Martin Sucha
- * Copyright (c) 2011 Oleg Romanenko
- * 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 kernel_generic
- * @{
- */
-
-/**
- * @file
- * @brief String functions.
- *
- * Strings and characters use the Universal Character Set (UCS). The standard
- * strings, called just strings are encoded in UTF-8. Wide strings (encoded
- * in UTF-32) are supported to a limited degree. A single character is
- * represented as char32_t.@n
- *
- * Overview of the terminology:@n
- *
- *  Term                  Meaning
- *  --------------------  ----------------------------------------------------
- *  byte                  8 bits stored in uint8_t (unsigned 8 bit integer)
- *
- *  character             UTF-32 encoded Unicode character, stored in char32_t
- *                        (unsigned 32 bit integer), code points 0 .. 1114111
- *                        are valid
- *
- *  ASCII character       7 bit encoded ASCII character, stored in char
- *                        (usually signed 8 bit integer), code points 0 .. 127
- *                        are valid
- *
- *  string                UTF-8 encoded NULL-terminated Unicode string, char *
- *
- *  wide string           UTF-32 encoded NULL-terminated Unicode string,
- *                        char32_t *
- *
- *  [wide] string size    number of BYTES in a [wide] string (excluding
- *                        the NULL-terminator), size_t
- *
- *  [wide] string length  number of CHARACTERS in a [wide] string (excluding
- *                        the NULL-terminator), size_t
- *
- *  [wide] string width   number of display cells on a monospace display taken
- *                        by a [wide] string, size_t
- *
- *
- * Overview of string metrics:@n
- *
- *  Metric  Abbrev.  Type     Meaning
- *  ------  ------   ------   -------------------------------------------------
- *  size    n        size_t   number of BYTES in a string (excluding the
- *                            NULL-terminator)
- *
- *  length  l        size_t   number of CHARACTERS in a string (excluding the
- *                            null terminator)
- *
- *  width  w         size_t   number of display cells on a monospace display
- *                            taken by a string
- *
- *
- * Function naming prefixes:@n
- *
- *  chr_    operate on characters
- *  ascii_  operate on ASCII characters
- *  str_    operate on strings
- *  wstr_   operate on wide strings
- *
- *  [w]str_[n|l|w]  operate on a prefix limited by size, length
- *                  or width
- *
- *
- * A specific character inside a [wide] string can be referred to by:@n
- *
- *  pointer (char *, char32_t *)
- *  byte offset (size_t)
- *  character index (size_t)
- *
- */
-
-#include <str.h>
-
-#include <assert.h>
-#include <errno.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <stdlib.h>
-
-#include <align.h>
-#include <macros.h>
-
-/** Byte mask consisting of lowest @n bits (out of 8) */
-#define LO_MASK_8(n)  ((uint8_t) ((1 << (n)) - 1))
-
-/** Byte mask consisting of lowest @n bits (out of 32) */
-#define LO_MASK_32(n)  ((uint32_t) ((1 << (n)) - 1))
-
-/** Byte mask consisting of highest @n bits (out of 8) */
-#define HI_MASK_8(n)  (~LO_MASK_8(8 - (n)))
-
-/** Number of data bits in a UTF-8 continuation byte */
-#define CONT_BITS  6
-
-/** Decode a single character from a string.
- *
- * Decode a single character from a string of size @a size. Decoding starts
- * at @a offset and this offset is moved to the beginning of the next
- * character. In case of decoding error, offset generally advances at least
- * by one. However, offset is never moved beyond size.
- *
- * @param str    String (not necessarily NULL-terminated).
- * @param offset Byte offset in string where to start decoding.
- * @param size   Size of the string (in bytes).
- *
- * @return Value of decoded character, U_SPECIAL on decoding error or
- *         NULL if attempt to decode beyond @a size.
- *
- */
-char32_t str_decode(const char *str, size_t *offset, size_t size)
-{
-	if (*offset + 1 > size)
-		return 0;
-
-	/* First byte read from string */
-	uint8_t b0 = (uint8_t) str[(*offset)++];
-
-	/* Determine code length */
-
-	unsigned int b0_bits;  /* Data bits in first byte */
-	unsigned int cbytes;   /* Number of continuation bytes */
-
-	if ((b0 & 0x80) == 0) {
-		/* 0xxxxxxx (Plain ASCII) */
-		b0_bits = 7;
-		cbytes = 0;
-	} else if ((b0 & 0xe0) == 0xc0) {
-		/* 110xxxxx 10xxxxxx */
-		b0_bits = 5;
-		cbytes = 1;
-	} else if ((b0 & 0xf0) == 0xe0) {
-		/* 1110xxxx 10xxxxxx 10xxxxxx */
-		b0_bits = 4;
-		cbytes = 2;
-	} else if ((b0 & 0xf8) == 0xf0) {
-		/* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
-		b0_bits = 3;
-		cbytes = 3;
-	} else {
-		/* 10xxxxxx -- unexpected continuation byte */
-		return U_SPECIAL;
-	}
-
-	if (*offset + cbytes > size)
-		return U_SPECIAL;
-
-	char32_t ch = b0 & LO_MASK_8(b0_bits);
-
-	/* Decode continuation bytes */
-	while (cbytes > 0) {
-		uint8_t b = (uint8_t) str[(*offset)++];
-
-		/* Must be 10xxxxxx */
-		if ((b & 0xc0) != 0x80)
-			return U_SPECIAL;
-
-		/* Shift data bits to ch */
-		ch = (ch << CONT_BITS) | (char32_t) (b & LO_MASK_8(CONT_BITS));
-		cbytes--;
-	}
-
-	return ch;
-}
-
-/** Encode a single character to string representation.
- *
- * Encode a single character to string representation (i.e. UTF-8) and store
- * it into a buffer at @a offset. Encoding starts at @a offset and this offset
- * is moved to the position where the next character can be written to.
- *
- * @param ch     Input character.
- * @param str    Output buffer.
- * @param offset Byte offset where to start writing.
- * @param size   Size of the output buffer (in bytes).
- *
- * @return EOK if the character was encoded successfully, EOVERFLOW if there
- *         was not enough space in the output buffer or EINVAL if the character
- *         code was invalid.
- */
-errno_t chr_encode(const char32_t ch, char *str, size_t *offset, size_t size)
-{
-	if (*offset >= size)
-		return EOVERFLOW;
-
-	if (!chr_check(ch))
-		return EINVAL;
-
-	/*
-	 * Unsigned version of ch (bit operations should only be done
-	 * on unsigned types).
-	 */
-	uint32_t cc = (uint32_t) ch;
-
-	/* Determine how many continuation bytes are needed */
-
-	unsigned int b0_bits;  /* Data bits in first byte */
-	unsigned int cbytes;   /* Number of continuation bytes */
-
-	if ((cc & ~LO_MASK_32(7)) == 0) {
-		b0_bits = 7;
-		cbytes = 0;
-	} else if ((cc & ~LO_MASK_32(11)) == 0) {
-		b0_bits = 5;
-		cbytes = 1;
-	} else if ((cc & ~LO_MASK_32(16)) == 0) {
-		b0_bits = 4;
-		cbytes = 2;
-	} else if ((cc & ~LO_MASK_32(21)) == 0) {
-		b0_bits = 3;
-		cbytes = 3;
-	} else {
-		/* Codes longer than 21 bits are not supported */
-		return EINVAL;
-	}
-
-	/* Check for available space in buffer */
-	if (*offset + cbytes >= size)
-		return EOVERFLOW;
-
-	/* Encode continuation bytes */
-	unsigned int i;
-	for (i = cbytes; i > 0; i--) {
-		str[*offset + i] = 0x80 | (cc & LO_MASK_32(CONT_BITS));
-		cc = cc >> CONT_BITS;
-	}
-
-	/* Encode first byte */
-	str[*offset] = (cc & LO_MASK_32(b0_bits)) | HI_MASK_8(8 - b0_bits - 1);
-
-	/* Advance offset */
-	*offset += cbytes + 1;
-
-	return EOK;
-}
-
-/** Get size of string.
- *
- * Get the number of bytes which are used by the string @a str (excluding the
- * NULL-terminator).
- *
- * @param str String to consider.
- *
- * @return Number of bytes used by the string
- *
- */
-size_t str_size(const char *str)
-{
-	size_t size = 0;
-
-	while (*str++ != 0)
-		size++;
-
-	return size;
-}
-
-/** Get size of wide string.
- *
- * Get the number of bytes which are used by the wide string @a str (excluding the
- * NULL-terminator).
- *
- * @param str Wide string to consider.
- *
- * @return Number of bytes used by the wide string
- *
- */
-size_t wstr_size(const char32_t *str)
-{
-	return (wstr_length(str) * sizeof(char32_t));
-}
-
-/** Get size of string with length limit.
- *
- * Get the number of bytes which are used by up to @a max_len first
- * characters in the string @a str. If @a max_len is greater than
- * the length of @a str, the entire string is measured (excluding the
- * NULL-terminator).
- *
- * @param str     String to consider.
- * @param max_len Maximum number of characters to measure.
- *
- * @return Number of bytes used by the characters.
- *
- */
-size_t str_lsize(const char *str, size_t max_len)
-{
-	size_t len = 0;
-	size_t offset = 0;
-
-	while (len < max_len) {
-		if (str_decode(str, &offset, STR_NO_LIMIT) == 0)
-			break;
-
-		len++;
-	}
-
-	return offset;
-}
-
-/** Get size of wide string with length limit.
- *
- * Get the number of bytes which are used by up to @a max_len first
- * wide characters in the wide string @a str. If @a max_len is greater than
- * the length of @a str, the entire wide string is measured (excluding the
- * NULL-terminator).
- *
- * @param str     Wide string to consider.
- * @param max_len Maximum number of wide characters to measure.
- *
- * @return Number of bytes used by the wide characters.
- *
- */
-size_t wstr_lsize(const char32_t *str, size_t max_len)
-{
-	return (wstr_nlength(str, max_len * sizeof(char32_t)) * sizeof(char32_t));
-}
-
-/** Get number of characters in a string.
- *
- * @param str NULL-terminated string.
- *
- * @return Number of characters in string.
- *
- */
-size_t str_length(const char *str)
-{
-	size_t len = 0;
-	size_t offset = 0;
-
-	while (str_decode(str, &offset, STR_NO_LIMIT) != 0)
-		len++;
-
-	return len;
-}
-
-/** Get number of characters in a wide string.
- *
- * @param str NULL-terminated wide string.
- *
- * @return Number of characters in @a str.
- *
- */
-size_t wstr_length(const char32_t *wstr)
-{
-	size_t len = 0;
-
-	while (*wstr++ != 0)
-		len++;
-
-	return len;
-}
-
-/** Get number of characters in a string with size limit.
- *
- * @param str  NULL-terminated string.
- * @param size Maximum number of bytes to consider.
- *
- * @return Number of characters in string.
- *
- */
-size_t str_nlength(const char *str, size_t size)
-{
-	size_t len = 0;
-	size_t offset = 0;
-
-	while (str_decode(str, &offset, size) != 0)
-		len++;
-
-	return len;
-}
-
-/** Get number of characters in a string with size limit.
- *
- * @param str  NULL-terminated string.
- * @param size Maximum number of bytes to consider.
- *
- * @return Number of characters in string.
- *
- */
-size_t wstr_nlength(const char32_t *str, size_t size)
-{
-	size_t len = 0;
-	size_t limit = ALIGN_DOWN(size, sizeof(char32_t));
-	size_t offset = 0;
-
-	while ((offset < limit) && (*str++ != 0)) {
-		len++;
-		offset += sizeof(char32_t);
-	}
-
-	return len;
-}
-
-/** Check whether character is plain ASCII.
- *
- * @return True if character is plain ASCII.
- *
- */
-bool ascii_check(char32_t ch)
-{
-	if (ch <= 127)
-		return true;
-
-	return false;
-}
-
-/** Check whether character is valid
- *
- * @return True if character is a valid Unicode code point.
- *
- */
-bool chr_check(char32_t ch)
-{
-	if (ch <= 1114111)
-		return true;
-
-	return false;
-}
-
-/** Compare two NULL terminated strings.
- *
- * Do a char-by-char comparison of two NULL-terminated strings.
- * The strings are considered equal iff their length is equal
- * and both strings consist of the same sequence of characters.
- *
- * A string S1 is less than another string S2 if it has a character with
- * lower value at the first character position where the strings differ.
- * If the strings differ in length, the shorter one is treated as if
- * padded by characters with a value of zero.
- *
- * @param s1 First string to compare.
- * @param s2 Second string to compare.
- *
- * @return 0 if the strings are equal, -1 if the first is less than the second,
- *         1 if the second is less than the first.
- *
- */
-int str_cmp(const char *s1, const char *s2)
-{
-	char32_t c1 = 0;
-	char32_t c2 = 0;
-
-	size_t off1 = 0;
-	size_t off2 = 0;
-
-	while (true) {
-		c1 = str_decode(s1, &off1, STR_NO_LIMIT);
-		c2 = str_decode(s2, &off2, STR_NO_LIMIT);
-
-		if (c1 < c2)
-			return -1;
-
-		if (c1 > c2)
-			return 1;
-
-		if (c1 == 0 || c2 == 0)
-			break;
-	}
-
-	return 0;
-}
-
-/** Compare two NULL terminated strings with length limit.
- *
- * Do a char-by-char comparison of two NULL-terminated strings.
- * The strings are considered equal iff
- * min(str_length(s1), max_len) == min(str_length(s2), max_len)
- * and both strings consist of the same sequence of characters,
- * up to max_len characters.
- *
- * A string S1 is less than another string S2 if it has a character with
- * lower value at the first character position where the strings differ.
- * If the strings differ in length, the shorter one is treated as if
- * padded by characters with a value of zero. Only the first max_len
- * characters are considered.
- *
- * @param s1      First string to compare.
- * @param s2      Second string to compare.
- * @param max_len Maximum number of characters to consider.
- *
- * @return 0 if the strings are equal, -1 if the first is less than the second,
- *         1 if the second is less than the first.
- *
- */
-int str_lcmp(const char *s1, const char *s2, size_t max_len)
-{
-	char32_t c1 = 0;
-	char32_t c2 = 0;
-
-	size_t off1 = 0;
-	size_t off2 = 0;
-
-	size_t len = 0;
-
-	while (true) {
-		if (len >= max_len)
-			break;
-
-		c1 = str_decode(s1, &off1, STR_NO_LIMIT);
-		c2 = str_decode(s2, &off2, STR_NO_LIMIT);
-
-		if (c1 < c2)
-			return -1;
-
-		if (c1 > c2)
-			return 1;
-
-		if (c1 == 0 || c2 == 0)
-			break;
-
-		++len;
-	}
-
-	return 0;
-
-}
-
-/** Copy string.
- *
- * Copy source string @a src to destination buffer @a dest.
- * No more than @a size bytes are written. If the size of the output buffer
- * is at least one byte, the output string will always be well-formed, i.e.
- * null-terminated and containing only complete characters.
- *
- * @param dest  Destination buffer.
- * @param count Size of the destination buffer (must be > 0).
- * @param src   Source string.
- *
- */
-void str_cpy(char *dest, size_t size, const char *src)
-{
-	/* There must be space for a null terminator in the buffer. */
-	assert(size > 0);
-	assert(src != NULL);
-
-	size_t src_off = 0;
-	size_t dest_off = 0;
-
-	char32_t ch;
-	while ((ch = str_decode(src, &src_off, STR_NO_LIMIT)) != 0) {
-		if (chr_encode(ch, dest, &dest_off, size - 1) != EOK)
-			break;
-	}
-
-	dest[dest_off] = '\0';
-}
-
-/** Copy size-limited substring.
- *
- * Copy prefix of string @a src of max. size @a size to destination buffer
- * @a dest. No more than @a size bytes are written. The output string will
- * always be well-formed, i.e. null-terminated and containing only complete
- * characters.
- *
- * No more than @a n bytes are read from the input string, so it does not
- * have to be null-terminated.
- *
- * @param dest  Destination buffer.
- * @param count Size of the destination buffer (must be > 0).
- * @param src   Source string.
- * @param n     Maximum number of bytes to read from @a src.
- *
- */
-void str_ncpy(char *dest, size_t size, const char *src, size_t n)
-{
-	/* There must be space for a null terminator in the buffer. */
-	assert(size > 0);
-
-	size_t src_off = 0;
-	size_t dest_off = 0;
-
-	char32_t ch;
-	while ((ch = str_decode(src, &src_off, n)) != 0) {
-		if (chr_encode(ch, dest, &dest_off, size - 1) != EOK)
-			break;
-	}
-
-	dest[dest_off] = '\0';
-}
-
-/** Convert wide string to string.
- *
- * Convert wide string @a src to string. The output is written to the buffer
- * specified by @a dest and @a size. @a size must be non-zero and the string
- * written will always be well-formed.
- *
- * @param dest Destination buffer.
- * @param size Size of the destination buffer.
- * @param src  Source wide string.
- */
-void wstr_to_str(char *dest, size_t size, const char32_t *src)
-{
-	char32_t ch;
-	size_t src_idx;
-	size_t dest_off;
-
-	/* There must be space for a null terminator in the buffer. */
-	assert(size > 0);
-
-	src_idx = 0;
-	dest_off = 0;
-
-	while ((ch = src[src_idx++]) != 0) {
-		if (chr_encode(ch, dest, &dest_off, size - 1) != EOK)
-			break;
-	}
-
-	dest[dest_off] = '\0';
-}
-
-/** Find first occurence of character in string.
- *
- * @param str String to search.
- * @param ch  Character to look for.
- *
- * @return Pointer to character in @a str or NULL if not found.
- */
-char *str_chr(const char *str, char32_t ch)
-{
-	char32_t acc;
-	size_t off = 0;
-	size_t last = 0;
-
-	while ((acc = str_decode(str, &off, STR_NO_LIMIT)) != 0) {
-		if (acc == ch)
-			return (char *) (str + last);
-		last = off;
-	}
-
-	return NULL;
-}
-
-/** Insert a wide character into a wide string.
- *
- * Insert a wide character into a wide string at position
- * @a pos. The characters after the position are shifted.
- *
- * @param str     String to insert to.
- * @param ch      Character to insert to.
- * @param pos     Character index where to insert.
- * @param max_pos Characters in the buffer.
- *
- * @return True if the insertion was sucessful, false if the position
- *         is out of bounds.
- *
- */
-bool wstr_linsert(char32_t *str, char32_t ch, size_t pos, size_t max_pos)
-{
-	size_t len = wstr_length(str);
-
-	if ((pos > len) || (pos + 1 > max_pos))
-		return false;
-
-	size_t i;
-	for (i = len; i + 1 > pos; i--)
-		str[i + 1] = str[i];
-
-	str[pos] = ch;
-
-	return true;
-}
-
-/** Remove a wide character from a wide string.
- *
- * Remove a wide character from a wide string at position
- * @a pos. The characters after the position are shifted.
- *
- * @param str String to remove from.
- * @param pos Character index to remove.
- *
- * @return True if the removal was sucessful, false if the position
- *         is out of bounds.
- *
- */
-bool wstr_remove(char32_t *str, size_t pos)
-{
-	size_t len = wstr_length(str);
-
-	if (pos >= len)
-		return false;
-
-	size_t i;
-	for (i = pos + 1; i <= len; i++)
-		str[i - 1] = str[i];
-
-	return true;
-}
-
-/** Duplicate string.
- *
- * Allocate a new string and copy characters from the source
- * string into it. The duplicate string is allocated via sleeping
- * malloc(), thus this function can sleep in no memory conditions.
- *
- * The allocation cannot fail and the return value is always
- * a valid pointer. The duplicate string is always a well-formed
- * null-terminated UTF-8 string, but it can differ from the source
- * string on the byte level.
- *
- * @param src Source string.
- *
- * @return Duplicate string.
- *
- */
-char *str_dup(const char *src)
-{
-	size_t size = str_size(src) + 1;
-	char *dest = malloc(size);
-	if (!dest)
-		return NULL;
-
-	str_cpy(dest, size, src);
-	return dest;
-}
-
-/** Duplicate string with size limit.
- *
- * Allocate a new string and copy up to @max_size bytes from the source
- * string into it. The duplicate string is allocated via sleeping
- * malloc(), thus this function can sleep in no memory conditions.
- * No more than @max_size + 1 bytes is allocated, but if the size
- * occupied by the source string is smaller than @max_size + 1,
- * less is allocated.
- *
- * The allocation cannot fail and the return value is always
- * a valid pointer. The duplicate string is always a well-formed
- * null-terminated UTF-8 string, but it can differ from the source
- * string on the byte level.
- *
- * @param src Source string.
- * @param n   Maximum number of bytes to duplicate.
- *
- * @return Duplicate string.
- *
- */
-char *str_ndup(const char *src, size_t n)
-{
-	size_t size = str_size(src);
-	if (size > n)
-		size = n;
-
-	char *dest = malloc(size + 1);
-	if (!dest)
-		return NULL;
-
-	str_ncpy(dest, size + 1, src, size);
-	return dest;
-}
-
-void order_suffix(const uint64_t val, uint64_t *rv, char *suffix)
-{
-	if (val > UINT64_C(10000000000000000000)) {
-		*rv = val / UINT64_C(1000000000000000000);
-		*suffix = 'Z';
-	} else if (val > UINT64_C(1000000000000000000)) {
-		*rv = val / UINT64_C(1000000000000000);
-		*suffix = 'E';
-	} else if (val > UINT64_C(1000000000000000)) {
-		*rv = val / UINT64_C(1000000000000);
-		*suffix = 'T';
-	} else if (val > UINT64_C(1000000000000)) {
-		*rv = val / UINT64_C(1000000000);
-		*suffix = 'G';
-	} else if (val > UINT64_C(1000000000)) {
-		*rv = val / UINT64_C(1000000);
-		*suffix = 'M';
-	} else if (val > UINT64_C(1000000)) {
-		*rv = val / UINT64_C(1000);
-		*suffix = 'k';
-	} else {
-		*rv = val;
-		*suffix = ' ';
-	}
-}
-
-void bin_order_suffix(const uint64_t val, uint64_t *rv, const char **suffix,
-    bool fixed)
-{
-	if (val > UINT64_C(1152921504606846976)) {
-		*rv = val / UINT64_C(1125899906842624);
-		*suffix = "EiB";
-	} else if (val > UINT64_C(1125899906842624)) {
-		*rv = val / UINT64_C(1099511627776);
-		*suffix = "TiB";
-	} else if (val > UINT64_C(1099511627776)) {
-		*rv = val / UINT64_C(1073741824);
-		*suffix = "GiB";
-	} else if (val > UINT64_C(1073741824)) {
-		*rv = val / UINT64_C(1048576);
-		*suffix = "MiB";
-	} else if (val > UINT64_C(1048576)) {
-		*rv = val / UINT64_C(1024);
-		*suffix = "KiB";
-	} else {
-		*rv = val;
-		if (fixed)
-			*suffix = "B  ";
-		else
-			*suffix = "B";
-	}
-}
-
-/** @}
- */
Index: rnel/generic/src/lib/str_error.c
===================================================================
--- kernel/generic/src/lib/str_error.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,100 +1,0 @@
-/*
- * Copyright (c) 2010 Martin Decky
- * Copyright (c) 2017 CZ.NIC, z.s.p.o.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include <errno.h>
-#include <str.h>
-
-/*
- * The arrays below are automatically generated from the same file that
- * errno.h constants are generated from. Triple-include of the same list
- * with redefinitions of __errno() macro are used to ensure that the
- * information cannot get out of synch. This is inpired by musl libc.
- */
-
-#undef __errno_entry
-#define __errno_entry(name, num, desc) name,
-
-static const errno_t err_num[] = {
-#include <abi/errno.in>
-};
-
-#undef __errno_entry
-#define __errno_entry(name, num, desc) #name,
-
-static const char *err_name[] = {
-#include <abi/errno.in>
-};
-
-#undef __errno_entry
-#define __errno_entry(name, num, desc) "[" #name "] " desc,
-
-static const char *err_desc[] = {
-#include <abi/errno.in>
-};
-
-/* Returns index corresponding to the given errno, or -1 if not found. */
-static int find_errno(errno_t e)
-{
-	/*
-	 * Just a dumb linear search.
-	 * There too few entries to warrant anything smarter.
-	 */
-
-	int len = sizeof(err_num) / sizeof(errno_t);
-
-	for (int i = 0; i < len; i++) {
-		if (err_num[i] == e) {
-			return i;
-		}
-	}
-
-	return -1;
-}
-
-const char *str_error_name(errno_t e)
-{
-	int i = find_errno(e);
-
-	if (i >= 0) {
-		return err_name[i];
-	}
-
-	return "(unknown)";
-}
-
-const char *str_error(errno_t e)
-{
-	int i = find_errno(e);
-
-	if (i >= 0) {
-		return err_desc[i];
-	}
-
-	return "Unknown error code";
-}
Index: rnel/generic/src/lib/strtol.c
===================================================================
--- kernel/generic/src/lib/strtol.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,303 +1,0 @@
-/*
- * Copyright (c) 2005 Martin Decky
- * Copyright (c) 2008 Jiri Svoboda
- * Copyright (c) 2011 Martin Sucha
- * Copyright (c) 2011 Oleg Romanenko
- * Copyright (c) 2011 Jiri Zarevucky
- * 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 kernel_generic
- * @{
- */
-/** @file
- */
-
-#include <assert.h>
-#include <errno.h>
-#include <inttypes.h>
-#include <limits.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <str.h>
-
-// FIXME: The original HelenOS functions return EOVERFLOW instead
-//        of ERANGE. It's a pointless distinction from standard functions,
-//        so we should change that. Beware the callers though.
-
-// TODO: more unit tests
-
-static inline int isdigit(int c)
-{
-	return c >= '0' && c <= '9';
-}
-
-static inline int islower(int c)
-{
-	return c >= 'a' && c <= 'z';
-}
-
-static inline int isupper(int c)
-{
-	return c >= 'A' && c <= 'Z';
-}
-
-static inline int isspace(int c)
-{
-	switch (c) {
-	case ' ':
-	case '\n':
-	case '\t':
-	case '\f':
-	case '\r':
-	case '\v':
-		return 1;
-	default:
-		return 0;
-	}
-}
-
-static inline int _digit_value(int c)
-{
-	if (isdigit(c)) {
-		return c - '0';
-	} else if (islower(c)) {
-		return c - 'a' + 10;
-	} else if (isupper(c)) {
-		return c - 'A' + 10;
-	}
-	return INT_MAX;
-}
-
-/*
- * FIXME: workaround for GCC "optimizing" the overflow check
- * into soft-emulated 128b multiplication using `__multi3`,
- * which we don't currently implement.
- */
-__attribute__((noinline)) static uintmax_t _max_value(int base)
-{
-	return UINTMAX_MAX / base;
-}
-
-static inline int _prefixbase(const char *restrict *nptrptr, bool nonstd)
-{
-	const char *nptr = *nptrptr;
-
-	if (nptr[0] != '0')
-		return 10;
-
-	if (nptr[1] == 'x' || nptr[1] == 'X') {
-		if (_digit_value(nptr[2]) < 16) {
-			*nptrptr += 2;
-			return 16;
-		}
-	}
-
-	if (nonstd) {
-		switch (nptr[1]) {
-		case 'b':
-		case 'B':
-			if (_digit_value(nptr[2]) < 2) {
-				*nptrptr += 2;
-				return 2;
-			}
-			break;
-		case 'o':
-		case 'O':
-			if (_digit_value(nptr[2]) < 8) {
-				*nptrptr += 2;
-				return 8;
-			}
-			break;
-		case 'd':
-		case 'D':
-		case 't':
-		case 'T':
-			if (_digit_value(nptr[2]) < 10) {
-				*nptrptr += 2;
-				return 10;
-			}
-			break;
-		}
-	}
-
-	return 8;
-}
-
-static inline uintmax_t _strtoumax(
-    const char *restrict nptr, char **restrict endptr, int base,
-    bool *restrict sgn, errno_t *err, bool nonstd)
-{
-	assert(nptr != NULL);
-	assert(sgn != NULL);
-
-	const char *first = nptr;
-
-	/* Skip leading whitespace. */
-
-	while (isspace(*nptr)) {
-		nptr++;
-	}
-
-	/* Parse sign, if any. */
-
-	switch (*nptr) {
-	case '-':
-		*sgn = true;
-		nptr++;
-		break;
-	case '+':
-		nptr++;
-		break;
-	}
-
-	/* Figure out the base. */
-
-	if (base == 0)
-		base = _prefixbase(&nptr, nonstd);
-
-	if (base == 16 && !nonstd) {
-		/*
-		 * Standard strto* functions allow hexadecimal prefix to be
-		 * present when base is explicitly set to 16.
-		 * Our nonstandard str_* functions don't allow it.
-		 * I don't know if that is intended, just matching the original
-		 * functionality here.
-		 */
-
-		if (nptr[0] == '0' && (nptr[1] == 'x' || nptr[1] == 'X') &&
-		    _digit_value(nptr[2]) < base)
-			nptr += 2;
-	}
-
-	if (base < 2 || base > 36) {
-		*err = EINVAL;
-		return 0;
-	}
-
-	/* Must be at least one digit. */
-
-	if (_digit_value(*nptr) >= base) {
-		/* No digits on input. */
-		if (endptr != NULL)
-			*endptr = (char *) first;
-		return 0;
-	}
-
-	/* Read the value.  */
-
-	uintmax_t result = 0;
-	uintmax_t max = _max_value(base);
-	int digit;
-
-	while (digit = _digit_value(*nptr), digit < base) {
-		if (result > max ||
-		    __builtin_add_overflow(result * base, digit, &result)) {
-
-			*err = nonstd ? EOVERFLOW : ERANGE;
-			result = UINTMAX_MAX;
-			break;
-		}
-
-		nptr++;
-	}
-
-	/* Set endptr. */
-
-	if (endptr != NULL) {
-		/*
-		 * Move the pointer to the end of the number,
-		 * in case it isn't there already.
-		 * This can happen when the number has legal formatting,
-		 * but is out of range of the target type.
-		 */
-		while (_digit_value(*nptr) < base) {
-			nptr++;
-		}
-
-		*endptr = (char *) nptr;
-	}
-
-	return result;
-}
-
-static inline uintmax_t _strtounsigned(const char *nptr, char **endptr, int base,
-    uintmax_t max, errno_t *err, bool nonstd)
-{
-	bool sgn = false;
-	uintmax_t number = _strtoumax(nptr, endptr, base, &sgn, err, nonstd);
-
-	if (nonstd && sgn) {
-		/* Do not allow negative values */
-		*err = EINVAL;
-		return 0;
-	}
-
-	if (number > max) {
-		*err = nonstd ? EOVERFLOW : ERANGE;
-		return max;
-	}
-
-	return (sgn ? -number : number);
-}
-
-/** Convert string to uint64_t.
- *
- * @param nptr   Pointer to string.
- * @param endptr If not NULL, pointer to the first invalid character
- *               is stored here.
- * @param base   Zero or number between 2 and 36 inclusive.
- * @param strict Do not allow any trailing characters.
- * @param result Result of the conversion.
- *
- * @return EOK if conversion was successful.
- *
- */
-errno_t str_uint64_t(const char *nptr, char **endptr, unsigned int base,
-    bool strict, uint64_t *result)
-{
-	assert(result != NULL);
-
-	errno_t rc = EOK;
-	char *lendptr = (char *) nptr;
-
-	uintmax_t r = _strtounsigned(nptr, &lendptr, base, UINT64_MAX, &rc, true);
-
-	if (endptr)
-		*endptr = lendptr;
-
-	if (rc != EOK)
-		return rc;
-
-	if (strict && *lendptr != '\0')
-		return EINVAL;
-
-	*result = r;
-	return EOK;
-}
-
-/** @}
- */
Index: kernel/generic/src/lib/ubsan.c
===================================================================
--- kernel/generic/src/lib/ubsan.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/lib/ubsan.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -10,5 +10,5 @@
 #include <stddef.h>
 #include <panic.h>
-#include <mem.h>
+#include <memw.h>
 
 #define PRINTF(...) printf(__VA_ARGS__)
Index: kernel/generic/src/main/kinit.c
===================================================================
--- kernel/generic/src/main/kinit.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/main/kinit.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -61,5 +61,5 @@
 #include <stdio.h>
 #include <log.h>
-#include <mem.h>
+#include <memw.h>
 #include <console/console.h>
 #include <interrupt.h>
@@ -69,8 +69,10 @@
 #include <ipc/ipc.h>
 #include <str.h>
+#include <str_error.h>
 #include <sysinfo/stats.h>
 #include <sysinfo/sysinfo.h>
 #include <align.h>
 #include <stdlib.h>
+#include <debug/register.h>
 
 #ifdef CONFIG_SMP
@@ -102,14 +104,9 @@
 	thread_t *thread;
 
-	/*
-	 * Detach kinit as nobody will call thread_join_timeout() on it.
-	 */
-	thread_detach(THREAD);
-
 	interrupts_disable();
 
 #ifdef CONFIG_SMP
 	if (config.cpu_count > 1) {
-		waitq_initialize(&ap_completion_wq);
+		semaphore_initialize(&ap_completion_semaphore, 0);
 
 		/*
@@ -125,7 +122,6 @@
 
 		thread_wire(thread, &cpus[0]);
-		thread_ready(thread);
+		thread_start(thread);
 		thread_join(thread);
-		thread_detach(thread);
 
 		/*
@@ -139,5 +135,6 @@
 			if (thread != NULL) {
 				thread_wire(thread, &cpus[i]);
-				thread_ready(thread);
+				thread_start(thread);
+				thread_detach(thread);
 			} else
 				log(LF_OTHER, LVL_ERROR,
@@ -155,8 +152,10 @@
 	thread = thread_create(kload, NULL, TASK, THREAD_FLAG_NONE,
 	    "kload");
-	if (thread != NULL)
-		thread_ready(thread);
-	else
+	if (thread != NULL) {
+		thread_start(thread);
+		thread_detach(thread);
+	} else {
 		log(LF_OTHER, LVL_ERROR, "Unable to create kload thread");
+	}
 
 #ifdef CONFIG_KCONSOLE
@@ -167,9 +166,11 @@
 		thread = thread_create(kconsole_thread, NULL, TASK,
 		    THREAD_FLAG_NONE, "kconsole");
-		if (thread != NULL)
-			thread_ready(thread);
-		else
+		if (thread != NULL) {
+			thread_start(thread);
+			thread_detach(thread);
+		} else {
 			log(LF_OTHER, LVL_ERROR,
 			    "Unable to create kconsole thread");
+		}
 	}
 #endif /* CONFIG_KCONSOLE */
@@ -187,5 +188,5 @@
 	 */
 	size_t i;
-	program_t programs[CONFIG_INIT_TASKS];
+	program_t programs[CONFIG_INIT_TASKS] = { };
 
 	// FIXME: do not propagate arguments through sysinfo
@@ -242,4 +243,16 @@
 		    PAGE_READ | PAGE_WRITE | PAGE_CACHEABLE);
 		assert(page);
+
+		if (str_cmp(name, "kernel.dbg") == 0) {
+			/*
+			 * Not an actual init task, but rather debug sections extracted
+			 * from the kernel ELF file and handed to us here so we can use
+			 * it for debugging.
+			 */
+
+			register_debug_data((void *) page, init.tasks[i].size);
+			programs[i].task = NULL;
+			continue;
+		}
 
 		if (str_cmp(name, "loader") == 0) {
@@ -259,5 +272,5 @@
 		}
 
-		errno_t rc = program_create_from_image((void *) page, namebuf,
+		errno_t rc = program_create_from_image((void *) page, init.tasks[i].size, namebuf,
 		    &programs[i]);
 
@@ -300,6 +313,8 @@
 	 */
 	for (i = 0; i < init.cnt; i++) {
-		if (programs[i].task != NULL)
+		if (programs[i].task != NULL) {
 			program_ready(&programs[i]);
+			task_release(programs[i].task);
+		}
 	}
 
Index: kernel/generic/src/main/main.c
===================================================================
--- kernel/generic/src/main/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/main/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -80,5 +80,4 @@
 #include <arch/arch.h>
 #include <arch.h>
-#include <arch/faddr.h>
 #include <ipc/ipc.h>
 #include <macros.h>
@@ -132,8 +131,12 @@
 };
 
-context_t ctx;
-
-/** Lowest safe stack virtual address. */
-uintptr_t stack_safe = 0;
+static context_t ctx;
+
+// NOTE: All kernel stacks must be aligned to STACK_SIZE, see CURRENT.
+static const size_t bootstrap_stack_size = STACK_SIZE;
+static _Alignas(STACK_SIZE) uint8_t bootstrap_stack[STACK_SIZE];
+
+/* Just a convenient value for some assembly code. */
+uint8_t *const bootstrap_stack_top = bootstrap_stack + STACK_SIZE;
 
 /*
@@ -170,38 +173,6 @@
 	    ALIGN_UP((uintptr_t) kdata_end - config.base, PAGE_SIZE);
 
-	/*
-	 * NOTE: All kernel stacks must be aligned to STACK_SIZE,
-	 *       see CURRENT.
-	 */
-
-	/* Place the stack after the kernel, init and ballocs. */
-	config.stack_base =
-	    ALIGN_UP(config.base + config.kernel_size, STACK_SIZE);
-	config.stack_size = STACK_SIZE;
-
-	/* Avoid placing stack on top of init */
-	size_t i;
-	for (i = 0; i < init.cnt; i++) {
-		uintptr_t p = init.tasks[i].paddr + init.tasks[i].size;
-		uintptr_t bottom = PA2KA(ALIGN_UP(p, STACK_SIZE));
-
-		if (config.stack_base < bottom)
-			config.stack_base = bottom;
-	}
-
-	/* Avoid placing stack on top of boot allocations. */
-	if (ballocs.size) {
-		uintptr_t bottom =
-		    ALIGN_UP(ballocs.base + ballocs.size, STACK_SIZE);
-		if (config.stack_base < bottom)
-			config.stack_base = bottom;
-	}
-
-	if (config.stack_base < stack_safe)
-		config.stack_base = ALIGN_UP(stack_safe, STACK_SIZE);
-
-	context_save(&ctx);
-	context_set(&ctx, FADDR(main_bsp_separated_stack),
-	    config.stack_base, STACK_SIZE);
+	context_create(&ctx, main_bsp_separated_stack,
+	    bootstrap_stack, bootstrap_stack_size);
 	context_restore(&ctx);
 	/* not reached */
@@ -220,8 +191,6 @@
 	version_print();
 
-	LOG("\nconfig.base=%p config.kernel_size=%zu"
-	    "\nconfig.stack_base=%p config.stack_size=%zu",
-	    (void *) config.base, config.kernel_size,
-	    (void *) config.stack_base, config.stack_size);
+	LOG("\nconfig.base=%p config.kernel_size=%zu",
+	    (void *) config.base, config.kernel_size);
 
 #ifdef CONFIG_KCONSOLE
@@ -311,11 +280,13 @@
 	if (!kinit_thread)
 		panic("Cannot create kinit thread.");
-	thread_ready(kinit_thread);
-
-	/*
-	 * This call to scheduler() will return to kinit,
+	thread_start(kinit_thread);
+	thread_detach(kinit_thread);
+
+	/*
+	 * This call to scheduler_run() will return to kinit,
 	 * starting the thread of kernel threads.
 	 */
-	scheduler();
+	current_copy(CURRENT, (current_t *) CPU_LOCAL->stack);
+	context_replace(scheduler_run, CPU_LOCAL->stack, STACK_SIZE);
 	/* not reached */
 }
@@ -357,6 +328,4 @@
 	ARCH_OP(post_cpu_init);
 
-	current_copy(CURRENT, (current_t *) CPU->stack);
-
 	/*
 	 * If we woke kmp up before we left the kernel stack, we could
@@ -364,8 +333,6 @@
 	 * switch to this cpu's private stack prior to waking kmp up.
 	 */
-	context_save(&CPU->saved_context);
-	context_set(&CPU->saved_context, FADDR(main_ap_separated_stack),
-	    (uintptr_t) CPU->stack, STACK_SIZE);
-	context_restore(&CPU->saved_context);
+	current_copy(CURRENT, (current_t *) CPU_LOCAL->stack);
+	context_replace(main_ap_separated_stack, CPU_LOCAL->stack, STACK_SIZE);
 	/* not reached */
 }
@@ -383,6 +350,6 @@
 	timeout_init();
 
-	waitq_wakeup(&ap_completion_wq, WAKEUP_FIRST);
-	scheduler();
+	semaphore_up(&ap_completion_semaphore);
+	scheduler_run();
 	/* not reached */
 }
Index: kernel/generic/src/main/uinit.c
===================================================================
--- kernel/generic/src/main/uinit.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/main/uinit.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -56,14 +56,4 @@
 void uinit(void *arg)
 {
-	/*
-	 * So far, we don't have a use for joining userspace threads so we
-	 * immediately detach each uinit thread. If joining of userspace threads
-	 * is required, some userspace API based on the kernel mechanism will
-	 * have to be implemented. Moreover, garbage collecting of threads that
-	 * didn't detach themselves and nobody else joined them will have to be
-	 * deployed for the event of forceful task termination.
-	 */
-	thread_detach(THREAD);
-
 #ifdef CONFIG_UDEBUG
 	udebug_stoppable_end();
Index: kernel/generic/src/mm/as.c
===================================================================
--- kernel/generic/src/mm/as.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/mm/as.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -69,5 +69,5 @@
 #include <assert.h>
 #include <stdio.h>
-#include <mem.h>
+#include <memw.h>
 #include <macros.h>
 #include <bitops.h>
@@ -86,5 +86,5 @@
  * address space operations such as creating or locking page tables.
  */
-as_operations_t *as_operations = NULL;
+const as_operations_t *as_operations = NULL;
 
 /** Cache for as_t objects */
@@ -1688,5 +1688,10 @@
 	spinlock_unlock(&asidlock);
 
+	if (AS)
+		as_release(AS);
+
 	AS = new_as;
+
+	as_hold(AS);
 }
 
Index: kernel/generic/src/mm/backend_anon.c
===================================================================
--- kernel/generic/src/mm/backend_anon.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/mm/backend_anon.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -51,5 +51,5 @@
 #include <typedefs.h>
 #include <align.h>
-#include <mem.h>
+#include <memw.h>
 #include <arch.h>
 
Index: kernel/generic/src/mm/backend_elf.c
===================================================================
--- kernel/generic/src/mm/backend_elf.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/mm/backend_elf.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -48,5 +48,5 @@
 #include <genarch/mm/page_ht.h>
 #include <align.h>
-#include <mem.h>
+#include <memw.h>
 #include <macros.h>
 #include <arch.h>
Index: kernel/generic/src/mm/backend_user.c
===================================================================
--- kernel/generic/src/mm/backend_user.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/mm/backend_user.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -50,4 +50,5 @@
 #include <log.h>
 #include <str.h>
+#include <str_error.h>
 
 static bool user_create(as_area_t *);
Index: kernel/generic/src/mm/frame.c
===================================================================
--- kernel/generic/src/mm/frame.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/mm/frame.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -753,5 +753,5 @@
 
 				if (overlaps(addr, PFN2ADDR(confcount),
-				    KA2PA(config.stack_base), config.stack_size))
+				    KA2PA(ballocs.base), ballocs.size))
 					continue;
 
@@ -1121,6 +1121,4 @@
 		frame_mark_unavailable(ADDR2PFN(KA2PA(config.base)),
 		    SIZE2FRAMES(config.kernel_size));
-		frame_mark_unavailable(ADDR2PFN(KA2PA(config.stack_base)),
-		    SIZE2FRAMES(config.stack_size));
 
 		for (size_t i = 0; i < init.cnt; i++)
Index: kernel/generic/src/mm/km.c
===================================================================
--- kernel/generic/src/mm/km.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/mm/km.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -166,10 +166,11 @@
 	page_table_lock(AS_KERNEL, true);
 
-	ipl = tlb_shootdown_start(TLB_INVL_ASID, ASID_KERNEL, 0, 0);
+	size_t pages = size >> PAGE_WIDTH;
+	ipl = tlb_shootdown_start(TLB_INVL_PAGES, ASID_KERNEL, vaddr, pages);
 
 	for (offs = 0; offs < size; offs += PAGE_SIZE)
 		page_mapping_remove(AS_KERNEL, vaddr + offs);
 
-	tlb_invalidate_asid(ASID_KERNEL);
+	tlb_invalidate_pages(ASID_KERNEL, vaddr, pages);
 
 	as_invalidate_translation_cache(AS_KERNEL, 0, -1);
@@ -203,5 +204,5 @@
  *
  * @param vaddr		Virtual address to be unmapped. May be unaligned, but
- *			it must a value previously returned by km_map().
+ *			it must be a value previously returned by km_map().
  * @param size		Size of area starting at vaddr to be unmapped.
  */
Index: kernel/generic/src/mm/malloc.c
===================================================================
--- kernel/generic/src/mm/malloc.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/mm/malloc.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -34,5 +34,5 @@
 #include <bitops.h>
 #include <mm/slab.h>
-#include <mem.h>
+#include <memw.h>
 #include <main/main.h> // malloc_init()
 #include <macros.h>
Index: kernel/generic/src/mm/page.c
===================================================================
--- kernel/generic/src/mm/page.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/mm/page.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -76,5 +76,5 @@
 
 /** Virtual operations for page subsystem. */
-page_mapping_operations_t *page_mapping_operations = NULL;
+const page_mapping_operations_t *page_mapping_operations = NULL;
 
 void page_init(void)
Index: kernel/generic/src/mm/slab.c
===================================================================
--- kernel/generic/src/mm/slab.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/mm/slab.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -106,5 +106,5 @@
 #include <mm/slab.h>
 #include <adt/list.h>
-#include <mem.h>
+#include <memw.h>
 #include <align.h>
 #include <mm/frame.h>
Index: kernel/generic/src/mm/tlb.c
===================================================================
--- kernel/generic/src/mm/tlb.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/mm/tlb.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -95,5 +95,5 @@
 		cpu_t *cpu = &cpus[i];
 
-		irq_spinlock_lock(&cpu->lock, false);
+		irq_spinlock_lock(&cpu->tlb_lock, false);
 		if (cpu->tlb_messages_count == TLB_MESSAGE_QUEUE_LEN) {
 			/*
@@ -116,5 +116,5 @@
 			cpu->tlb_messages[idx].count = count;
 		}
-		irq_spinlock_unlock(&cpu->lock, false);
+		irq_spinlock_unlock(&cpu->tlb_lock, false);
 	}
 
@@ -158,5 +158,5 @@
 	irq_spinlock_unlock(&tlblock, false);
 
-	irq_spinlock_lock(&CPU->lock, false);
+	irq_spinlock_lock(&CPU->tlb_lock, false);
 	assert(CPU->tlb_messages_count <= TLB_MESSAGE_QUEUE_LEN);
 
@@ -189,5 +189,5 @@
 
 	CPU->tlb_messages_count = 0;
-	irq_spinlock_unlock(&CPU->lock, false);
+	irq_spinlock_unlock(&CPU->tlb_lock, false);
 	CPU->tlb_active = true;
 }
Index: rnel/generic/src/preempt/preemption.c
===================================================================
--- kernel/generic/src/preempt/preemption.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,41 +1,0 @@
-/*
- * Copyright (c) 2005 Jakub Jermar
- * 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 kernel_generic
- * @{
- */
-
-/**
- * @file preemption.c
- * @brief Preemption control.
- */
-
-#include <preemption.h>
-
-/** @}
- */
Index: rnel/generic/src/printf/printf_core.c
===================================================================
--- kernel/generic/src/printf/printf_core.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,935 +1,0 @@
-/*
- * Copyright (c) 2001-2004 Jakub Jermar
- * Copyright (c) 2006 Josef Cejka
- * Copyright (c) 2009 Martin Decky
- * 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 kernel_generic
- * @{
- */
-/**
- * @file
- * @brief Printing functions.
- */
-
-#include <printf/printf_core.h>
-#include <print.h>
-#include <stdarg.h>
-#include <macros.h>
-#include <stddef.h>
-#include <str.h>
-#include <arch.h>
-
-/** show prefixes 0x or 0 */
-#define __PRINTF_FLAG_PREFIX       0x00000001
-
-/** signed / unsigned number */
-#define __PRINTF_FLAG_SIGNED       0x00000002
-
-/** print leading zeroes */
-#define __PRINTF_FLAG_ZEROPADDED   0x00000004
-
-/** align to left */
-#define __PRINTF_FLAG_LEFTALIGNED  0x00000010
-
-/** always show + sign */
-#define __PRINTF_FLAG_SHOWPLUS     0x00000020
-
-/** print space instead of plus */
-#define __PRINTF_FLAG_SPACESIGN    0x00000040
-
-/** show big characters */
-#define __PRINTF_FLAG_BIGCHARS     0x00000080
-
-/** number has - sign */
-#define __PRINTF_FLAG_NEGATIVE     0x00000100
-
-/**
- * Buffer big enough for 64-bit number printed in base 2, sign, prefix and 0
- * to terminate string... (last one is only for better testing end of buffer by
- * zero-filling subroutine)
- */
-#define PRINT_NUMBER_BUFFER_SIZE  (64 + 5)
-
-/** Get signed or unsigned integer argument */
-#define PRINTF_GET_INT_ARGUMENT(type, ap, flags) \
-	({ \
-		unsigned type res; \
-		\
-		if ((flags) & __PRINTF_FLAG_SIGNED) { \
-			signed type arg = va_arg((ap), signed type); \
-			\
-			if (arg < 0) { \
-				res = -arg; \
-				(flags) |= __PRINTF_FLAG_NEGATIVE; \
-			} else \
-				res = arg; \
-		} else \
-			res = va_arg((ap), unsigned type); \
-		\
-		res; \
-	})
-
-/** Enumeration of possible arguments types.
- */
-typedef enum {
-	PrintfQualifierByte = 0,
-	PrintfQualifierShort,
-	PrintfQualifierInt,
-	PrintfQualifierLong,
-	PrintfQualifierLongLong,
-	PrintfQualifierPointer,
-	PrintfQualifierSize,
-	PrintfQualifierMax
-} qualifier_t;
-
-static const char *nullstr = "(NULL)";
-static const char *digits_small = "0123456789abcdef";
-static const char *digits_big = "0123456789ABCDEF";
-static const char invalch = U_SPECIAL;
-
-/** Print one or more characters without adding newline.
- *
- * @param buf  Buffer holding characters with size of
- *             at least size bytes. NULL is not allowed!
- * @param size Size of the buffer in bytes.
- * @param ps   Output method and its data.
- *
- * @return Number of characters printed.
- *
- */
-static int printf_putnchars(const char *buf, size_t size,
-    printf_spec_t *ps)
-{
-	return ps->str_write((void *) buf, size, ps->data);
-}
-
-/** Print one or more wide characters without adding newline.
- *
- * @param buf  Buffer holding wide characters with size of
- *             at least size bytes. NULL is not allowed!
- * @param size Size of the buffer in bytes.
- * @param ps   Output method and its data.
- *
- * @return Number of wide characters printed.
- *
- */
-static int printf_wputnchars(const char32_t *buf, size_t size,
-    printf_spec_t *ps)
-{
-	return ps->wstr_write((void *) buf, size, ps->data);
-}
-
-/** Print string without adding a newline.
- *
- * @param str String to print.
- * @param ps  Write function specification and support data.
- *
- * @return Number of characters printed.
- *
- */
-static int printf_putstr(const char *str, printf_spec_t *ps)
-{
-	if (str == NULL)
-		return printf_putnchars(nullstr, str_size(nullstr), ps);
-
-	return ps->str_write((void *) str, str_size(str), ps->data);
-}
-
-/** Print one ASCII character.
- *
- * @param c  ASCII character to be printed.
- * @param ps Output method.
- *
- * @return Number of characters printed.
- *
- */
-static int printf_putchar(const char ch, printf_spec_t *ps)
-{
-	if (!ascii_check(ch))
-		return ps->str_write((void *) &invalch, 1, ps->data);
-
-	return ps->str_write(&ch, 1, ps->data);
-}
-
-/** Print one wide character.
- *
- * @param c  Wide character to be printed.
- * @param ps Output method.
- *
- * @return Number of characters printed.
- *
- */
-static int printf_putuchar(const char32_t ch, printf_spec_t *ps)
-{
-	if (!chr_check(ch))
-		return ps->str_write((void *) &invalch, 1, ps->data);
-
-	return ps->wstr_write(&ch, sizeof(char32_t), ps->data);
-}
-
-/** Print one formatted ASCII character.
- *
- * @param ch    Character to print.
- * @param width Width modifier.
- * @param flags Flags that change the way the character is printed.
- *
- * @return Number of characters printed, negative value on failure.
- *
- */
-static int print_char(const char ch, int width, uint32_t flags, printf_spec_t *ps)
-{
-	size_t counter = 0;
-	if (!(flags & __PRINTF_FLAG_LEFTALIGNED)) {
-		while (--width > 0) {
-			/*
-			 * One space is consumed by the character itself, hence
-			 * the predecrement.
-			 */
-			if (printf_putchar(' ', ps) > 0)
-				counter++;
-		}
-	}
-
-	if (printf_putchar(ch, ps) > 0)
-		counter++;
-
-	while (--width > 0) {
-		/*
-		 * One space is consumed by the character itself, hence
-		 * the predecrement.
-		 */
-		if (printf_putchar(' ', ps) > 0)
-			counter++;
-	}
-
-	return (int) (counter);
-}
-
-/** Print one formatted wide character.
- *
- * @param ch    Character to print.
- * @param width Width modifier.
- * @param flags Flags that change the way the character is printed.
- *
- * @return Number of characters printed, negative value on failure.
- *
- */
-static int print_wchar(const char32_t ch, int width, uint32_t flags, printf_spec_t *ps)
-{
-	size_t counter = 0;
-	if (!(flags & __PRINTF_FLAG_LEFTALIGNED)) {
-		while (--width > 0) {
-			/*
-			 * One space is consumed by the character itself, hence
-			 * the predecrement.
-			 */
-			if (printf_putchar(' ', ps) > 0)
-				counter++;
-		}
-	}
-
-	if (printf_putuchar(ch, ps) > 0)
-		counter++;
-
-	while (--width > 0) {
-		/*
-		 * One space is consumed by the character itself, hence
-		 * the predecrement.
-		 */
-		if (printf_putchar(' ', ps) > 0)
-			counter++;
-	}
-
-	return (int) (counter);
-}
-
-/** Print string.
- *
- * @param str       String to be printed.
- * @param width     Width modifier.
- * @param precision Precision modifier.
- * @param flags     Flags that modify the way the string is printed.
- *
- * @return Number of characters printed, negative value on failure.
- */
-static int print_str(char *str, int width, unsigned int precision,
-    uint32_t flags, printf_spec_t *ps)
-{
-	if (str == NULL)
-		return printf_putstr(nullstr, ps);
-
-	/* Print leading spaces. */
-	size_t strw = str_length(str);
-	if ((precision == 0) || (precision > strw))
-		precision = strw;
-
-	/* Left padding */
-	size_t counter = 0;
-	width -= precision;
-	if (!(flags & __PRINTF_FLAG_LEFTALIGNED)) {
-		while (width-- > 0) {
-			if (printf_putchar(' ', ps) == 1)
-				counter++;
-		}
-	}
-
-	/* Part of @a str fitting into the alloted space. */
-	int retval;
-	size_t size = str_lsize(str, precision);
-	if ((retval = printf_putnchars(str, size, ps)) < 0)
-		return -counter;
-
-	counter += retval;
-
-	/* Right padding */
-	while (width-- > 0) {
-		if (printf_putchar(' ', ps) == 1)
-			counter++;
-	}
-
-	return ((int) counter);
-
-}
-
-/** Print wide string.
- *
- * @param str       Wide string to be printed.
- * @param width     Width modifier.
- * @param precision Precision modifier.
- * @param flags     Flags that modify the way the string is printed.
- *
- * @return Number of wide characters printed, negative value on failure.
- */
-static int print_wstr(char32_t *str, int width, unsigned int precision,
-    uint32_t flags, printf_spec_t *ps)
-{
-	if (str == NULL)
-		return printf_putstr(nullstr, ps);
-
-	/* Print leading spaces. */
-	size_t strw = wstr_length(str);
-	if ((precision == 0) || (precision > strw))
-		precision = strw;
-
-	/* Left padding */
-	size_t counter = 0;
-	width -= precision;
-	if (!(flags & __PRINTF_FLAG_LEFTALIGNED)) {
-		while (width-- > 0) {
-			if (printf_putchar(' ', ps) == 1)
-				counter++;
-		}
-	}
-
-	/* Part of @a wstr fitting into the alloted space. */
-	int retval;
-	size_t size = wstr_lsize(str, precision);
-	if ((retval = printf_wputnchars(str, size, ps)) < 0)
-		return -counter;
-
-	counter += retval;
-
-	/* Right padding */
-	while (width-- > 0) {
-		if (printf_putchar(' ', ps) == 1)
-			counter++;
-	}
-
-	return ((int) counter);
-}
-
-/** Print a number in a given base.
- *
- * Print significant digits of a number in given base.
- *
- * @param num       Number to print.
- * @param width     Width modifier.
- * @param precision Precision modifier.
- * @param base      Base to print the number in (must be between 2 and 16).
- * @param flags     Flags that modify the way the number is printed.
- *
- * @return Number of characters printed.
- *
- */
-static int print_number(uint64_t num, int width, int precision, int base,
-    uint32_t flags, printf_spec_t *ps)
-{
-	const char *digits;
-	if (flags & __PRINTF_FLAG_BIGCHARS)
-		digits = digits_big;
-	else
-		digits = digits_small;
-
-	char data[PRINT_NUMBER_BUFFER_SIZE];
-	char *ptr = &data[PRINT_NUMBER_BUFFER_SIZE - 1];
-
-	/* Size of number with all prefixes and signs */
-	int size = 0;
-
-	/* Put zero at end of string */
-	*ptr-- = 0;
-
-	if (num == 0) {
-		*ptr-- = '0';
-		size++;
-	} else {
-		do {
-			*ptr-- = digits[num % base];
-			size++;
-		} while (num /= base);
-	}
-
-	/* Size of plain number */
-	int number_size = size;
-
-	/*
-	 * Collect the sum of all prefixes/signs/etc. to calculate padding and
-	 * leading zeroes.
-	 */
-	if (flags & __PRINTF_FLAG_PREFIX) {
-		switch (base) {
-		case 2:
-			/* Binary formating is not standard, but usefull */
-			size += 2;
-			break;
-		case 8:
-			size++;
-			break;
-		case 16:
-			size += 2;
-			break;
-		}
-	}
-
-	char sgn = 0;
-	if (flags & __PRINTF_FLAG_SIGNED) {
-		if (flags & __PRINTF_FLAG_NEGATIVE) {
-			sgn = '-';
-			size++;
-		} else if (flags & __PRINTF_FLAG_SHOWPLUS) {
-			sgn = '+';
-			size++;
-		} else if (flags & __PRINTF_FLAG_SPACESIGN) {
-			sgn = ' ';
-			size++;
-		}
-	}
-
-	if (flags & __PRINTF_FLAG_LEFTALIGNED)
-		flags &= ~__PRINTF_FLAG_ZEROPADDED;
-
-	/*
-	 * If the number is left-aligned or precision is specified then
-	 * padding with zeros is ignored.
-	 */
-	if (flags & __PRINTF_FLAG_ZEROPADDED) {
-		if ((precision == 0) && (width > size))
-			precision = width - size + number_size;
-	}
-
-	/* Print leading spaces */
-	if (number_size > precision) {
-		/* Print the whole number, not only a part */
-		precision = number_size;
-	}
-
-	width -= precision + size - number_size;
-	size_t counter = 0;
-
-	if (!(flags & __PRINTF_FLAG_LEFTALIGNED)) {
-		while (width-- > 0) {
-			if (printf_putchar(' ', ps) == 1)
-				counter++;
-		}
-	}
-
-	/* Print sign */
-	if (sgn) {
-		if (printf_putchar(sgn, ps) == 1)
-			counter++;
-	}
-
-	/* Print prefix */
-	if (flags & __PRINTF_FLAG_PREFIX) {
-		switch (base) {
-		case 2:
-			/* Binary formating is not standard, but usefull */
-			if (printf_putchar('0', ps) == 1)
-				counter++;
-			if (flags & __PRINTF_FLAG_BIGCHARS) {
-				if (printf_putchar('B', ps) == 1)
-					counter++;
-			} else {
-				if (printf_putchar('b', ps) == 1)
-					counter++;
-			}
-			break;
-		case 8:
-			if (printf_putchar('o', ps) == 1)
-				counter++;
-			break;
-		case 16:
-			if (printf_putchar('0', ps) == 1)
-				counter++;
-			if (flags & __PRINTF_FLAG_BIGCHARS) {
-				if (printf_putchar('X', ps) == 1)
-					counter++;
-			} else {
-				if (printf_putchar('x', ps) == 1)
-					counter++;
-			}
-			break;
-		}
-	}
-
-	/* Print leading zeroes */
-	precision -= number_size;
-	while (precision-- > 0) {
-		if (printf_putchar('0', ps) == 1)
-			counter++;
-	}
-
-	/* Print the number itself */
-	int retval;
-	if ((retval = printf_putstr(++ptr, ps)) > 0)
-		counter += retval;
-
-	/* Print trailing spaces */
-
-	while (width-- > 0) {
-		if (printf_putchar(' ', ps) == 1)
-			counter++;
-	}
-
-	return ((int) counter);
-}
-
-/** Print formatted string.
- *
- * Print string formatted according to the fmt parameter and variadic arguments.
- * Each formatting directive must have the following form:
- *
- *  \% [ FLAGS ] [ WIDTH ] [ .PRECISION ] [ TYPE ] CONVERSION
- *
- * FLAGS:@n
- *  - "#" Force to print prefix. For \%o conversion, the prefix is 0, for
- *        \%x and \%X prefixes are 0x and 0X and for conversion \%b the
- *        prefix is 0b.
- *
- *  - "-" Align to left.
- *
- *  - "+" Print positive sign just as negative.
- *
- *  - " " If the printed number is positive and "+" flag is not set,
- *        print space in place of sign.
- *
- *  - "0" Print 0 as padding instead of spaces. Zeroes are placed between
- *        sign and the rest of the number. This flag is ignored if "-"
- *        flag is specified.
- *
- * WIDTH:@n
- *  - Specify the minimal width of a printed argument. If it is bigger,
- *    width is ignored. If width is specified with a "*" character instead of
- *    number, width is taken from parameter list. And integer parameter is
- *    expected before parameter for processed conversion specification. If
- *    this value is negative its absolute value is taken and the "-" flag is
- *    set.
- *
- * PRECISION:@n
- *  - Value precision. For numbers it specifies minimum valid numbers.
- *    Smaller numbers are printed with leading zeroes. Bigger numbers are not
- *    affected. Strings with more than precision characters are cut off. Just
- *    as with width, an "*" can be used used instead of a number. An integer
- *    value is then expected in parameters. When both width and precision are
- *    specified using "*", the first parameter is used for width and the
- *    second one for precision.
- *
- * TYPE:@n
- *  - "hh" Signed or unsigned char.@n
- *  - "h"  Signed or unsigned short.@n
- *  - ""   Signed or unsigned int (default value).@n
- *  - "l"  Signed or unsigned long int.@n
- *         If conversion is "c", the character is wint_t (wide character).@n
- *         If conversion is "s", the string is char32_t * (UTF-32 string).@n
- *  - "ll" Signed or unsigned long long int.@n
- *  - "z"  Signed or unsigned ssize_t or site_t.@n
- *
- * CONVERSION:@n
- *  - % Print percentile character itself.
- *
- *  - c Print single character. The character is expected to be plain
- *      ASCII (e.g. only values 0 .. 127 are valid).@n
- *      If type is "l", then the character is expected to be wide character
- *      (e.g. values 0 .. 0x10ffff are valid).
- *
- *  - s Print zero terminated string. If a NULL value is passed as
- *      value, "(NULL)" is printed instead.@n
- *      If type is "l", then the string is expected to be wide string.
- *
- *  - P, p Print value of a pointer. Void * value is expected and it is
- *         printed in hexadecimal notation with prefix (as with
- *         \%#0.8X / \%#0.8x for 32-bit or \%#0.16lX / \%#0.16lx for 64-bit
- *         long pointers).
- *
- *  - b Print value as unsigned binary number. Prefix is not printed by
- *      default. (Nonstandard extension.)
- *
- *  - o Print value as unsigned octal number. Prefix is not printed by
- *      default.
- *
- *  - d, i Print signed decimal number. There is no difference between d
- *         and i conversion.
- *
- *  - u Print unsigned decimal number.
- *
- *  - X, x Print hexadecimal number with upper- or lower-case. Prefix is
- *         not printed by default.
- *
- * All other characters from fmt except the formatting directives are printed
- * verbatim.
- *
- * @param fmt Format NULL-terminated string.
- *
- * @return Number of characters printed, negative value on failure.
- *
- */
-int printf_core(const char *fmt, printf_spec_t *ps, va_list ap)
-{
-	size_t i;        /* Index of the currently processed character from fmt */
-	size_t nxt = 0;  /* Index of the next character from fmt */
-	size_t j = 0;    /* Index to the first not printed nonformating character */
-
-	size_t counter = 0;   /* Number of characters printed */
-	int retval;           /* Return values from nested functions */
-
-	while (true) {
-		i = nxt;
-		char32_t uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-
-		if (uc == 0)
-			break;
-
-		/* Control character */
-		if (uc == '%') {
-			/* Print common characters if any processed */
-			if (i > j) {
-				if ((retval = printf_putnchars(&fmt[j], i - j, ps)) < 0) {
-					/* Error */
-					counter = -counter;
-					goto out;
-				}
-				counter += retval;
-			}
-
-			j = i;
-
-			/* Parse modifiers */
-			uint32_t flags = 0;
-			bool end = false;
-
-			do {
-				i = nxt;
-				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-				switch (uc) {
-				case '#':
-					flags |= __PRINTF_FLAG_PREFIX;
-					break;
-				case '-':
-					flags |= __PRINTF_FLAG_LEFTALIGNED;
-					break;
-				case '+':
-					flags |= __PRINTF_FLAG_SHOWPLUS;
-					break;
-				case ' ':
-					flags |= __PRINTF_FLAG_SPACESIGN;
-					break;
-				case '0':
-					flags |= __PRINTF_FLAG_ZEROPADDED;
-					break;
-				default:
-					end = true;
-				}
-			} while (!end);
-
-			/* Width & '*' operator */
-			int width = 0;
-			if (isdigit(uc)) {
-				while (true) {
-					width *= 10;
-					width += uc - '0';
-
-					i = nxt;
-					uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-					if (uc == 0)
-						break;
-					if (!isdigit(uc))
-						break;
-				}
-			} else if (uc == '*') {
-				/* Get width value from argument list */
-				i = nxt;
-				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-				width = (int) va_arg(ap, int);
-				if (width < 0) {
-					/* Negative width sets '-' flag */
-					width *= -1;
-					flags |= __PRINTF_FLAG_LEFTALIGNED;
-				}
-			}
-
-			/* Precision and '*' operator */
-			int precision = 0;
-			if (uc == '.') {
-				i = nxt;
-				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-				if (isdigit(uc)) {
-					while (true) {
-						precision *= 10;
-						precision += uc - '0';
-
-						i = nxt;
-						uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-						if (uc == 0)
-							break;
-						if (!isdigit(uc))
-							break;
-					}
-				} else if (uc == '*') {
-					/* Get precision value from the argument list */
-					i = nxt;
-					uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-					precision = (int) va_arg(ap, int);
-					if (precision < 0) {
-						/* Ignore negative precision */
-						precision = 0;
-					}
-				}
-			}
-
-			qualifier_t qualifier;
-
-			switch (uc) {
-			case 't':
-				/* ptrdiff_t */
-				if (sizeof(ptrdiff_t) == sizeof(int32_t))
-					qualifier = PrintfQualifierInt;
-				else
-					qualifier = PrintfQualifierLongLong;
-				i = nxt;
-				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-				break;
-			case 'h':
-				/* Char or short */
-				qualifier = PrintfQualifierShort;
-				i = nxt;
-				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-				if (uc == 'h') {
-					i = nxt;
-					uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-					qualifier = PrintfQualifierByte;
-				}
-				break;
-			case 'l':
-				/* Long or long long */
-				qualifier = PrintfQualifierLong;
-				i = nxt;
-				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-				if (uc == 'l') {
-					i = nxt;
-					uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-					qualifier = PrintfQualifierLongLong;
-				}
-				break;
-			case 'z':
-				qualifier = PrintfQualifierSize;
-				i = nxt;
-				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-				break;
-			case 'j':
-				qualifier = PrintfQualifierMax;
-				i = nxt;
-				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-				break;
-			default:
-				/* Default type */
-				qualifier = PrintfQualifierInt;
-			}
-
-			unsigned int base = 10;
-
-			switch (uc) {
-				/*
-				 * String and character conversions.
-				 */
-			case 's':
-				if (qualifier == PrintfQualifierLong)
-					retval = print_wstr(va_arg(ap, char32_t *), width, precision, flags, ps);
-				else
-					retval = print_str(va_arg(ap, char *), width, precision, flags, ps);
-
-				if (retval < 0) {
-					counter = -counter;
-					goto out;
-				}
-
-				counter += retval;
-				j = nxt;
-				continue;
-			case 'c':
-				if (qualifier == PrintfQualifierLong)
-					retval = print_wchar(va_arg(ap, wint_t), width, flags, ps);
-				else
-					retval = print_char(va_arg(ap, unsigned int), width, flags, ps);
-
-				if (retval < 0) {
-					counter = -counter;
-					goto out;
-				}
-
-				counter += retval;
-				j = nxt;
-				continue;
-
-				/*
-				 * Integer values
-				 */
-			case 'P':
-				/* Pointer */
-				flags |= __PRINTF_FLAG_BIGCHARS;
-				/* Fallthrough */
-			case 'p':
-				flags |= __PRINTF_FLAG_PREFIX;
-				flags |= __PRINTF_FLAG_ZEROPADDED;
-				base = 16;
-				qualifier = PrintfQualifierPointer;
-				break;
-			case 'b':
-				base = 2;
-				break;
-			case 'o':
-				base = 8;
-				break;
-			case 'd':
-			case 'i':
-				flags |= __PRINTF_FLAG_SIGNED;
-				/* Fallthrough */
-			case 'u':
-				break;
-			case 'X':
-				flags |= __PRINTF_FLAG_BIGCHARS;
-				/* Fallthrough */
-			case 'x':
-				base = 16;
-				break;
-
-			case '%':
-				/* Percentile itself */
-				j = i;
-				continue;
-
-				/*
-				 * Bad formatting.
-				 */
-			default:
-				/*
-				 * Unknown format. Now, j is the index of '%'
-				 * so we will print whole bad format sequence.
-				 */
-				continue;
-			}
-
-			/* Print integers */
-			size_t size;
-			uint64_t number;
-
-			switch (qualifier) {
-			case PrintfQualifierByte:
-				size = sizeof(unsigned char);
-				number = PRINTF_GET_INT_ARGUMENT(int, ap, flags);
-				break;
-			case PrintfQualifierShort:
-				size = sizeof(unsigned short);
-				number = PRINTF_GET_INT_ARGUMENT(int, ap, flags);
-				break;
-			case PrintfQualifierInt:
-				size = sizeof(unsigned int);
-				number = PRINTF_GET_INT_ARGUMENT(int, ap, flags);
-				break;
-			case PrintfQualifierLong:
-				size = sizeof(unsigned long);
-				number = PRINTF_GET_INT_ARGUMENT(long, ap, flags);
-				break;
-			case PrintfQualifierLongLong:
-				size = sizeof(unsigned long long);
-				number = PRINTF_GET_INT_ARGUMENT(long long, ap, flags);
-				break;
-			case PrintfQualifierPointer:
-				size = sizeof(void *);
-				precision = size << 1;
-				number = (uint64_t) (uintptr_t) va_arg(ap, void *);
-				break;
-			case PrintfQualifierSize:
-				size = sizeof(size_t);
-				number = (uint64_t) va_arg(ap, size_t);
-				break;
-			case PrintfQualifierMax:
-				size = sizeof(uintmax_t);
-				number = (uint64_t) va_arg(ap, uintmax_t);
-				break;
-			default:
-				/* Unknown qualifier */
-				counter = -counter;
-				goto out;
-			}
-
-			if ((retval = print_number(number, width, precision,
-			    base, flags, ps)) < 0) {
-				counter = -counter;
-				goto out;
-			}
-
-			counter += retval;
-			j = nxt;
-		}
-	}
-
-	if (i > j) {
-		if ((retval = printf_putnchars(&fmt[j], i - j, ps)) < 0) {
-			/* Error */
-			counter = -counter;
-			goto out;
-		}
-		counter += retval;
-	}
-
-out:
-	return ((int) counter);
-}
-
-/** @}
- */
Index: kernel/generic/src/printf/vsnprintf.c
===================================================================
--- kernel/generic/src/printf/vsnprintf.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/printf/vsnprintf.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,5 +36,5 @@
 #include <printf/printf_core.h>
 #include <str.h>
-#include <mem.h>
+#include <memw.h>
 #include <errno.h>
 
Index: kernel/generic/src/proc/current.c
===================================================================
--- kernel/generic/src/proc/current.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/proc/current.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -55,4 +55,5 @@
 {
 	the->preemption = 0;
+	the->mutex_locks = 0;
 	the->cpu = NULL;
 	the->thread = NULL;
Index: kernel/generic/src/proc/program.c
===================================================================
--- kernel/generic/src/proc/program.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/proc/program.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -49,4 +49,5 @@
 #include <lib/elf_load.h>
 #include <str.h>
+#include <str_error.h>
 #include <log.h>
 #include <syscall/copy.h>
@@ -98,5 +99,5 @@
 	if (!area) {
 		free(kernel_uarg);
-		task_destroy(prg->task);
+		task_release(prg->task);
 		prg->task = NULL;
 		return ENOMEM;
@@ -118,5 +119,5 @@
 		free(kernel_uarg);
 		as_area_destroy(as, virt);
-		task_destroy(prg->task);
+		task_release(prg->task);
 		prg->task = NULL;
 		return ELIMIT;
@@ -142,5 +143,5 @@
  *
  */
-errno_t program_create_from_image(void *image_addr, char *name, program_t *prg)
+errno_t program_create_from_image(void *image_addr, size_t image_size, char *name, program_t *prg)
 {
 	as_t *as = as_create(0);
@@ -156,6 +157,14 @@
 	}
 
-	return program_create(as, ((elf_header_t *) image_addr)->e_entry,
+	errno_t rc = program_create(as, ((elf_header_t *) image_addr)->e_entry,
 	    name, prg);
+
+	if (rc == EOK) {
+		prg->task->debug_sections = calloc(1, sizeof(debug_sections_t));
+		if (prg->task->debug_sections != NULL)
+			*prg->task->debug_sections = get_debug_sections(image_addr, image_size);
+	}
+
+	return rc;
 }
 
@@ -203,5 +212,7 @@
 void program_ready(program_t *prg)
 {
-	thread_ready(prg->main_thread);
+	thread_start(prg->main_thread);
+	thread_detach(prg->main_thread);
+	prg->main_thread = NULL;
 }
 
@@ -241,4 +252,6 @@
 	program_ready(&prg);
 
+	task_release(prg.task);
+
 	return EOK;
 }
Index: kernel/generic/src/proc/scheduler.c
===================================================================
--- kernel/generic/src/proc/scheduler.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/proc/scheduler.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,5 @@
 /*
  * Copyright (c) 2010 Jakub Jermar
+ * Copyright (c) 2023 Jiří Zárevúcky
  * All rights reserved.
  *
@@ -50,5 +51,4 @@
 #include <time/delay.h>
 #include <arch/asm.h>
-#include <arch/faddr.h>
 #include <arch/cycle.h>
 #include <atomic.h>
@@ -66,68 +66,5 @@
 #include <stacktrace.h>
 
-static void scheduler_separated_stack(void);
-
 atomic_size_t nrdy;  /**< Number of ready threads in the system. */
-
-/** Carry out actions before new task runs. */
-static void before_task_runs(void)
-{
-	before_task_runs_arch();
-}
-
-/** Take actions before new thread runs.
- *
- * Perform actions that need to be
- * taken before the newly selected
- * thread is passed control.
- *
- * THREAD->lock is locked on entry
- *
- */
-static void before_thread_runs(void)
-{
-	before_thread_runs_arch();
-
-#ifdef CONFIG_FPU_LAZY
-	if (THREAD == CPU->fpu_owner)
-		fpu_enable();
-	else
-		fpu_disable();
-#elif defined CONFIG_FPU
-	fpu_enable();
-	if (THREAD->fpu_context_exists)
-		fpu_context_restore(THREAD->saved_fpu_context);
-	else {
-		fpu_init();
-		THREAD->fpu_context_exists = true;
-	}
-#endif
-
-#ifdef CONFIG_UDEBUG
-	if (THREAD->btrace) {
-		istate_t *istate = THREAD->udebug.uspace_state;
-		if (istate != NULL) {
-			printf("Thread %" PRIu64 " stack trace:\n", THREAD->tid);
-			stack_trace_istate(istate);
-		}
-
-		THREAD->btrace = false;
-	}
-#endif
-}
-
-/** Take actions after THREAD had run.
- *
- * Perform actions that need to be
- * taken after the running thread
- * had been preempted by the scheduler.
- *
- * THREAD->lock is locked on entry
- *
- */
-static void after_thread_ran(void)
-{
-	after_thread_ran_arch();
-}
 
 #ifdef CONFIG_FPU_LAZY
@@ -135,20 +72,19 @@
 {
 	fpu_enable();
-	irq_spinlock_lock(&CPU->lock, false);
+
+	/* We need this lock to ensure synchronization with thread destructor. */
+	irq_spinlock_lock(&CPU->fpu_lock, false);
 
 	/* Save old context */
-	if (CPU->fpu_owner != NULL) {
-		irq_spinlock_lock(&CPU->fpu_owner->lock, false);
-		fpu_context_save(CPU->fpu_owner->saved_fpu_context);
-
-		/* Don't prevent migration */
-		CPU->fpu_owner->fpu_context_engaged = false;
-		irq_spinlock_unlock(&CPU->fpu_owner->lock, false);
-		CPU->fpu_owner = NULL;
-	}
-
-	irq_spinlock_lock(&THREAD->lock, false);
+	thread_t *owner = atomic_load_explicit(&CPU->fpu_owner, memory_order_relaxed);
+	if (owner != NULL) {
+		fpu_context_save(&owner->fpu_context);
+		atomic_store_explicit(&CPU->fpu_owner, NULL, memory_order_relaxed);
+	}
+
+	irq_spinlock_unlock(&CPU->fpu_lock, false);
+
 	if (THREAD->fpu_context_exists) {
-		fpu_context_restore(THREAD->saved_fpu_context);
+		fpu_context_restore(&THREAD->fpu_context);
 	} else {
 		fpu_init();
@@ -156,9 +92,5 @@
 	}
 
-	CPU->fpu_owner = THREAD;
-	THREAD->fpu_context_engaged = true;
-	irq_spinlock_unlock(&THREAD->lock, false);
-
-	irq_spinlock_unlock(&CPU->lock, false);
+	atomic_store_explicit(&CPU->fpu_owner, THREAD, memory_order_relaxed);
 }
 #endif /* CONFIG_FPU_LAZY */
@@ -182,35 +114,13 @@
  *
  */
-static thread_t *find_best_thread(void)
-{
+static thread_t *try_find_thread(int *rq_index)
+{
+	assert(interrupts_disabled());
 	assert(CPU != NULL);
 
-loop:
-
-	if (atomic_load(&CPU->nrdy) == 0) {
-		/*
-		 * For there was nothing to run, the CPU goes to sleep
-		 * until a hardware interrupt or an IPI comes.
-		 * This improves energy saving and hyperthreading.
-		 */
-		irq_spinlock_lock(&CPU->lock, false);
-		CPU->idle = true;
-		irq_spinlock_unlock(&CPU->lock, false);
-		interrupts_enable();
-
-		/*
-		 * An interrupt might occur right now and wake up a thread.
-		 * In such case, the CPU will continue to go to sleep
-		 * even though there is a runnable thread.
-		 */
-		cpu_sleep();
-		interrupts_disable();
-		goto loop;
-	}
-
-	assert(!CPU->idle);
-
-	unsigned int i;
-	for (i = 0; i < RQ_COUNT; i++) {
+	if (atomic_load(&CPU->nrdy) == 0)
+		return NULL;
+
+	for (int i = 0; i < RQ_COUNT; i++) {
 		irq_spinlock_lock(&(CPU->rq[i].lock), false);
 		if (CPU->rq[i].n == 0) {
@@ -233,21 +143,71 @@
 		list_remove(&thread->rq_link);
 
-		irq_spinlock_pass(&(CPU->rq[i].lock), &thread->lock);
-
-		thread->cpu = CPU;
-		thread->ticks = us2ticks((i + 1) * 10000);
-		thread->priority = i;  /* Correct rq index */
-
-		/*
-		 * Clear the stolen flag so that it can be migrated
-		 * when load balancing needs emerge.
-		 */
-		thread->stolen = false;
-		irq_spinlock_unlock(&thread->lock, false);
-
+		irq_spinlock_unlock(&(CPU->rq[i].lock), false);
+
+		*rq_index = i;
 		return thread;
 	}
 
-	goto loop;
+	return NULL;
+}
+
+/** Get thread to be scheduled
+ *
+ * Get the optimal thread to be scheduled
+ * according to thread accounting and scheduler
+ * policy.
+ *
+ * @return Thread to be scheduled.
+ *
+ */
+static thread_t *find_best_thread(int *rq_index)
+{
+	assert(interrupts_disabled());
+	assert(CPU != NULL);
+
+	while (true) {
+		thread_t *thread = try_find_thread(rq_index);
+
+		if (thread != NULL)
+			return thread;
+
+		/*
+		 * For there was nothing to run, the CPU goes to sleep
+		 * until a hardware interrupt or an IPI comes.
+		 * This improves energy saving and hyperthreading.
+		 */
+		CPU_LOCAL->idle = true;
+
+		/*
+		 * Go to sleep with interrupts enabled.
+		 * Ideally, this should be atomic, but this is not guaranteed on
+		 * all platforms yet, so it is possible we will go sleep when
+		 * a thread has just become available.
+		 */
+		cpu_interruptible_sleep();
+	}
+}
+
+static void switch_task(task_t *task)
+{
+	/* If the task stays the same, a lot of work is avoided. */
+	if (TASK == task)
+		return;
+
+	as_t *old_as = AS;
+	as_t *new_as = task->as;
+
+	/* It is possible for two tasks to share one address space. */
+	if (old_as != new_as)
+		as_switch(old_as, new_as);
+
+	if (TASK)
+		task_release(TASK);
+
+	TASK = task;
+
+	task_hold(TASK);
+
+	before_task_runs_arch();
 }
 
@@ -265,252 +225,104 @@
 static void relink_rq(int start)
 {
+	assert(interrupts_disabled());
+
+	if (CPU_LOCAL->current_clock_tick < CPU_LOCAL->relink_deadline)
+		return;
+
+	CPU_LOCAL->relink_deadline = CPU_LOCAL->current_clock_tick + NEEDS_RELINK_MAX;
+
+	/* Temporary cache for lists we are moving. */
 	list_t list;
-
 	list_initialize(&list);
-	irq_spinlock_lock(&CPU->lock, false);
-
-	if (CPU->needs_relink > NEEDS_RELINK_MAX) {
-		int i;
-		for (i = start; i < RQ_COUNT - 1; i++) {
-			/* Remember and empty rq[i + 1] */
-
-			irq_spinlock_lock(&CPU->rq[i + 1].lock, false);
-			list_concat(&list, &CPU->rq[i + 1].rq);
-			size_t n = CPU->rq[i + 1].n;
-			CPU->rq[i + 1].n = 0;
-			irq_spinlock_unlock(&CPU->rq[i + 1].lock, false);
-
-			/* Append rq[i + 1] to rq[i] */
-
-			irq_spinlock_lock(&CPU->rq[i].lock, false);
-			list_concat(&CPU->rq[i].rq, &list);
-			CPU->rq[i].n += n;
-			irq_spinlock_unlock(&CPU->rq[i].lock, false);
-		}
-
-		CPU->needs_relink = 0;
-	}
-
-	irq_spinlock_unlock(&CPU->lock, false);
-}
-
-/** The scheduler
- *
- * The thread scheduling procedure.
- * Passes control directly to
- * scheduler_separated_stack().
- *
- */
-void scheduler(void)
-{
-	volatile ipl_t ipl;
-
-	assert(CPU != NULL);
-
-	ipl = interrupts_disable();
-
-	if (atomic_load(&haltstate))
-		halt();
-
-	if (THREAD) {
-		irq_spinlock_lock(&THREAD->lock, false);
-
-		/* Update thread kernel accounting */
-		THREAD->kcycles += get_cycle() - THREAD->last_cycle;
-
+
+	size_t n = 0;
+
+	/* Move every list (except the one with highest priority) one level up. */
+	for (int i = RQ_COUNT - 1; i > start; i--) {
+		irq_spinlock_lock(&CPU->rq[i].lock, false);
+
+		/* Swap lists. */
+		list_swap(&CPU->rq[i].rq, &list);
+
+		/* Swap number of items. */
+		size_t tmpn = CPU->rq[i].n;
+		CPU->rq[i].n = n;
+		n = tmpn;
+
+		irq_spinlock_unlock(&CPU->rq[i].lock, false);
+	}
+
+	/* Append the contents of rq[start + 1]  to rq[start]. */
+	if (n != 0) {
+		irq_spinlock_lock(&CPU->rq[start].lock, false);
+		list_concat(&CPU->rq[start].rq, &list);
+		CPU->rq[start].n += n;
+		irq_spinlock_unlock(&CPU->rq[start].lock, false);
+	}
+}
+
+/**
+ * Do whatever needs to be done with current FPU state before we switch to
+ * another thread.
+ */
+static void fpu_cleanup(void)
+{
 #if (defined CONFIG_FPU) && (!defined CONFIG_FPU_LAZY)
-		fpu_context_save(THREAD->saved_fpu_context);
+	fpu_context_save(&THREAD->fpu_context);
 #endif
-		if (!context_save(&THREAD->saved_context)) {
-			/*
-			 * This is the place where threads leave scheduler();
-			 */
-
-			/* Save current CPU cycle */
-			THREAD->last_cycle = get_cycle();
-
-			irq_spinlock_unlock(&THREAD->lock, false);
-			interrupts_restore(THREAD->saved_context.ipl);
-
-			return;
-		}
-
-		/*
-		 * Interrupt priority level of preempted thread is recorded
-		 * here to facilitate scheduler() invocations from
-		 * interrupts_disable()'d code (e.g. waitq_sleep_timeout()).
-		 *
-		 */
-		THREAD->saved_context.ipl = ipl;
-	}
-
+}
+
+/**
+ * Set correct FPU state for this thread after switch from another thread.
+ */
+static void fpu_restore(void)
+{
+#ifdef CONFIG_FPU_LAZY
 	/*
-	 * Through the 'CURRENT' structure, we keep track of THREAD, TASK, CPU, AS
-	 * and preemption counter. At this point CURRENT could be coming either
-	 * from THREAD's or CPU's stack.
-	 *
+	 * The only concurrent modification possible for fpu_owner here is
+	 * another thread changing it from itself to NULL in its destructor.
 	 */
-	current_copy(CURRENT, (current_t *) CPU->stack);
+	thread_t *owner = atomic_load_explicit(&CPU->fpu_owner,
+	    memory_order_relaxed);
+
+	if (THREAD == owner)
+		fpu_enable();
+	else
+		fpu_disable();
+
+#elif defined CONFIG_FPU
+	fpu_enable();
+	if (THREAD->fpu_context_exists)
+		fpu_context_restore(&THREAD->fpu_context);
+	else {
+		fpu_init();
+		THREAD->fpu_context_exists = true;
+	}
+#endif
+}
+
+/** Things to do before we switch to THREAD context.
+ */
+static void prepare_to_run_thread(int rq_index)
+{
+	relink_rq(rq_index);
+
+	switch_task(THREAD->task);
+
+	assert(atomic_get_unordered(&THREAD->cpu) == CPU);
+
+	atomic_set_unordered(&THREAD->state, Running);
+	atomic_set_unordered(&THREAD->priority, rq_index);  /* Correct rq index */
 
 	/*
-	 * We may not keep the old stack.
-	 * Reason: If we kept the old stack and got blocked, for instance, in
-	 * find_best_thread(), the old thread could get rescheduled by another
-	 * CPU and overwrite the part of its own stack that was also used by
-	 * the scheduler on this CPU.
-	 *
-	 * Moreover, we have to bypass the compiler-generated POP sequence
-	 * which is fooled by SP being set to the very top of the stack.
-	 * Therefore the scheduler() function continues in
-	 * scheduler_separated_stack().
-	 *
+	 * Clear the stolen flag so that it can be migrated
+	 * when load balancing needs emerge.
 	 */
-	context_save(&CPU->saved_context);
-	context_set(&CPU->saved_context, FADDR(scheduler_separated_stack),
-	    (uintptr_t) CPU->stack, STACK_SIZE);
-	context_restore(&CPU->saved_context);
-
-	/* Not reached */
-}
-
-/** Scheduler stack switch wrapper
- *
- * Second part of the scheduler() function
- * using new stack. Handling the actual context
- * switch to a new thread.
- *
- */
-void scheduler_separated_stack(void)
-{
-	DEADLOCK_PROBE_INIT(p_joinwq);
-	task_t *old_task = TASK;
-	as_t *old_as = AS;
-
-	assert((!THREAD) || (irq_spinlock_locked(&THREAD->lock)));
-	assert(CPU != NULL);
-	assert(interrupts_disabled());
-
-	/*
-	 * Hold the current task and the address space to prevent their
-	 * possible destruction should thread_destroy() be called on this or any
-	 * other processor while the scheduler is still using them.
-	 */
-	if (old_task)
-		task_hold(old_task);
-
-	if (old_as)
-		as_hold(old_as);
-
-	if (THREAD) {
-		/* Must be run after the switch to scheduler stack */
-		after_thread_ran();
-
-		switch (THREAD->state) {
-		case Running:
-			irq_spinlock_unlock(&THREAD->lock, false);
-			thread_ready(THREAD);
-			break;
-
-		case Exiting:
-		repeat:
-			if (THREAD->detached) {
-				thread_destroy(THREAD, false);
-			} else {
-				/*
-				 * The thread structure is kept allocated until
-				 * somebody calls thread_detach() on it.
-				 */
-				if (!irq_spinlock_trylock(&THREAD->join_wq.lock)) {
-					/*
-					 * Avoid deadlock.
-					 */
-					irq_spinlock_unlock(&THREAD->lock, false);
-					delay(HZ);
-					irq_spinlock_lock(&THREAD->lock, false);
-					DEADLOCK_PROBE(p_joinwq,
-					    DEADLOCK_THRESHOLD);
-					goto repeat;
-				}
-				_waitq_wakeup_unsafe(&THREAD->join_wq,
-				    WAKEUP_FIRST);
-				irq_spinlock_unlock(&THREAD->join_wq.lock, false);
-
-				THREAD->state = Lingering;
-				irq_spinlock_unlock(&THREAD->lock, false);
-			}
-			break;
-
-		case Sleeping:
-			/*
-			 * Prefer the thread after it's woken up.
-			 */
-			THREAD->priority = -1;
-
-			/*
-			 * We need to release wq->lock which we locked in
-			 * waitq_sleep(). Address of wq->lock is kept in
-			 * THREAD->sleep_queue.
-			 */
-			irq_spinlock_unlock(&THREAD->sleep_queue->lock, false);
-
-			irq_spinlock_unlock(&THREAD->lock, false);
-			break;
-
-		default:
-			/*
-			 * Entering state is unexpected.
-			 */
-			panic("tid%" PRIu64 ": unexpected state %s.",
-			    THREAD->tid, thread_states[THREAD->state]);
-			break;
-		}
-
-		THREAD = NULL;
-	}
-
-	THREAD = find_best_thread();
-
-	irq_spinlock_lock(&THREAD->lock, false);
-	int priority = THREAD->priority;
-	irq_spinlock_unlock(&THREAD->lock, false);
-
-	relink_rq(priority);
-
-	/*
-	 * If both the old and the new task are the same,
-	 * lots of work is avoided.
-	 */
-	if (TASK != THREAD->task) {
-		as_t *new_as = THREAD->task->as;
-
-		/*
-		 * Note that it is possible for two tasks
-		 * to share one address space.
-		 */
-		if (old_as != new_as) {
-			/*
-			 * Both tasks and address spaces are different.
-			 * Replace the old one with the new one.
-			 */
-			as_switch(old_as, new_as);
-		}
-
-		TASK = THREAD->task;
-		before_task_runs();
-	}
-
-	if (old_task)
-		task_release(old_task);
-
-	if (old_as)
-		as_release(old_as);
-
-	irq_spinlock_lock(&THREAD->lock, false);
-	THREAD->state = Running;
+	THREAD->stolen = false;
 
 #ifdef SCHEDULER_VERBOSE
 	log(LF_OTHER, LVL_DEBUG,
 	    "cpu%u: tid %" PRIu64 " (priority=%d, ticks=%" PRIu64
-	    ", nrdy=%zu)", CPU->id, THREAD->tid, THREAD->priority,
+	    ", nrdy=%zu)", CPU->id, THREAD->tid, rq_index,
 	    THREAD->ticks, atomic_load(&CPU->nrdy));
 #endif
@@ -524,13 +336,277 @@
 	 * function must be executed before the switch to the new stack.
 	 */
-	before_thread_runs();
+	before_thread_runs_arch();
+
+#ifdef CONFIG_UDEBUG
+	if (atomic_get_unordered(&THREAD->btrace)) {
+		istate_t *istate = THREAD->udebug.uspace_state;
+		if (istate != NULL) {
+			printf("Thread %" PRIu64 " stack trace:\n", THREAD->tid);
+			stack_trace_istate(istate);
+		} else {
+			printf("Thread %" PRIu64 " interrupt state not available\n", THREAD->tid);
+		}
+
+		atomic_set_unordered(&THREAD->btrace, false);
+	}
+#endif
+
+	fpu_restore();
+
+	/* Time allocation in microseconds. */
+	uint64_t time_to_run = (rq_index + 1) * 10000;
+
+	/* Set the time of next preemption. */
+	CPU_LOCAL->preempt_deadline =
+	    CPU_LOCAL->current_clock_tick + us2ticks(time_to_run);
+
+	/* Save current CPU cycle */
+	THREAD->last_cycle = get_cycle();
+}
+
+static void add_to_rq(thread_t *thread, cpu_t *cpu, int i)
+{
+	/* Add to the appropriate runqueue. */
+	runq_t *rq = &cpu->rq[i];
+
+	irq_spinlock_lock(&rq->lock, false);
+	list_append(&thread->rq_link, &rq->rq);
+	rq->n++;
+	irq_spinlock_unlock(&rq->lock, false);
+
+	atomic_inc(&nrdy);
+	atomic_inc(&cpu->nrdy);
+}
+
+/** Requeue a thread that was just preempted on this CPU.
+ */
+static void thread_requeue_preempted(thread_t *thread)
+{
+	assert(interrupts_disabled());
+	assert(atomic_get_unordered(&thread->state) == Running);
+	assert(atomic_get_unordered(&thread->cpu) == CPU);
+
+	int prio = atomic_get_unordered(&thread->priority);
+
+	if (prio < RQ_COUNT - 1) {
+		prio++;
+		atomic_set_unordered(&thread->priority, prio);
+	}
+
+	atomic_set_unordered(&thread->state, Ready);
+
+	add_to_rq(thread, CPU, prio);
+}
+
+void thread_requeue_sleeping(thread_t *thread)
+{
+	ipl_t ipl = interrupts_disable();
+
+	assert(atomic_get_unordered(&thread->state) == Sleeping || atomic_get_unordered(&thread->state) == Entering);
+
+	atomic_set_unordered(&thread->priority, 0);
+	atomic_set_unordered(&thread->state, Ready);
+
+	/* Prefer the CPU on which the thread ran last */
+	cpu_t *cpu = atomic_get_unordered(&thread->cpu);
+
+	if (!cpu) {
+		cpu = CPU;
+		atomic_set_unordered(&thread->cpu, CPU);
+	}
+
+	add_to_rq(thread, cpu, 0);
+
+	interrupts_restore(ipl);
+}
+
+static void cleanup_after_thread(thread_t *thread)
+{
+	assert(CURRENT->mutex_locks == 0);
+	assert(interrupts_disabled());
+
+	int expected;
+
+	switch (atomic_get_unordered(&thread->state)) {
+	case Running:
+		thread_requeue_preempted(thread);
+		break;
+
+	case Exiting:
+		waitq_close(&thread->join_wq);
+
+		/*
+		 * Release the reference CPU has for the thread.
+		 * If there are no other references (e.g. threads calling join),
+		 * the thread structure is deallocated.
+		 */
+		thread_put(thread);
+		break;
+
+	case Sleeping:
+		expected = SLEEP_INITIAL;
+
+		/* Only set SLEEP_ASLEEP in sleep pad if it's still in initial state */
+		if (!atomic_compare_exchange_strong_explicit(&thread->sleep_state,
+		    &expected, SLEEP_ASLEEP,
+		    memory_order_acq_rel, memory_order_acquire)) {
+
+			assert(expected == SLEEP_WOKE);
+			/* The thread has already been woken up, requeue immediately. */
+			thread_requeue_sleeping(thread);
+		}
+		break;
+
+	default:
+		/*
+		 * Entering state is unexpected.
+		 */
+		panic("tid%" PRIu64 ": unexpected state %s.",
+		    thread->tid, thread_states[atomic_get_unordered(&thread->state)]);
+		break;
+	}
+}
+
+/** Switch to scheduler context to let other threads run. */
+void scheduler_enter(state_t new_state)
+{
+	ipl_t ipl = interrupts_disable();
+
+	assert(CPU != NULL);
+	assert(THREAD != NULL);
+
+	if (atomic_load(&haltstate))
+		halt();
+
+	/* Check if we have a thread to switch to. */
+
+	int rq_index;
+	thread_t *new_thread = try_find_thread(&rq_index);
+
+	if (new_thread == NULL && new_state == Running) {
+		/* No other thread to run, but we still have work to do here. */
+		interrupts_restore(ipl);
+		return;
+	}
+
+	atomic_set_unordered(&THREAD->state, new_state);
+
+	/* Update thread kernel accounting */
+	atomic_time_increment(&THREAD->kcycles, get_cycle() - THREAD->last_cycle);
+
+	fpu_cleanup();
 
 	/*
-	 * Copy the knowledge of CPU, TASK, THREAD and preemption counter to
-	 * thread's stack.
+	 * On Sparc, this saves some extra userspace state that's not
+	 * covered by context_save()/context_restore().
 	 */
-	current_copy(CURRENT, (current_t *) THREAD->kstack);
-
-	context_restore(&THREAD->saved_context);
+	after_thread_ran_arch();
+
+	if (new_thread) {
+		thread_t *old_thread = THREAD;
+		CPU_LOCAL->prev_thread = old_thread;
+		THREAD = new_thread;
+		/* No waiting necessary, we can switch to the new thread directly. */
+		prepare_to_run_thread(rq_index);
+
+		current_copy(CURRENT, (current_t *) new_thread->kstack);
+		context_swap(&old_thread->saved_context, &new_thread->saved_context);
+	} else {
+		/*
+		 * A new thread isn't immediately available, switch to a separate
+		 * stack to sleep or do other idle stuff.
+		 */
+		current_copy(CURRENT, (current_t *) CPU_LOCAL->stack);
+		context_swap(&THREAD->saved_context, &CPU_LOCAL->scheduler_context);
+	}
+
+	assert(CURRENT->mutex_locks == 0);
+	assert(interrupts_disabled());
+
+	/* Check if we need to clean up after another thread. */
+	if (CPU_LOCAL->prev_thread) {
+		cleanup_after_thread(CPU_LOCAL->prev_thread);
+		CPU_LOCAL->prev_thread = NULL;
+	}
+
+	interrupts_restore(ipl);
+}
+
+/** Enter main scheduler loop. Never returns.
+ *
+ * This function switches to a runnable thread as soon as one is available,
+ * after which it is only switched back to if a thread is stopping and there is
+ * no other thread to run in its place. We need a separate context for that
+ * because we're going to block the CPU, which means we need another context
+ * to clean up after the previous thread.
+ */
+void scheduler_run(void)
+{
+	assert(interrupts_disabled());
+
+	assert(CPU != NULL);
+	assert(TASK == NULL);
+	assert(THREAD == NULL);
+	assert(interrupts_disabled());
+
+	while (!atomic_load(&haltstate)) {
+		assert(CURRENT->mutex_locks == 0);
+
+		int rq_index;
+		THREAD = find_best_thread(&rq_index);
+		prepare_to_run_thread(rq_index);
+
+		/*
+		 * Copy the knowledge of CPU, TASK, THREAD and preemption counter to
+		 * thread's stack.
+		 */
+		current_copy(CURRENT, (current_t *) THREAD->kstack);
+
+		/* Switch to thread context. */
+		context_swap(&CPU_LOCAL->scheduler_context, &THREAD->saved_context);
+
+		/* Back from another thread. */
+		assert(CPU != NULL);
+		assert(THREAD != NULL);
+		assert(CURRENT->mutex_locks == 0);
+		assert(interrupts_disabled());
+
+		cleanup_after_thread(THREAD);
+
+		/*
+		 * Necessary because we're allowing interrupts in find_best_thread(),
+		 * so we need to avoid other code referencing the thread we left.
+		 */
+		THREAD = NULL;
+	}
+
+	halt();
+}
+
+/** Thread wrapper.
+ *
+ * This wrapper is provided to ensure that a starting thread properly handles
+ * everything it needs to do when first scheduled, and when it exits.
+ */
+void thread_main_func(void)
+{
+	assert(interrupts_disabled());
+
+	void (*f)(void *) = THREAD->thread_code;
+	void *arg = THREAD->thread_arg;
+
+	/* This is where each thread wakes up after its creation */
+
+	/* Check if we need to clean up after another thread. */
+	if (CPU_LOCAL->prev_thread) {
+		cleanup_after_thread(CPU_LOCAL->prev_thread);
+		CPU_LOCAL->prev_thread = NULL;
+	}
+
+	interrupts_enable();
+
+	f(arg);
+
+	thread_exit();
 
 	/* Not reached */
@@ -538,4 +614,71 @@
 
 #ifdef CONFIG_SMP
+
+static thread_t *steal_thread_from(cpu_t *old_cpu, int i)
+{
+	runq_t *old_rq = &old_cpu->rq[i];
+	runq_t *new_rq = &CPU->rq[i];
+
+	ipl_t ipl = interrupts_disable();
+
+	irq_spinlock_lock(&old_rq->lock, false);
+
+	/*
+	 * If fpu_owner is any thread in the list, its store is seen here thanks to
+	 * the runqueue lock.
+	 */
+	thread_t *fpu_owner = atomic_load_explicit(&old_cpu->fpu_owner,
+	    memory_order_relaxed);
+
+	/* Search rq from the back */
+	list_foreach_rev(old_rq->rq, rq_link, thread_t, thread) {
+
+		/*
+		 * Do not steal CPU-wired threads, threads
+		 * already stolen, threads for which migration
+		 * was temporarily disabled or threads whose
+		 * FPU context is still in the CPU.
+		 */
+		if (thread->stolen || thread->nomigrate || thread == fpu_owner) {
+			continue;
+		}
+
+		thread->stolen = true;
+		atomic_set_unordered(&thread->cpu, CPU);
+
+		/*
+		 * Ready thread on local CPU
+		 */
+
+#ifdef KCPULB_VERBOSE
+		log(LF_OTHER, LVL_DEBUG,
+		    "kcpulb%u: TID %" PRIu64 " -> cpu%u, "
+		    "nrdy=%ld, avg=%ld", CPU->id, thread->tid,
+		    CPU->id, atomic_load(&CPU->nrdy),
+		    atomic_load(&nrdy) / config.cpu_active);
+#endif
+
+		/* Remove thread from ready queue. */
+		old_rq->n--;
+		list_remove(&thread->rq_link);
+		irq_spinlock_unlock(&old_rq->lock, false);
+
+		/* Append thread to local queue. */
+		irq_spinlock_lock(&new_rq->lock, false);
+		list_append(&thread->rq_link, &new_rq->rq);
+		new_rq->n++;
+		irq_spinlock_unlock(&new_rq->lock, false);
+
+		atomic_dec(&old_cpu->nrdy);
+		atomic_inc(&CPU->nrdy);
+		interrupts_restore(ipl);
+		return thread;
+	}
+
+	irq_spinlock_unlock(&old_rq->lock, false);
+	interrupts_restore(ipl);
+	return NULL;
+}
+
 /** Load balancing thread
  *
@@ -550,9 +693,4 @@
 	size_t average;
 	size_t rdy;
-
-	/*
-	 * Detach kcpulb as nobody will call thread_join_timeout() on it.
-	 */
-	thread_detach(THREAD);
 
 loop:
@@ -582,10 +720,9 @@
 	 */
 	size_t acpu;
-	size_t acpu_bias = 0;
 	int rq;
 
 	for (rq = RQ_COUNT - 1; rq >= 0; rq--) {
 		for (acpu = 0; acpu < config.cpu_active; acpu++) {
-			cpu_t *cpu = &cpus[(acpu + acpu_bias) % config.cpu_active];
+			cpu_t *cpu = &cpus[acpu];
 
 			/*
@@ -601,87 +738,6 @@
 				continue;
 
-			irq_spinlock_lock(&(cpu->rq[rq].lock), true);
-			if (cpu->rq[rq].n == 0) {
-				irq_spinlock_unlock(&(cpu->rq[rq].lock), true);
-				continue;
-			}
-
-			thread_t *thread = NULL;
-
-			/* Search rq from the back */
-			link_t *link = list_last(&cpu->rq[rq].rq);
-
-			while (link != NULL) {
-				thread = (thread_t *) list_get_instance(link,
-				    thread_t, rq_link);
-
-				/*
-				 * Do not steal CPU-wired threads, threads
-				 * already stolen, threads for which migration
-				 * was temporarily disabled or threads whose
-				 * FPU context is still in the CPU.
-				 */
-				irq_spinlock_lock(&thread->lock, false);
-
-				if ((!thread->wired) && (!thread->stolen) &&
-				    (!thread->nomigrate) &&
-				    (!thread->fpu_context_engaged)) {
-					/*
-					 * Remove thread from ready queue.
-					 */
-					irq_spinlock_unlock(&thread->lock,
-					    false);
-
-					atomic_dec(&cpu->nrdy);
-					atomic_dec(&nrdy);
-
-					cpu->rq[rq].n--;
-					list_remove(&thread->rq_link);
-
-					break;
-				}
-
-				irq_spinlock_unlock(&thread->lock, false);
-
-				link = list_prev(link, &cpu->rq[rq].rq);
-				thread = NULL;
-			}
-
-			if (thread) {
-				/*
-				 * Ready thread on local CPU
-				 */
-
-				irq_spinlock_pass(&(cpu->rq[rq].lock),
-				    &thread->lock);
-
-#ifdef KCPULB_VERBOSE
-				log(LF_OTHER, LVL_DEBUG,
-				    "kcpulb%u: TID %" PRIu64 " -> cpu%u, "
-				    "nrdy=%ld, avg=%ld", CPU->id, thread->tid,
-				    CPU->id, atomic_load(&CPU->nrdy),
-				    atomic_load(&nrdy) / config.cpu_active);
-#endif
-
-				thread->stolen = true;
-				thread->state = Entering;
-
-				irq_spinlock_unlock(&thread->lock, true);
-				thread_ready(thread);
-
-				if (--count == 0)
-					goto satisfied;
-
-				/*
-				 * We are not satisfied yet, focus on another
-				 * CPU next time.
-				 *
-				 */
-				acpu_bias++;
-
-				continue;
-			} else
-				irq_spinlock_unlock(&(cpu->rq[rq].lock), true);
-
+			if (steal_thread_from(cpu, rq) && --count == 0)
+				goto satisfied;
 		}
 	}
@@ -692,5 +748,5 @@
 		 *
 		 */
-		scheduler();
+		thread_yield();
 	} else {
 		/*
@@ -719,9 +775,6 @@
 			continue;
 
-		irq_spinlock_lock(&cpus[cpu].lock, true);
-
-		printf("cpu%u: address=%p, nrdy=%zu, needs_relink=%zu\n",
-		    cpus[cpu].id, &cpus[cpu], atomic_load(&cpus[cpu].nrdy),
-		    cpus[cpu].needs_relink);
+		printf("cpu%u: address=%p, nrdy=%zu\n",
+		    cpus[cpu].id, &cpus[cpu], atomic_load(&cpus[cpu].nrdy));
 
 		unsigned int i;
@@ -737,5 +790,5 @@
 			    thread) {
 				printf("%" PRIu64 "(%s) ", thread->tid,
-				    thread_states[thread->state]);
+				    thread_states[atomic_get_unordered(&thread->state)]);
 			}
 			printf("\n");
@@ -743,6 +796,4 @@
 			irq_spinlock_unlock(&(cpus[cpu].rq[i].lock), false);
 		}
-
-		irq_spinlock_unlock(&cpus[cpu].lock, true);
 	}
 }
Index: kernel/generic/src/proc/task.c
===================================================================
--- kernel/generic/src/proc/task.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/proc/task.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -158,5 +158,4 @@
 		return rc;
 
-	atomic_store(&task->refcount, 0);
 	atomic_store(&task->lifecount, 0);
 
@@ -201,4 +200,6 @@
 	if (!task)
 		return NULL;
+
+	refcount_init(&task->refcount);
 
 	task_create_arch(task);
@@ -224,4 +225,6 @@
 
 	task->answerbox.active = true;
+
+	task->debug_sections = NULL;
 
 #ifdef CONFIG_UDEBUG
@@ -266,5 +269,5 @@
  *
  */
-void task_destroy(task_t *task)
+static void task_destroy(task_t *task)
 {
 	/*
@@ -297,5 +300,5 @@
 void task_hold(task_t *task)
 {
-	atomic_inc(&task->refcount);
+	refcount_up(&task->refcount);
 }
 
@@ -309,5 +312,5 @@
 void task_release(task_t *task)
 {
-	if ((atomic_predec(&task->refcount)) == 0)
+	if (refcount_down(&task->refcount))
 		task_destroy(task);
 }
@@ -385,10 +388,8 @@
 	irq_spinlock_lock(&tasks_lock, true);
 	irq_spinlock_lock(&TASK->lock, false);
-	irq_spinlock_lock(&threads_lock, false);
 
 	/* Set task name */
 	str_cpy(TASK->name, TASK_NAME_BUFLEN, namebuf);
 
-	irq_spinlock_unlock(&threads_lock, false);
 	irq_spinlock_unlock(&TASK->lock, false);
 	irq_spinlock_unlock(&tasks_lock, true);
@@ -416,22 +417,32 @@
 /** Find task structure corresponding to task ID.
  *
- * The tasks_lock must be already held by the caller of this function and
- * interrupts must be disabled.
- *
  * @param id Task ID.
  *
- * @return Task structure address or NULL if there is no such task ID.
+ * @return Task reference or NULL if there is no such task ID.
  *
  */
 task_t *task_find_by_id(task_id_t id)
 {
-	assert(interrupts_disabled());
-	assert(irq_spinlock_locked(&tasks_lock));
+	task_t *task = NULL;
+
+	irq_spinlock_lock(&tasks_lock, true);
 
 	odlink_t *odlink = odict_find_eq(&tasks, &id, NULL);
-	if (odlink != NULL)
-		return odict_get_instance(odlink, task_t, ltasks);
-
-	return NULL;
+	if (odlink != NULL) {
+		task = odict_get_instance(odlink, task_t, ltasks);
+
+		/*
+		 * The directory of tasks can't hold a reference, since that would
+		 * prevent task from ever being destroyed. That means we have to
+		 * check for the case where the task is already being destroyed, but
+		 * not yet removed from the directory.
+		 */
+		if (!refcount_try_up(&task->refcount))
+			task = NULL;
+	}
+
+	irq_spinlock_unlock(&tasks_lock, true);
+
+	return task;
 }
 
@@ -506,6 +517,4 @@
 	/* Current values of threads */
 	list_foreach(task->threads, th_link, thread_t, thread) {
-		irq_spinlock_lock(&thread->lock, false);
-
 		/* Process only counted threads */
 		if (!thread->uncounted) {
@@ -515,9 +524,7 @@
 			}
 
-			uret += thread->ucycles;
-			kret += thread->kcycles;
+			uret += atomic_time_read(&thread->ucycles);
+			kret += atomic_time_read(&thread->kcycles);
 		}
-
-		irq_spinlock_unlock(&thread->lock, false);
 	}
 
@@ -528,6 +535,5 @@
 static void task_kill_internal(task_t *task)
 {
-	irq_spinlock_lock(&task->lock, false);
-	irq_spinlock_lock(&threads_lock, false);
+	irq_spinlock_lock(&task->lock, true);
 
 	/*
@@ -536,20 +542,8 @@
 
 	list_foreach(task->threads, th_link, thread_t, thread) {
-		bool sleeping = false;
-
-		irq_spinlock_lock(&thread->lock, false);
-
-		thread->interrupted = true;
-		if (thread->state == Sleeping)
-			sleeping = true;
-
-		irq_spinlock_unlock(&thread->lock, false);
-
-		if (sleeping)
-			waitq_interrupt_sleep(thread);
+		thread_interrupt(thread);
 	}
 
-	irq_spinlock_unlock(&threads_lock, false);
-	irq_spinlock_unlock(&task->lock, false);
+	irq_spinlock_unlock(&task->lock, true);
 }
 
@@ -569,15 +563,10 @@
 		return EPERM;
 
-	irq_spinlock_lock(&tasks_lock, true);
-
 	task_t *task = task_find_by_id(id);
-	if (!task) {
-		irq_spinlock_unlock(&tasks_lock, true);
+	if (!task)
 		return ENOENT;
-	}
 
 	task_kill_internal(task);
-	irq_spinlock_unlock(&tasks_lock, true);
-
+	task_release(task);
 	return EOK;
 }
@@ -609,8 +598,5 @@
 	}
 
-	irq_spinlock_lock(&tasks_lock, true);
 	task_kill_internal(TASK);
-	irq_spinlock_unlock(&tasks_lock, true);
-
 	thread_exit();
 }
@@ -641,5 +627,5 @@
 	if (additional)
 		printf("%-8" PRIu64 " %9zu", task->taskid,
-		    atomic_load(&task->refcount));
+		    atomic_load(&task->lifecount));
 	else
 		printf("%-8" PRIu64 " %-14s %-5" PRIu32 " %10p %10p"
@@ -653,5 +639,5 @@
 		printf("%-8" PRIu64 " %9" PRIu64 "%c %9" PRIu64 "%c "
 		    "%9zu\n", task->taskid, ucycles, usuffix, kcycles,
-		    ksuffix, atomic_load(&task->refcount));
+		    ksuffix, atomic_load(&task->lifecount));
 	else
 		printf("%-8" PRIu64 " %-14s %-5" PRIu32 " %18p %18p\n",
Index: kernel/generic/src/proc/thread.c
===================================================================
--- kernel/generic/src/proc/thread.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/proc/thread.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -60,7 +60,6 @@
 #include <arch/interrupt.h>
 #include <smp/ipi.h>
-#include <arch/faddr.h>
 #include <atomic.h>
-#include <mem.h>
+#include <memw.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -69,4 +68,5 @@
 #include <errno.h>
 #include <debug.h>
+#include <halt.h>
 
 /** Thread states */
@@ -94,4 +94,7 @@
  *
  * Members are of type thread_t.
+ *
+ * This structure contains weak references. Any reference from it must not leave
+ * threads_lock critical section unless strengthened via thread_try_ref().
  */
 odict_t threads;
@@ -102,52 +105,7 @@
 static slab_cache_t *thread_cache;
 
-#ifdef CONFIG_FPU
-slab_cache_t *fpu_context_cache;
-#endif
-
 static void *threads_getkey(odlink_t *);
 static int threads_cmp(void *, void *);
 
-/** Thread wrapper.
- *
- * This wrapper is provided to ensure that every thread makes a call to
- * thread_exit() when its implementing function returns.
- *
- * interrupts_disable() is assumed.
- *
- */
-static void cushion(void)
-{
-	void (*f)(void *) = THREAD->thread_code;
-	void *arg = THREAD->thread_arg;
-	THREAD->last_cycle = get_cycle();
-
-	/* This is where each thread wakes up after its creation */
-	irq_spinlock_unlock(&THREAD->lock, false);
-	interrupts_enable();
-
-	f(arg);
-
-	/* Accumulate accounting to the task */
-	irq_spinlock_lock(&THREAD->lock, true);
-	if (!THREAD->uncounted) {
-		thread_update_accounting(true);
-		uint64_t ucycles = THREAD->ucycles;
-		THREAD->ucycles = 0;
-		uint64_t kcycles = THREAD->kcycles;
-		THREAD->kcycles = 0;
-
-		irq_spinlock_pass(&THREAD->lock, &TASK->lock);
-		TASK->ucycles += ucycles;
-		TASK->kcycles += kcycles;
-		irq_spinlock_unlock(&TASK->lock, true);
-	} else
-		irq_spinlock_unlock(&THREAD->lock, true);
-
-	thread_exit();
-
-	/* Not reached */
-}
-
 /** Initialization and allocation for thread_t structure
  *
@@ -157,5 +115,4 @@
 	thread_t *thread = (thread_t *) obj;
 
-	irq_spinlock_initialize(&thread->lock, "thread_t_lock");
 	link_initialize(&thread->rq_link);
 	link_initialize(&thread->wq_link);
@@ -164,11 +121,4 @@
 	/* call the architecture-specific part of the constructor */
 	thr_constructor_arch(thread);
-
-#ifdef CONFIG_FPU
-	thread->saved_fpu_context = slab_alloc(fpu_context_cache,
-	    FRAME_ATOMIC | kmflags);
-	if (!thread->saved_fpu_context)
-		return ENOMEM;
-#endif /* CONFIG_FPU */
 
 	/*
@@ -198,11 +148,6 @@
 	uintptr_t stack_phys =
 	    frame_alloc(STACK_FRAMES, kmflags, STACK_SIZE - 1);
-	if (!stack_phys) {
-#ifdef CONFIG_FPU
-		assert(thread->saved_fpu_context);
-		slab_free(fpu_context_cache, thread->saved_fpu_context);
-#endif
+	if (!stack_phys)
 		return ENOMEM;
-	}
 
 	thread->kstack = (uint8_t *) PA2KA(stack_phys);
@@ -225,9 +170,4 @@
 	frame_free(KA2PA(thread->kstack), STACK_FRAMES);
 
-#ifdef CONFIG_FPU
-	assert(thread->saved_fpu_context);
-	slab_free(fpu_context_cache, thread->saved_fpu_context);
-#endif
-
 	return STACK_FRAMES;  /* number of frames freed */
 }
@@ -243,12 +183,7 @@
 
 	atomic_store(&nrdy, 0);
-	thread_cache = slab_cache_create("thread_t", sizeof(thread_t), 0,
+	thread_cache = slab_cache_create("thread_t", sizeof(thread_t), _Alignof(thread_t),
 	    thr_constructor, thr_destructor, 0);
 
-#ifdef CONFIG_FPU
-	fpu_context_cache = slab_cache_create("fpu_context_t",
-	    sizeof(fpu_context_t), FPU_CONTEXT_ALIGN, NULL, NULL, 0);
-#endif
-
 	odict_initialize(&threads, threads_getkey, threads_cmp);
 }
@@ -261,65 +196,18 @@
 void thread_wire(thread_t *thread, cpu_t *cpu)
 {
-	irq_spinlock_lock(&thread->lock, true);
-	thread->cpu = cpu;
-	thread->wired = true;
-	irq_spinlock_unlock(&thread->lock, true);
-}
-
-/** Invoked right before thread_ready() readies the thread. thread is locked. */
-static void before_thread_is_ready(thread_t *thread)
-{
-	assert(irq_spinlock_locked(&thread->lock));
-}
-
-/** Make thread ready
- *
- * Switch thread to the ready state.
- *
- * @param thread Thread to make ready.
- *
- */
-void thread_ready(thread_t *thread)
-{
-	irq_spinlock_lock(&thread->lock, true);
-
-	assert(thread->state != Ready);
-
-	before_thread_is_ready(thread);
-
-	int i = (thread->priority < RQ_COUNT - 1) ?
-	    ++thread->priority : thread->priority;
-
-	cpu_t *cpu;
-	if (thread->wired || thread->nomigrate || thread->fpu_context_engaged) {
-		/* Cannot ready to another CPU */
-		assert(thread->cpu != NULL);
-		cpu = thread->cpu;
-	} else if (thread->stolen) {
-		/* Ready to the stealing CPU */
-		cpu = CPU;
-	} else if (thread->cpu) {
-		/* Prefer the CPU on which the thread ran last */
-		assert(thread->cpu != NULL);
-		cpu = thread->cpu;
-	} else {
-		cpu = CPU;
-	}
-
-	thread->state = Ready;
-
-	irq_spinlock_pass(&thread->lock, &(cpu->rq[i].lock));
-
-	/*
-	 * Append thread to respective ready queue
-	 * on respective processor.
-	 */
-
-	list_append(&thread->rq_link, &cpu->rq[i].rq);
-	cpu->rq[i].n++;
-	irq_spinlock_unlock(&(cpu->rq[i].lock), true);
-
-	atomic_inc(&nrdy);
-	atomic_inc(&cpu->nrdy);
+	ipl_t ipl = interrupts_disable();
+	atomic_set_unordered(&thread->cpu, cpu);
+	thread->nomigrate++;
+	interrupts_restore(ipl);
+}
+
+/** Start a thread that wasn't started yet since it was created.
+ *
+ * @param thread A reference to the newly created thread.
+ */
+void thread_start(thread_t *thread)
+{
+	assert(atomic_get_unordered(&thread->state) == Entering);
+	thread_requeue_sleeping(thread_ref(thread));
 }
 
@@ -346,4 +234,6 @@
 		return NULL;
 
+	refcount_init(&thread->refcount);
+
 	if (thread_create_arch(thread, flags) != EOK) {
 		slab_free(thread_cache, thread);
@@ -358,13 +248,8 @@
 	irq_spinlock_unlock(&tidlock, true);
 
-	memset(&thread->saved_context, 0, sizeof(thread->saved_context));
-	context_set(&thread->saved_context, FADDR(cushion),
-	    (uintptr_t) thread->kstack, STACK_SIZE);
+	context_create(&thread->saved_context, thread_main_func,
+	    thread->kstack, STACK_SIZE);
 
 	current_initialize((current_t *) thread->kstack);
-
-	ipl_t ipl = interrupts_disable();
-	thread->saved_context.ipl = interrupts_read();
-	interrupts_restore(ipl);
 
 	str_cpy(thread->name, THREAD_NAME_BUFLEN, name);
@@ -372,12 +257,10 @@
 	thread->thread_code = func;
 	thread->thread_arg = arg;
-	thread->ticks = -1;
-	thread->ucycles = 0;
-	thread->kcycles = 0;
+	thread->ucycles = ATOMIC_TIME_INITIALIZER();
+	thread->kcycles = ATOMIC_TIME_INITIALIZER();
 	thread->uncounted =
 	    ((flags & THREAD_FLAG_UNCOUNTED) == THREAD_FLAG_UNCOUNTED);
-	thread->priority = -1;          /* Start in rq[0] */
-	thread->cpu = NULL;
-	thread->wired = false;
+	atomic_init(&thread->priority, 0);
+	atomic_init(&thread->cpu, NULL);
 	thread->stolen = false;
 	thread->uspace =
@@ -385,11 +268,7 @@
 
 	thread->nomigrate = 0;
-	thread->state = Entering;
-
-	timeout_initialize(&thread->sleep_timeout);
-	thread->sleep_interruptible = false;
-	thread->sleep_composable = false;
-	thread->sleep_queue = NULL;
-	thread->timeout_pending = false;
+	atomic_init(&thread->state, Entering);
+
+	atomic_init(&thread->sleep_queue, NULL);
 
 	thread->in_copy_from_uspace = false;
@@ -397,5 +276,6 @@
 
 	thread->interrupted = false;
-	thread->detached = false;
+	atomic_init(&thread->sleep_state, SLEEP_INITIAL);
+
 	waitq_initialize(&thread->join_wq);
 
@@ -403,5 +283,4 @@
 
 	thread->fpu_context_exists = false;
-	thread->fpu_context_engaged = false;
 
 	odlink_initialize(&thread->lthreads);
@@ -409,5 +288,5 @@
 #ifdef CONFIG_UDEBUG
 	/* Initialize debugging stuff */
-	thread->btrace = false;
+	atomic_init(&thread->btrace, false);
 	udebug_thread_initialize(&thread->udebug);
 #endif
@@ -423,32 +302,61 @@
  * Detach thread from all queues, cpus etc. and destroy it.
  *
- * @param thread  Thread to be destroyed.
- * @param irq_res Indicate whether it should unlock thread->lock
- *                in interrupts-restore mode.
- *
- */
-void thread_destroy(thread_t *thread, bool irq_res)
-{
-	assert(irq_spinlock_locked(&thread->lock));
-	assert((thread->state == Exiting) || (thread->state == Lingering));
+ * @param obj  Thread to be destroyed.
+ *
+ */
+static void thread_destroy(void *obj)
+{
+	thread_t *thread = (thread_t *) obj;
+
+	assert_link_not_used(&thread->rq_link);
+	assert_link_not_used(&thread->wq_link);
+
 	assert(thread->task);
-	assert(thread->cpu);
-
-	irq_spinlock_lock(&thread->cpu->lock, false);
-	if (thread->cpu->fpu_owner == thread)
-		thread->cpu->fpu_owner = NULL;
-	irq_spinlock_unlock(&thread->cpu->lock, false);
-
-	irq_spinlock_pass(&thread->lock, &threads_lock);
-
+
+	ipl_t ipl = interrupts_disable();
+
+	/* Remove thread from global list. */
+	irq_spinlock_lock(&threads_lock, false);
 	odict_remove(&thread->lthreads);
-
-	irq_spinlock_pass(&threads_lock, &thread->task->lock);
+	irq_spinlock_unlock(&threads_lock, false);
+
+	/* Remove thread from task's list and accumulate accounting. */
+	irq_spinlock_lock(&thread->task->lock, false);
+
+	list_remove(&thread->th_link);
 
 	/*
-	 * Detach from the containing task.
+	 * No other CPU has access to this thread anymore, so we don't need
+	 * thread->lock for accessing thread's fields after this point.
 	 */
-	list_remove(&thread->th_link);
-	irq_spinlock_unlock(&thread->task->lock, irq_res);
+
+	if (!thread->uncounted) {
+		thread->task->ucycles += atomic_time_read(&thread->ucycles);
+		thread->task->kcycles += atomic_time_read(&thread->kcycles);
+	}
+
+	irq_spinlock_unlock(&thread->task->lock, false);
+
+	assert((atomic_get_unordered(&thread->state) == Exiting) || (atomic_get_unordered(&thread->state) == Lingering));
+
+	/* Clear cpu->fpu_owner if set to this thread. */
+#ifdef CONFIG_FPU_LAZY
+	cpu_t *cpu = atomic_get_unordered(&thread->cpu);
+	if (cpu) {
+		/*
+		 * We need to lock for this because the old CPU can concurrently try
+		 * to dump this thread's FPU state, in which case we need to wait for
+		 * it to finish. An atomic compare-and-swap wouldn't be enough.
+		 */
+		irq_spinlock_lock(&cpu->fpu_lock, false);
+
+		if (atomic_get_unordered(&cpu->fpu_owner) == thread)
+			atomic_set_unordered(&cpu->fpu_owner, NULL);
+
+		irq_spinlock_unlock(&cpu->fpu_lock, false);
+	}
+#endif
+
+	interrupts_restore(ipl);
 
 	/*
@@ -456,5 +364,14 @@
 	 */
 	task_release(thread->task);
+	thread->task = NULL;
+
 	slab_free(thread_cache, thread);
+}
+
+void thread_put(thread_t *thread)
+{
+	if (refcount_down(&thread->refcount)) {
+		thread_destroy(thread);
+	}
 }
 
@@ -470,8 +387,10 @@
 void thread_attach(thread_t *thread, task_t *task)
 {
+	ipl_t ipl = interrupts_disable();
+
 	/*
 	 * Attach to the specified task.
 	 */
-	irq_spinlock_lock(&task->lock, true);
+	irq_spinlock_lock(&task->lock, false);
 
 	/* Hold a reference to the task. */
@@ -484,11 +403,14 @@
 	list_append(&thread->th_link, &task->threads);
 
-	irq_spinlock_pass(&task->lock, &threads_lock);
+	irq_spinlock_unlock(&task->lock, false);
 
 	/*
 	 * Register this thread in the system-wide dictionary.
 	 */
+	irq_spinlock_lock(&threads_lock, false);
 	odict_insert(&thread->lthreads, &threads, NULL);
-	irq_spinlock_unlock(&threads_lock, true);
+	irq_spinlock_unlock(&threads_lock, false);
+
+	interrupts_restore(ipl);
 }
 
@@ -527,20 +449,6 @@
 	}
 
-restart:
-	irq_spinlock_lock(&THREAD->lock, true);
-	if (THREAD->timeout_pending) {
-		/* Busy waiting for timeouts in progress */
-		irq_spinlock_unlock(&THREAD->lock, true);
-		goto restart;
-	}
-
-	THREAD->state = Exiting;
-	irq_spinlock_unlock(&THREAD->lock, true);
-
-	scheduler();
-
-	/* Not reached */
-	while (true)
-		;
+	scheduler_enter(Exiting);
+	unreachable();
 }
 
@@ -551,44 +459,118 @@
  * blocking call was interruptable. See waitq_sleep_timeout().
  *
- * The caller must guarantee the thread object is valid during the entire
- * function, eg by holding the threads_lock lock.
- *
  * Interrupted threads automatically exit when returning back to user space.
  *
- * @param thread A valid thread object. The caller must guarantee it
- *               will remain valid until thread_interrupt() exits.
+ * @param thread A valid thread object.
  */
 void thread_interrupt(thread_t *thread)
 {
 	assert(thread != NULL);
-
-	irq_spinlock_lock(&thread->lock, true);
-
 	thread->interrupted = true;
-	bool sleeping = (thread->state == Sleeping);
-
-	irq_spinlock_unlock(&thread->lock, true);
-
-	if (sleeping)
-		waitq_interrupt_sleep(thread);
-}
-
-/** Returns true if the thread was interrupted.
- *
- * @param thread A valid thread object. User must guarantee it will
- *               be alive during the entire call.
- * @return true if the thread was already interrupted via thread_interrupt().
- */
-bool thread_interrupted(thread_t *thread)
+	thread_wakeup(thread);
+}
+
+/** Prepare for putting the thread to sleep.
+ *
+ * @returns whether the thread is currently terminating. If THREAD_OK
+ * is returned, the thread is guaranteed to be woken up instantly if the thread
+ * is terminated at any time between this function's return and
+ * thread_wait_finish(). If THREAD_TERMINATING is returned, the thread can still
+ * go to sleep, but doing so will delay termination.
+ */
+thread_termination_state_t thread_wait_start(void)
+{
+	assert(THREAD != NULL);
+
+	/*
+	 * This is an exchange rather than a store so that we can use the acquire
+	 * semantics, which is needed to ensure that code after this operation sees
+	 * memory ops made before thread_wakeup() in other thread, if that wakeup
+	 * was reset by this operation.
+	 *
+	 * In particular, we need this to ensure we can't miss the thread being
+	 * terminated concurrently with a synchronization primitive preparing to
+	 * sleep.
+	 */
+	(void) atomic_exchange_explicit(&THREAD->sleep_state, SLEEP_INITIAL,
+	    memory_order_acquire);
+
+	return THREAD->interrupted ? THREAD_TERMINATING : THREAD_OK;
+}
+
+static void thread_wait_timeout_callback(void *arg)
+{
+	thread_wakeup(arg);
+}
+
+/**
+ * Suspends this thread's execution until thread_wakeup() is called on it,
+ * or deadline is reached.
+ *
+ * The way this would normally be used is that the current thread call
+ * thread_wait_start(), and if interruption has not been signaled, stores
+ * a reference to itself in a synchronized structure (such as waitq).
+ * After that, it releases any spinlocks it might hold and calls this function.
+ *
+ * The thread doing the wakeup will acquire the thread's reference from said
+ * synchronized structure and calls thread_wakeup() on it.
+ *
+ * Notably, there can be more than one thread performing wakeup.
+ * The number of performed calls to thread_wakeup(), or their relative
+ * ordering with thread_wait_finish(), does not matter. However, calls to
+ * thread_wakeup() are expected to be synchronized with thread_wait_start()
+ * with which they are associated, otherwise wakeups may be missed.
+ * However, the operation of thread_wakeup() is defined at any time,
+ * synchronization notwithstanding (in the sense of C un/defined behavior),
+ * and is in fact used to interrupt waiting threads by external events.
+ * The waiting thread must operate correctly in face of spurious wakeups,
+ * and clean up its reference in the synchronization structure if necessary.
+ *
+ * Returns THREAD_WAIT_TIMEOUT if timeout fired, which is a necessary condition
+ * for it to have been waken up by the timeout, but the caller must assume
+ * that proper wakeups, timeouts and interrupts may occur concurrently, so
+ * the fact timeout has been registered does not necessarily mean the thread
+ * has not been woken up or interrupted.
+ */
+thread_wait_result_t thread_wait_finish(deadline_t deadline)
+{
+	assert(THREAD != NULL);
+
+	timeout_t timeout;
+
+	/* Extra check to avoid going to scheduler if we don't need to. */
+	if (atomic_load_explicit(&THREAD->sleep_state, memory_order_acquire) !=
+	    SLEEP_INITIAL)
+		return THREAD_WAIT_SUCCESS;
+
+	if (deadline != DEADLINE_NEVER) {
+		timeout_initialize(&timeout);
+		timeout_register_deadline(&timeout, deadline,
+		    thread_wait_timeout_callback, THREAD);
+	}
+
+	scheduler_enter(Sleeping);
+
+	if (deadline != DEADLINE_NEVER && !timeout_unregister(&timeout)) {
+		return THREAD_WAIT_TIMEOUT;
+	} else {
+		return THREAD_WAIT_SUCCESS;
+	}
+}
+
+void thread_wakeup(thread_t *thread)
 {
 	assert(thread != NULL);
 
-	bool interrupted;
-
-	irq_spinlock_lock(&thread->lock, true);
-	interrupted = thread->interrupted;
-	irq_spinlock_unlock(&thread->lock, true);
-
-	return interrupted;
+	int state = atomic_exchange_explicit(&thread->sleep_state, SLEEP_WOKE,
+	    memory_order_acq_rel);
+
+	if (state == SLEEP_ASLEEP) {
+		/*
+		 * Only one thread gets to do this.
+		 * The reference consumed here is the reference implicitly passed to
+		 * the waking thread by the sleeper in thread_wait_finish().
+		 */
+		thread_requeue_sleeping(thread);
+	}
 }
 
@@ -596,7 +578,10 @@
 void thread_migration_disable(void)
 {
+	ipl_t ipl = interrupts_disable();
+
 	assert(THREAD);
-
 	THREAD->nomigrate++;
+
+	interrupts_restore(ipl);
 }
 
@@ -604,4 +589,6 @@
 void thread_migration_enable(void)
 {
+	ipl_t ipl = interrupts_disable();
+
 	assert(THREAD);
 	assert(THREAD->nomigrate > 0);
@@ -609,4 +596,6 @@
 	if (THREAD->nomigrate > 0)
 		THREAD->nomigrate--;
+
+	interrupts_restore(ipl);
 }
 
@@ -632,5 +621,11 @@
 }
 
+errno_t thread_join(thread_t *thread)
+{
+	return thread_join_timeout(thread, SYNCH_NO_TIMEOUT, SYNCH_FLAGS_NONE);
+}
+
 /** Wait for another thread to exit.
+ * After successful wait, the thread reference is destroyed.
  *
  * @param thread Thread to join on exit.
@@ -643,52 +638,20 @@
 errno_t thread_join_timeout(thread_t *thread, uint32_t usec, unsigned int flags)
 {
+	assert(thread != NULL);
+
 	if (thread == THREAD)
 		return EINVAL;
 
-	/*
-	 * Since thread join can only be called once on an undetached thread,
-	 * the thread pointer is guaranteed to be still valid.
-	 */
-
-	irq_spinlock_lock(&thread->lock, true);
-	assert(!thread->detached);
-	irq_spinlock_unlock(&thread->lock, true);
-
-	return waitq_sleep_timeout(&thread->join_wq, usec, flags, NULL);
-
-	// FIXME: join should deallocate the thread.
-	//        Current code calls detach after join, that's contrary to how
-	//        join is used in other threading APIs.
-}
-
-/** Detach thread.
- *
- * Mark the thread as detached. If the thread is already
- * in the Lingering state, deallocate its resources.
- *
- * @param thread Thread to be detached.
- *
- */
+	errno_t rc = _waitq_sleep_timeout(&thread->join_wq, usec, flags);
+
+	if (rc == EOK)
+		thread_put(thread);
+
+	return rc;
+}
+
 void thread_detach(thread_t *thread)
 {
-	/*
-	 * Since the thread is expected not to be already detached,
-	 * pointer to it must be still valid.
-	 */
-	irq_spinlock_lock(&thread->lock, true);
-	assert(!thread->detached);
-
-	if (thread->state == Lingering) {
-		/*
-		 * Unlock &thread->lock and restore
-		 * interrupts in thread_destroy().
-		 */
-		thread_destroy(thread, true);
-		return;
-	} else {
-		thread->detached = true;
-	}
-
-	irq_spinlock_unlock(&thread->lock, true);
+	thread_put(thread);
 }
 
@@ -706,5 +669,12 @@
 	waitq_initialize(&wq);
 
-	(void) waitq_sleep_timeout(&wq, usec, SYNCH_FLAGS_NON_BLOCKING, NULL);
+	(void) waitq_sleep_timeout(&wq, usec);
+}
+
+/** Allow other threads to run. */
+void thread_yield(void)
+{
+	assert(THREAD != NULL);
+	scheduler_enter(Running);
 }
 
@@ -713,6 +683,8 @@
 	uint64_t ucycles, kcycles;
 	char usuffix, ksuffix;
-	order_suffix(thread->ucycles, &ucycles, &usuffix);
-	order_suffix(thread->kcycles, &kcycles, &ksuffix);
+	order_suffix(atomic_time_read(&thread->ucycles), &ucycles, &usuffix);
+	order_suffix(atomic_time_read(&thread->kcycles), &kcycles, &ksuffix);
+
+	state_t state = atomic_get_unordered(&thread->state);
 
 	char *name;
@@ -722,41 +694,22 @@
 		name = thread->name;
 
-#ifdef __32_BITS__
 	if (additional)
-		printf("%-8" PRIu64 " %10p %10p %9" PRIu64 "%c %9" PRIu64 "%c ",
+		printf("%-8" PRIu64 " %p %p %9" PRIu64 "%c %9" PRIu64 "%c ",
 		    thread->tid, thread->thread_code, thread->kstack,
 		    ucycles, usuffix, kcycles, ksuffix);
 	else
-		printf("%-8" PRIu64 " %-14s %10p %-8s %10p %-5" PRIu32 "\n",
-		    thread->tid, name, thread, thread_states[thread->state],
+		printf("%-8" PRIu64 " %-14s %p %-8s %p %-5" PRIu32 "\n",
+		    thread->tid, name, thread, thread_states[state],
 		    thread->task, thread->task->container);
-#endif
-
-#ifdef __64_BITS__
-	if (additional)
-		printf("%-8" PRIu64 " %18p %18p\n"
-		    "         %9" PRIu64 "%c %9" PRIu64 "%c ",
-		    thread->tid, thread->thread_code, thread->kstack,
-		    ucycles, usuffix, kcycles, ksuffix);
-	else
-		printf("%-8" PRIu64 " %-14s %18p %-8s %18p %-5" PRIu32 "\n",
-		    thread->tid, name, thread, thread_states[thread->state],
-		    thread->task, thread->task->container);
-#endif
 
 	if (additional) {
-		if (thread->cpu)
-			printf("%-5u", thread->cpu->id);
+		cpu_t *cpu = atomic_get_unordered(&thread->cpu);
+		if (cpu)
+			printf("%-5u", cpu->id);
 		else
 			printf("none ");
 
-		if (thread->state == Sleeping) {
-#ifdef __32_BITS__
-			printf(" %10p", thread->sleep_queue);
-#endif
-
-#ifdef __64_BITS__
-			printf(" %18p", thread->sleep_queue);
-#endif
+		if (state == Sleeping) {
+			printf(" %p", thread->sleep_queue);
 		}
 
@@ -774,24 +727,22 @@
 	thread_t *thread;
 
-	/* Messing with thread structures, avoid deadlock */
+	/* Accessing system-wide threads list through thread_first()/thread_next(). */
 	irq_spinlock_lock(&threads_lock, true);
 
-#ifdef __32_BITS__
-	if (additional)
-		printf("[id    ] [code    ] [stack   ] [ucycles ] [kcycles ]"
-		    " [cpu] [waitqueue]\n");
-	else
-		printf("[id    ] [name        ] [address ] [state ] [task    ]"
-		    " [ctn]\n");
-#endif
-
-#ifdef __64_BITS__
-	if (additional) {
-		printf("[id    ] [code            ] [stack           ]\n"
-		    "         [ucycles ] [kcycles ] [cpu] [waitqueue       ]\n");
-	} else
-		printf("[id    ] [name        ] [address         ] [state ]"
-		    " [task            ] [ctn]\n");
-#endif
+	if (sizeof(void *) <= 4) {
+		if (additional)
+			printf("[id    ] [code    ] [stack   ] [ucycles ] [kcycles ]"
+			    " [cpu] [waitqueue]\n");
+		else
+			printf("[id    ] [name        ] [address ] [state ] [task    ]"
+			    " [ctn]\n");
+	} else {
+		if (additional) {
+			printf("[id    ] [code            ] [stack           ] [ucycles ] [kcycles ]"
+			    " [cpu] [waitqueue       ]\n");
+		} else
+			printf("[id    ] [name        ] [address         ] [state ]"
+			    " [task            ] [ctn]\n");
+	}
 
 	thread = thread_first();
@@ -804,23 +755,28 @@
 }
 
-/** Check whether thread exists.
- *
- * Note that threads_lock must be already held and
- * interrupts must be already disabled.
- *
- * @param thread Pointer to thread.
- *
- * @return True if thread t is known to the system, false otherwise.
- *
- */
-bool thread_exists(thread_t *thread)
-{
-	assert(interrupts_disabled());
-	assert(irq_spinlock_locked(&threads_lock));
-
+static bool thread_exists(thread_t *thread)
+{
 	odlink_t *odlink = odict_find_eq(&threads, thread, NULL);
 	return odlink != NULL;
 }
 
+/** Check whether the thread exists, and if so, return a reference to it.
+ */
+thread_t *thread_try_get(thread_t *thread)
+{
+	irq_spinlock_lock(&threads_lock, true);
+
+	if (thread_exists(thread)) {
+		/* Try to strengthen the reference. */
+		thread = thread_try_ref(thread);
+	} else {
+		thread = NULL;
+	}
+
+	irq_spinlock_unlock(&threads_lock, true);
+
+	return thread;
+}
+
 /** Update accounting of current thread.
  *
@@ -833,13 +789,12 @@
 void thread_update_accounting(bool user)
 {
+	assert(interrupts_disabled());
+
 	uint64_t time = get_cycle();
 
-	assert(interrupts_disabled());
-	assert(irq_spinlock_locked(&THREAD->lock));
-
 	if (user)
-		THREAD->ucycles += time - THREAD->last_cycle;
+		atomic_time_increment(&THREAD->ucycles, time - THREAD->last_cycle);
 	else
-		THREAD->kcycles += time - THREAD->last_cycle;
+		atomic_time_increment(&THREAD->kcycles, time - THREAD->last_cycle);
 
 	THREAD->last_cycle = time;
@@ -850,4 +805,8 @@
  * The threads_lock must be already held by the caller of this function and
  * interrupts must be disabled.
+ *
+ * The returned reference is weak.
+ * If the caller needs to keep it, thread_try_ref() must be used to upgrade
+ * to a strong reference _before_ threads_lock is released.
  *
  * @param id Thread ID.
@@ -928,13 +887,11 @@
 {
 	irq_spinlock_lock(&threads_lock, true);
-
-	thread_t *thread = thread_find_by_id(thread_id);
+	thread_t *thread = thread_try_ref(thread_find_by_id(thread_id));
+	irq_spinlock_unlock(&threads_lock, true);
+
 	if (thread == NULL) {
 		printf("No such thread.\n");
-		irq_spinlock_unlock(&threads_lock, true);
 		return;
 	}
-
-	irq_spinlock_lock(&thread->lock, false);
 
 	/*
@@ -950,20 +907,9 @@
 	 */
 
-	bool sleeping = false;
-	istate_t *istate = thread->udebug.uspace_state;
-	if (istate != NULL) {
-		printf("Scheduling thread stack trace.\n");
-		thread->btrace = true;
-		if (thread->state == Sleeping)
-			sleeping = true;
-	} else
-		printf("Thread interrupt state not available.\n");
-
-	irq_spinlock_unlock(&thread->lock, false);
-
-	if (sleeping)
-		waitq_interrupt_sleep(thread);
-
-	irq_spinlock_unlock(&threads_lock, true);
+	printf("Scheduling thread stack trace.\n");
+	atomic_set_unordered(&thread->btrace, true);
+
+	thread_wakeup(thread);
+	thread_put(thread);
 }
 
@@ -1065,5 +1011,6 @@
 		thread_attach(thread, TASK);
 #endif
-		thread_ready(thread);
+		thread_start(thread);
+		thread_put(thread);
 
 		return 0;
Index: kernel/generic/src/security/perm.c
===================================================================
--- kernel/generic/src/security/perm.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/security/perm.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -89,18 +89,19 @@
 		return EPERM;
 
-	irq_spinlock_lock(&tasks_lock, true);
 	task_t *task = task_find_by_id(taskid);
-
-	if ((!task) || (!container_check(CONTAINER, task->container))) {
-		irq_spinlock_unlock(&tasks_lock, true);
+	if (!task)
 		return ENOENT;
+
+	errno_t rc = ENOENT;
+
+	irq_spinlock_lock(&task->lock, true);
+	if (container_check(CONTAINER, task->container)) {
+		task->perms |= perms;
+		rc = EOK;
 	}
-
-	irq_spinlock_lock(&task->lock, false);
-	task->perms |= perms;
-	irq_spinlock_unlock(&task->lock, false);
-
-	irq_spinlock_unlock(&tasks_lock, true);
-	return EOK;
+	irq_spinlock_unlock(&task->lock, true);
+
+	task_release(task);
+	return rc;
 }
 
@@ -118,11 +119,7 @@
 static errno_t perm_revoke(task_id_t taskid, perm_t perms)
 {
-	irq_spinlock_lock(&tasks_lock, true);
-
 	task_t *task = task_find_by_id(taskid);
-	if ((!task) || (!container_check(CONTAINER, task->container))) {
-		irq_spinlock_unlock(&tasks_lock, true);
+	if (!task)
 		return ENOENT;
-	}
 
 	/*
@@ -131,17 +128,20 @@
 	 * doesn't have PERM_PERM.
 	 */
-	irq_spinlock_lock(&TASK->lock, false);
-
-	if ((!(TASK->perms & PERM_PERM)) || (task != TASK)) {
-		irq_spinlock_unlock(&TASK->lock, false);
-		irq_spinlock_unlock(&tasks_lock, true);
+	if (task != TASK && !(perm_get(TASK) & PERM_PERM)) {
+		task_release(task);
 		return EPERM;
 	}
 
-	task->perms &= ~perms;
-	irq_spinlock_unlock(&TASK->lock, false);
-
-	irq_spinlock_unlock(&tasks_lock, true);
-	return EOK;
+	errno_t rc = ENOENT;
+
+	irq_spinlock_lock(&task->lock, true);
+	if (container_check(CONTAINER, task->container)) {
+		task->perms &= ~perms;
+		rc = EOK;
+	}
+	irq_spinlock_unlock(&task->lock, true);
+
+	task_release(task);
+	return rc;
 }
 
Index: kernel/generic/src/smp/smp.c
===================================================================
--- kernel/generic/src/smp/smp.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/smp/smp.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -39,5 +39,5 @@
 #ifdef CONFIG_SMP
 
-waitq_t ap_completion_wq;
+semaphore_t ap_completion_semaphore;
 
 #endif /* CONFIG_SMP */
Index: kernel/generic/src/synch/condvar.c
===================================================================
--- kernel/generic/src/synch/condvar.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/synch/condvar.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -58,5 +58,5 @@
 void condvar_signal(condvar_t *cv)
 {
-	waitq_wakeup(&cv->wq, WAKEUP_FIRST);
+	waitq_signal(&cv->wq);
 }
 
@@ -68,5 +68,5 @@
 void condvar_broadcast(condvar_t *cv)
 {
-	waitq_wakeup(&cv->wq, WAKEUP_ALL);
+	waitq_wake_all(&cv->wq);
 }
 
@@ -76,31 +76,30 @@
  * @param mtx		Mutex.
  * @param usec		Timeout value in microseconds.
- * @param flags		Select mode of operation.
- *
- * For exact description of meaning of possible combinations of usec and flags,
- * see comment for waitq_sleep_timeout().  Note that when
- * SYNCH_FLAGS_NON_BLOCKING is specified here, EAGAIN is always
- * returned.
  *
  * @return		See comment for waitq_sleep_timeout().
  */
-errno_t _condvar_wait_timeout(condvar_t *cv, mutex_t *mtx, uint32_t usec, int flags)
+errno_t condvar_wait_timeout(condvar_t *cv, mutex_t *mtx, uint32_t usec)
 {
-	errno_t rc;
-	ipl_t ipl;
-	bool blocked;
+	wait_guard_t guard = waitq_sleep_prepare(&cv->wq);
 
-	ipl = waitq_sleep_prepare(&cv->wq);
 	/* Unlock only after the waitq is locked so we don't miss a wakeup. */
 	mutex_unlock(mtx);
 
-	cv->wq.missed_wakeups = 0;	/* Enforce blocking. */
-	rc = waitq_sleep_timeout_unsafe(&cv->wq, usec, flags, &blocked);
-	assert(blocked || rc != EOK);
+	errno_t rc = waitq_sleep_timeout_unsafe(&cv->wq, usec, SYNCH_FLAGS_NON_BLOCKING, guard);
 
-	waitq_sleep_finish(&cv->wq, blocked, ipl);
-	/* Lock only after releasing the waitq to avoid a possible deadlock. */
 	mutex_lock(mtx);
+	return rc;
+}
 
+errno_t condvar_wait(condvar_t *cv, mutex_t *mtx)
+{
+	wait_guard_t guard = waitq_sleep_prepare(&cv->wq);
+
+	/* Unlock only after the waitq is locked so we don't miss a wakeup. */
+	mutex_unlock(mtx);
+
+	errno_t rc = waitq_sleep_unsafe(&cv->wq, guard);
+
+	mutex_lock(mtx);
 	return rc;
 }
@@ -127,21 +126,12 @@
     uint32_t usec, int flags)
 {
-	errno_t rc;
-	ipl_t ipl;
-	bool blocked;
-
-	ipl = waitq_sleep_prepare(&cv->wq);
+	wait_guard_t guard = waitq_sleep_prepare(&cv->wq);
 
 	/* Unlock only after the waitq is locked so we don't miss a wakeup. */
 	spinlock_unlock(lock);
 
-	cv->wq.missed_wakeups = 0;	/* Enforce blocking. */
-	rc = waitq_sleep_timeout_unsafe(&cv->wq, usec, flags, &blocked);
-	assert(blocked || rc != EOK);
+	errno_t rc = waitq_sleep_timeout_unsafe(&cv->wq, usec, flags, guard);
 
-	waitq_sleep_finish(&cv->wq, blocked, ipl);
-	/* Lock only after releasing the waitq to avoid a possible deadlock. */
 	spinlock_lock(lock);
-
 	return rc;
 }
Index: kernel/generic/src/synch/irq_spinlock.c
===================================================================
--- kernel/generic/src/synch/irq_spinlock.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ kernel/generic/src/synch/irq_spinlock.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2001-2004 Jakub Jermar
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * 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 kernel_sync
+ * @{
+ */
+
+/**
+ * @file
+ * @brief IRQ Spinlocks.
+ */
+
+#include <arch/asm.h>
+#include <synch/spinlock.h>
+
+#include <cpu.h>
+
+#ifdef CONFIG_DEBUG_SPINLOCK
+
+#define CPU_OWNER ((CPU == NULL) ? (cpu_t *) UINTPTR_MAX : CPU)
+
+static inline bool owned_by_me(irq_spinlock_t *lock)
+{
+	return atomic_load_explicit(&lock->owner, memory_order_relaxed) == CPU_OWNER;
+}
+
+static inline bool not_owned_by_me(irq_spinlock_t *lock)
+{
+	return !owned_by_me(lock);
+}
+
+static inline void claim(irq_spinlock_t *lock)
+{
+	cpu_t *cpu = CPU_OWNER;
+	atomic_store_explicit(&lock->owner, cpu, memory_order_relaxed);
+	CURRENT->mutex_locks++;
+}
+
+static inline void unclaim(irq_spinlock_t *lock)
+{
+	CURRENT->mutex_locks--;
+	atomic_store_explicit(&lock->owner, NULL, memory_order_relaxed);
+}
+
+#else
+
+static inline bool owned_by_me(irq_spinlock_t *lock)
+{
+	return true;
+}
+
+static inline bool not_owned_by_me(irq_spinlock_t *lock)
+{
+	return true;
+}
+
+static inline void claim(irq_spinlock_t *lock)
+{
+}
+
+static inline void unclaim(irq_spinlock_t *lock)
+{
+}
+
+#endif
+
+/** Initialize interrupts-disabled spinlock
+ *
+ * @param lock IRQ spinlock to be initialized.
+ * @param name IRQ spinlock name.
+ *
+ */
+void irq_spinlock_initialize(irq_spinlock_t *lock, const char *name)
+{
+	*lock = (irq_spinlock_t) IRQ_SPINLOCK_INITIALIZER(name);
+}
+
+/** Lock interrupts-disabled spinlock
+ *
+ * Lock a spinlock which requires disabled interrupts.
+ *
+ * @param lock    IRQ spinlock to be locked.
+ * @param irq_dis If true, disables interrupts before locking the spinlock.
+ *                If false, interrupts are expected to be already disabled.
+ *
+ */
+void irq_spinlock_lock(irq_spinlock_t *lock, bool irq_dis)
+{
+	ASSERT_IRQ_SPINLOCK(not_owned_by_me(lock), lock);
+
+	if (irq_dis) {
+		ipl_t ipl = interrupts_disable();
+		spinlock_lock(&(lock->lock));
+
+		lock->guard = true;
+		lock->ipl = ipl;
+	} else {
+		ASSERT_IRQ_SPINLOCK(interrupts_disabled(), lock);
+
+		spinlock_lock(&(lock->lock));
+		ASSERT_IRQ_SPINLOCK(!lock->guard, lock);
+	}
+
+	claim(lock);
+}
+
+/** Unlock interrupts-disabled spinlock
+ *
+ * Unlock a spinlock which requires disabled interrupts.
+ *
+ * @param lock    IRQ spinlock to be unlocked.
+ * @param irq_res If true, interrupts are restored to previously
+ *                saved interrupt level.
+ *
+ */
+void irq_spinlock_unlock(irq_spinlock_t *lock, bool irq_res)
+{
+	ASSERT_IRQ_SPINLOCK(interrupts_disabled(), lock);
+	ASSERT_IRQ_SPINLOCK(owned_by_me(lock), lock);
+
+	unclaim(lock);
+
+	if (irq_res) {
+		ASSERT_IRQ_SPINLOCK(lock->guard, lock);
+
+		lock->guard = false;
+		ipl_t ipl = lock->ipl;
+
+		spinlock_unlock(&(lock->lock));
+		interrupts_restore(ipl);
+	} else {
+		ASSERT_IRQ_SPINLOCK(!lock->guard, lock);
+		spinlock_unlock(&(lock->lock));
+	}
+}
+
+/** Lock interrupts-disabled spinlock
+ *
+ * Lock an interrupts-disabled spinlock conditionally. If the
+ * spinlock is not available at the moment, signal failure.
+ * Interrupts are expected to be already disabled.
+ *
+ * @param lock IRQ spinlock to be locked conditionally.
+ *
+ * @return Zero on failure, non-zero otherwise.
+ *
+ */
+bool irq_spinlock_trylock(irq_spinlock_t *lock)
+{
+	ASSERT_IRQ_SPINLOCK(interrupts_disabled(), lock);
+	bool ret = spinlock_trylock(&(lock->lock));
+	if (ret)
+		claim(lock);
+
+	ASSERT_IRQ_SPINLOCK((!ret) || (!lock->guard), lock);
+	return ret;
+}
+
+/** Pass lock from one interrupts-disabled spinlock to another
+ *
+ * Pass lock from one IRQ spinlock to another IRQ spinlock
+ * without enabling interrupts during the process.
+ *
+ * The first IRQ spinlock is supposed to be locked.
+ *
+ * @param unlock IRQ spinlock to be unlocked.
+ * @param lock   IRQ spinlock to be locked.
+ *
+ */
+void irq_spinlock_pass(irq_spinlock_t *unlock, irq_spinlock_t *lock)
+{
+	ASSERT_IRQ_SPINLOCK(interrupts_disabled(), unlock);
+	ASSERT_IRQ_SPINLOCK(owned_by_me(unlock), unlock);
+	ASSERT_IRQ_SPINLOCK(not_owned_by_me(lock), lock);
+
+	/* Pass guard from unlock to lock */
+	bool guard = unlock->guard;
+	ipl_t ipl = unlock->ipl;
+	unlock->guard = false;
+
+	unclaim(unlock);
+
+	spinlock_unlock(&(unlock->lock));
+	spinlock_lock(&(lock->lock));
+
+	claim(lock);
+
+	ASSERT_IRQ_SPINLOCK(!lock->guard, lock);
+
+	if (guard) {
+		lock->guard = true;
+		lock->ipl = ipl;
+	}
+}
+
+/** Hand-over-hand locking of interrupts-disabled spinlocks
+ *
+ * Implement hand-over-hand locking between two interrupts-disabled
+ * spinlocks without enabling interrupts during the process.
+ *
+ * The first IRQ spinlock is supposed to be locked.
+ *
+ * @param unlock IRQ spinlock to be unlocked.
+ * @param lock   IRQ spinlock to be locked.
+ *
+ */
+void irq_spinlock_exchange(irq_spinlock_t *unlock, irq_spinlock_t *lock)
+{
+	ASSERT_IRQ_SPINLOCK(interrupts_disabled(), unlock);
+	ASSERT_IRQ_SPINLOCK(owned_by_me(unlock), unlock);
+	ASSERT_IRQ_SPINLOCK(not_owned_by_me(lock), lock);
+
+	spinlock_lock(&(lock->lock));
+	ASSERT_IRQ_SPINLOCK(!lock->guard, lock);
+
+	/* Pass guard from unlock to lock */
+	if (unlock->guard) {
+		lock->guard = true;
+		lock->ipl = unlock->ipl;
+		unlock->guard = false;
+	}
+
+	claim(lock);
+	unclaim(unlock);
+
+	spinlock_unlock(&(unlock->lock));
+}
+
+/** Find out whether the IRQ spinlock is currently locked.
+ *
+ * @param lock		IRQ spinlock.
+ * @return		True if the IRQ spinlock is locked, false otherwise.
+ */
+bool irq_spinlock_locked(irq_spinlock_t *lock)
+{
+	return owned_by_me(lock) && spinlock_locked(&lock->lock);
+}
+
+/** @}
+ */
Index: kernel/generic/src/synch/mutex.c
===================================================================
--- kernel/generic/src/synch/mutex.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/synch/mutex.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,5 @@
 /*
  * Copyright (c) 2001-2004 Jakub Jermar
+ * Copyright (c) 2023 Jiří Zárevúcky
  * All rights reserved.
  *
@@ -66,64 +67,89 @@
 bool mutex_locked(mutex_t *mtx)
 {
-	return semaphore_count_get(&mtx->sem) <= 0;
+	errno_t rc = semaphore_trydown(&mtx->sem);
+	if (rc == EOK) {
+		semaphore_up(&mtx->sem);
+	}
+	return rc != EOK;
 }
 
-#define MUTEX_DEADLOCK_THRESHOLD	100000000
+static void mutex_lock_active(mutex_t *mtx)
+{
+	assert((mtx->type == MUTEX_ACTIVE) || !THREAD);
+
+	const unsigned deadlock_treshold = 100000000;
+	unsigned int cnt = 0;
+	bool deadlock_reported = false;
+
+	while (semaphore_trydown(&mtx->sem) != EOK) {
+		if (cnt++ > deadlock_treshold) {
+			printf("cpu%u: looping on active mutex %p\n", CPU->id, mtx);
+			stack_trace();
+			cnt = 0;
+			deadlock_reported = true;
+		}
+	}
+
+	if (deadlock_reported)
+		printf("cpu%u: not deadlocked\n", CPU->id);
+}
 
 /** Acquire mutex.
  *
- * Timeout mode and non-blocking mode can be requested.
+ * This operation is uninterruptible and cannot fail.
+ */
+void mutex_lock(mutex_t *mtx)
+{
+	if (mtx->type == MUTEX_RECURSIVE && mtx->owner == THREAD) {
+		assert(THREAD);
+		mtx->nesting++;
+		return;
+	}
+
+	if (mtx->type == MUTEX_ACTIVE || !THREAD) {
+		mutex_lock_active(mtx);
+		return;
+	}
+
+	semaphore_down(&mtx->sem);
+	mtx->owner = THREAD;
+	mtx->nesting = 1;
+}
+
+/** Acquire mutex with timeout.
  *
  * @param mtx    Mutex.
  * @param usec   Timeout in microseconds.
- * @param flags  Specify mode of operation.
  *
- * For exact description of possible combinations of usec and flags, see
- * comment for waitq_sleep_timeout().
- *
- * @return See comment for waitq_sleep_timeout().
- *
+ * @return EOK if lock was successfully acquired, something else otherwise.
  */
-errno_t _mutex_lock_timeout(mutex_t *mtx, uint32_t usec, unsigned int flags)
+errno_t mutex_lock_timeout(mutex_t *mtx, uint32_t usec)
 {
-	errno_t rc;
-
-	if (mtx->type == MUTEX_PASSIVE && THREAD) {
-		rc = _semaphore_down_timeout(&mtx->sem, usec, flags);
-	} else if (mtx->type == MUTEX_RECURSIVE) {
+	if (usec != 0) {
+		assert(mtx->type != MUTEX_ACTIVE);
 		assert(THREAD);
-
-		if (mtx->owner == THREAD) {
-			mtx->nesting++;
-			return EOK;
-		} else {
-			rc = _semaphore_down_timeout(&mtx->sem, usec, flags);
-			if (rc == EOK) {
-				mtx->owner = THREAD;
-				mtx->nesting = 1;
-			}
-		}
-	} else {
-		assert((mtx->type == MUTEX_ACTIVE) || !THREAD);
-		assert(usec == SYNCH_NO_TIMEOUT);
-		assert(!(flags & SYNCH_FLAGS_INTERRUPTIBLE));
-
-		unsigned int cnt = 0;
-		bool deadlock_reported = false;
-		do {
-			if (cnt++ > MUTEX_DEADLOCK_THRESHOLD) {
-				printf("cpu%u: looping on active mutex %p\n",
-				    CPU->id, mtx);
-				stack_trace();
-				cnt = 0;
-				deadlock_reported = true;
-			}
-			rc = semaphore_trydown(&mtx->sem);
-		} while (rc != EOK && !(flags & SYNCH_FLAGS_NON_BLOCKING));
-		if (deadlock_reported)
-			printf("cpu%u: not deadlocked\n", CPU->id);
 	}
 
+	if (mtx->type == MUTEX_RECURSIVE && mtx->owner == THREAD) {
+		assert(THREAD);
+		mtx->nesting++;
+		return EOK;
+	}
+
+	errno_t rc = semaphore_down_timeout(&mtx->sem, usec);
+	if (rc == EOK) {
+		mtx->owner = THREAD;
+		mtx->nesting = 1;
+	}
 	return rc;
+}
+
+/** Attempt to acquire mutex without blocking.
+ *
+ * @return EOK if lock was successfully acquired, something else otherwise.
+ */
+errno_t mutex_trylock(mutex_t *mtx)
+{
+	return mutex_lock_timeout(mtx, 0);
 }
 
Index: kernel/generic/src/synch/semaphore.c
===================================================================
--- kernel/generic/src/synch/semaphore.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/synch/semaphore.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -52,26 +52,31 @@
 void semaphore_initialize(semaphore_t *sem, int val)
 {
-	waitq_initialize(&sem->wq);
-	waitq_count_set(&sem->wq, val);
+	waitq_initialize_with_count(&sem->wq, val);
 }
 
-/** Semaphore down
- *
- * Semaphore down.
- * Conditional mode and mode with timeout can be requested.
+errno_t semaphore_trydown(semaphore_t *sem)
+{
+	return semaphore_down_timeout(sem, 0);
+}
+
+/** Semaphore down with timeout
  *
  * @param sem   Semaphore.
  * @param usec  Timeout in microseconds.
- * @param flags Select mode of operation.
- *
- * For exact description of possible combinations of
- * usec and flags, see comment for waitq_sleep_timeout().
  *
  * @return See comment for waitq_sleep_timeout().
  *
  */
-errno_t _semaphore_down_timeout(semaphore_t *sem, uint32_t usec, unsigned int flags)
+errno_t semaphore_down_timeout(semaphore_t *sem, uint32_t usec)
 {
-	return waitq_sleep_timeout(&sem->wq, usec, flags, NULL);
+	errno_t rc = waitq_sleep_timeout(&sem->wq, usec);
+	assert(rc == EOK || rc == ETIMEOUT || rc == EAGAIN);
+	return rc;
+}
+
+void semaphore_down(semaphore_t *sem)
+{
+	errno_t rc = waitq_sleep(&sem->wq);
+	assert(rc == EOK);
 }
 
@@ -85,16 +90,5 @@
 void semaphore_up(semaphore_t *sem)
 {
-	waitq_wakeup(&sem->wq, WAKEUP_FIRST);
-}
-
-/** Get the semaphore counter value.
- *
- * @param sem		Semaphore.
- * @return		The number of threads that can down the semaphore
- * 			without blocking.
- */
-int semaphore_count_get(semaphore_t *sem)
-{
-	return waitq_count_get(&sem->wq);
+	waitq_wake_one(&sem->wq);
 }
 
Index: kernel/generic/src/synch/spinlock.c
===================================================================
--- kernel/generic/src/synch/spinlock.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/synch/spinlock.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,5 @@
 /*
  * Copyright (c) 2001-2004 Jakub Jermar
+ * Copyright (c) 2023 Jiří Zárevúcky
  * All rights reserved.
  *
@@ -36,4 +37,7 @@
  */
 
+#ifdef CONFIG_SMP
+
+#include <arch/asm.h>
 #include <synch/spinlock.h>
 #include <atomic.h>
@@ -46,6 +50,4 @@
 #include <stacktrace.h>
 #include <cpu.h>
-
-#ifdef CONFIG_SMP
 
 /** Initialize spinlock
@@ -62,22 +64,20 @@
 }
 
-#ifdef CONFIG_DEBUG_SPINLOCK
-
 /** Lock spinlock
- *
- * Lock spinlock.
- * This version has limitted ability to report
- * possible occurence of deadlock.
  *
  * @param lock Pointer to spinlock_t structure.
  *
  */
-void spinlock_lock_debug(spinlock_t *lock)
+void spinlock_lock(spinlock_t *lock)
 {
+	preemption_disable();
+
+	bool deadlock_reported = false;
 	size_t i = 0;
-	bool deadlock_reported = false;
 
-	preemption_disable();
 	while (atomic_flag_test_and_set_explicit(&lock->flag, memory_order_acquire)) {
+		cpu_spin_hint();
+
+#ifdef CONFIG_DEBUG_SPINLOCK
 		/*
 		 * We need to be careful about particular locks
@@ -111,5 +111,9 @@
 			deadlock_reported = true;
 		}
+#endif
 	}
+
+	/* Avoid compiler warning with debug disabled. */
+	(void) i;
 
 	if (deadlock_reported)
@@ -119,11 +123,11 @@
 /** Unlock spinlock
  *
- * Unlock spinlock.
- *
  * @param sl Pointer to spinlock_t structure.
  */
-void spinlock_unlock_debug(spinlock_t *lock)
+void spinlock_unlock(spinlock_t *lock)
 {
+#ifdef CONFIG_DEBUG_SPINLOCK
 	ASSERT_SPINLOCK(spinlock_locked(lock), lock);
+#endif
 
 	atomic_flag_clear_explicit(&lock->flag, memory_order_release);
@@ -131,8 +135,5 @@
 }
 
-#endif
-
-/** Lock spinlock conditionally
- *
+/**
  * Lock spinlock conditionally. If the spinlock is not available
  * at the moment, signal failure.
@@ -140,5 +141,5 @@
  * @param lock Pointer to spinlock_t structure.
  *
- * @return Zero on failure, non-zero otherwise.
+ * @return true on success.
  *
  */
@@ -146,4 +147,5 @@
 {
 	preemption_disable();
+
 	bool ret = !atomic_flag_test_and_set_explicit(&lock->flag, memory_order_acquire);
 
@@ -172,159 +174,5 @@
 }
 
-#endif
-
-/** Initialize interrupts-disabled spinlock
- *
- * @param lock IRQ spinlock to be initialized.
- * @param name IRQ spinlock name.
- *
- */
-void irq_spinlock_initialize(irq_spinlock_t *lock, const char *name)
-{
-	spinlock_initialize(&(lock->lock), name);
-	lock->guard = false;
-	lock->ipl = 0;
-}
-
-/** Lock interrupts-disabled spinlock
- *
- * Lock a spinlock which requires disabled interrupts.
- *
- * @param lock    IRQ spinlock to be locked.
- * @param irq_dis If true, disables interrupts before locking the spinlock.
- *                If false, interrupts are expected to be already disabled.
- *
- */
-void irq_spinlock_lock(irq_spinlock_t *lock, bool irq_dis)
-{
-	if (irq_dis) {
-		ipl_t ipl = interrupts_disable();
-		spinlock_lock(&(lock->lock));
-
-		lock->guard = true;
-		lock->ipl = ipl;
-	} else {
-		ASSERT_IRQ_SPINLOCK(interrupts_disabled(), lock);
-
-		spinlock_lock(&(lock->lock));
-		ASSERT_IRQ_SPINLOCK(!lock->guard, lock);
-	}
-}
-
-/** Unlock interrupts-disabled spinlock
- *
- * Unlock a spinlock which requires disabled interrupts.
- *
- * @param lock    IRQ spinlock to be unlocked.
- * @param irq_res If true, interrupts are restored to previously
- *                saved interrupt level.
- *
- */
-void irq_spinlock_unlock(irq_spinlock_t *lock, bool irq_res)
-{
-	ASSERT_IRQ_SPINLOCK(interrupts_disabled(), lock);
-
-	if (irq_res) {
-		ASSERT_IRQ_SPINLOCK(lock->guard, lock);
-
-		lock->guard = false;
-		ipl_t ipl = lock->ipl;
-
-		spinlock_unlock(&(lock->lock));
-		interrupts_restore(ipl);
-	} else {
-		ASSERT_IRQ_SPINLOCK(!lock->guard, lock);
-		spinlock_unlock(&(lock->lock));
-	}
-}
-
-/** Lock interrupts-disabled spinlock
- *
- * Lock an interrupts-disabled spinlock conditionally. If the
- * spinlock is not available at the moment, signal failure.
- * Interrupts are expected to be already disabled.
- *
- * @param lock IRQ spinlock to be locked conditionally.
- *
- * @return Zero on failure, non-zero otherwise.
- *
- */
-bool irq_spinlock_trylock(irq_spinlock_t *lock)
-{
-	ASSERT_IRQ_SPINLOCK(interrupts_disabled(), lock);
-	bool ret = spinlock_trylock(&(lock->lock));
-
-	ASSERT_IRQ_SPINLOCK((!ret) || (!lock->guard), lock);
-	return ret;
-}
-
-/** Pass lock from one interrupts-disabled spinlock to another
- *
- * Pass lock from one IRQ spinlock to another IRQ spinlock
- * without enabling interrupts during the process.
- *
- * The first IRQ spinlock is supposed to be locked.
- *
- * @param unlock IRQ spinlock to be unlocked.
- * @param lock   IRQ spinlock to be locked.
- *
- */
-void irq_spinlock_pass(irq_spinlock_t *unlock, irq_spinlock_t *lock)
-{
-	ASSERT_IRQ_SPINLOCK(interrupts_disabled(), unlock);
-
-	/* Pass guard from unlock to lock */
-	bool guard = unlock->guard;
-	ipl_t ipl = unlock->ipl;
-	unlock->guard = false;
-
-	spinlock_unlock(&(unlock->lock));
-	spinlock_lock(&(lock->lock));
-
-	ASSERT_IRQ_SPINLOCK(!lock->guard, lock);
-
-	if (guard) {
-		lock->guard = true;
-		lock->ipl = ipl;
-	}
-}
-
-/** Hand-over-hand locking of interrupts-disabled spinlocks
- *
- * Implement hand-over-hand locking between two interrupts-disabled
- * spinlocks without enabling interrupts during the process.
- *
- * The first IRQ spinlock is supposed to be locked.
- *
- * @param unlock IRQ spinlock to be unlocked.
- * @param lock   IRQ spinlock to be locked.
- *
- */
-void irq_spinlock_exchange(irq_spinlock_t *unlock, irq_spinlock_t *lock)
-{
-	ASSERT_IRQ_SPINLOCK(interrupts_disabled(), unlock);
-
-	spinlock_lock(&(lock->lock));
-	ASSERT_IRQ_SPINLOCK(!lock->guard, lock);
-
-	/* Pass guard from unlock to lock */
-	if (unlock->guard) {
-		lock->guard = true;
-		lock->ipl = unlock->ipl;
-		unlock->guard = false;
-	}
-
-	spinlock_unlock(&(unlock->lock));
-}
-
-/** Find out whether the IRQ spinlock is currently locked.
- *
- * @param lock		IRQ spinlock.
- * @return		True if the IRQ spinlock is locked, false otherwise.
- */
-bool irq_spinlock_locked(irq_spinlock_t *ilock)
-{
-	return spinlock_locked(&ilock->lock);
-}
+#endif  /* CONFIG_SMP */
 
 /** @}
Index: kernel/generic/src/synch/syswaitq.c
===================================================================
--- kernel/generic/src/synch/syswaitq.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/synch/syswaitq.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -159,6 +159,6 @@
 #endif
 
-	errno_t rc = waitq_sleep_timeout(kobj->waitq, timeout,
-	    SYNCH_FLAGS_INTERRUPTIBLE | flags, NULL);
+	errno_t rc = _waitq_sleep_timeout(kobj->waitq, timeout,
+	    SYNCH_FLAGS_INTERRUPTIBLE | flags);
 
 #ifdef CONFIG_UDEBUG
@@ -183,5 +183,5 @@
 		return (sys_errno_t) ENOENT;
 
-	waitq_wakeup(kobj->waitq, WAKEUP_FIRST);
+	waitq_wake_one(kobj->waitq);
 
 	kobject_put(kobj);
Index: kernel/generic/src/synch/waitq.c
===================================================================
--- kernel/generic/src/synch/waitq.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/synch/waitq.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,5 @@
 /*
  * Copyright (c) 2001-2004 Jakub Jermar
+ * Copyright (c) 2022 Jiří Zárevúcky
  * All rights reserved.
  *
@@ -48,4 +49,5 @@
 #include <synch/waitq.h>
 #include <synch/spinlock.h>
+#include <preemption.h>
 #include <proc/thread.h>
 #include <proc/scheduler.h>
@@ -57,8 +59,5 @@
 #include <adt/list.h>
 #include <arch/cycle.h>
-#include <mem.h>
-
-static void waitq_sleep_timed_out(void *);
-static void waitq_complete_wakeup(waitq_t *);
+#include <memw.h>
 
 /** Initialize wait queue
@@ -76,117 +75,12 @@
 }
 
-/** Handle timeout during waitq_sleep_timeout() call
- *
- * This routine is called when waitq_sleep_timeout() times out.
- * Interrupts are disabled.
- *
- * It is supposed to try to remove 'its' thread from the wait queue;
- * it can eventually fail to achieve this goal when these two events
- * overlap. In that case it behaves just as though there was no
- * timeout at all.
- *
- * @param data Pointer to the thread that called waitq_sleep_timeout().
- *
- */
-void waitq_sleep_timed_out(void *data)
-{
-	thread_t *thread = (thread_t *) data;
-	bool do_wakeup = false;
-	DEADLOCK_PROBE_INIT(p_wqlock);
-
-	irq_spinlock_lock(&threads_lock, false);
-	if (!thread_exists(thread))
-		goto out;
-
-grab_locks:
-	irq_spinlock_lock(&thread->lock, false);
-
-	waitq_t *wq;
-	if ((wq = thread->sleep_queue)) {  /* Assignment */
-		if (!irq_spinlock_trylock(&wq->lock)) {
-			irq_spinlock_unlock(&thread->lock, false);
-			DEADLOCK_PROBE(p_wqlock, DEADLOCK_THRESHOLD);
-			/* Avoid deadlock */
-			goto grab_locks;
-		}
-
-		list_remove(&thread->wq_link);
-		thread->saved_context = thread->sleep_timeout_context;
-		do_wakeup = true;
-		if (thread->sleep_composable)
-			wq->ignore_wakeups++;
-		thread->sleep_queue = NULL;
-		irq_spinlock_unlock(&wq->lock, false);
-	}
-
-	thread->timeout_pending = false;
-	irq_spinlock_unlock(&thread->lock, false);
-
-	if (do_wakeup)
-		thread_ready(thread);
-
-out:
-	irq_spinlock_unlock(&threads_lock, false);
-}
-
-/** Interrupt sleeping thread.
- *
- * This routine attempts to interrupt a thread from its sleep in
- * a waitqueue. If the thread is not found sleeping, no action
- * is taken.
- *
- * The threads_lock must be already held and interrupts must be
- * disabled upon calling this function.
- *
- * @param thread Thread to be interrupted.
- *
- */
-void waitq_interrupt_sleep(thread_t *thread)
-{
-	bool do_wakeup = false;
-	DEADLOCK_PROBE_INIT(p_wqlock);
-
-	/*
-	 * The thread is quaranteed to exist because
-	 * threads_lock is held.
-	 */
-
-grab_locks:
-	irq_spinlock_lock(&thread->lock, false);
-
-	waitq_t *wq;
-	if ((wq = thread->sleep_queue)) {  /* Assignment */
-		if (!(thread->sleep_interruptible)) {
-			/*
-			 * The sleep cannot be interrupted.
-			 */
-			irq_spinlock_unlock(&thread->lock, false);
-			return;
-		}
-
-		if (!irq_spinlock_trylock(&wq->lock)) {
-			/* Avoid deadlock */
-			irq_spinlock_unlock(&thread->lock, false);
-			DEADLOCK_PROBE(p_wqlock, DEADLOCK_THRESHOLD);
-			goto grab_locks;
-		}
-
-		if ((thread->timeout_pending) &&
-		    (timeout_unregister(&thread->sleep_timeout)))
-			thread->timeout_pending = false;
-
-		list_remove(&thread->wq_link);
-		thread->saved_context = thread->sleep_interruption_context;
-		if (thread->sleep_composable)
-			wq->ignore_wakeups++;
-		do_wakeup = true;
-		thread->sleep_queue = NULL;
-		irq_spinlock_unlock(&wq->lock, false);
-	}
-
-	irq_spinlock_unlock(&thread->lock, false);
-
-	if (do_wakeup)
-		thread_ready(thread);
+/**
+ * Initialize wait queue with an initial number of queued wakeups
+ * (or a wakeup debt if negative).
+ */
+void waitq_initialize_with_count(waitq_t *wq, int count)
+{
+	waitq_initialize(wq);
+	wq->wakeup_balance = count;
 }
 
@@ -194,13 +88,20 @@
 	(((flags) & SYNCH_FLAGS_NON_BLOCKING) && ((usec) == 0))
 
+errno_t waitq_sleep(waitq_t *wq)
+{
+	return _waitq_sleep_timeout(wq, SYNCH_NO_TIMEOUT, SYNCH_FLAGS_NONE);
+}
+
+errno_t waitq_sleep_timeout(waitq_t *wq, uint32_t usec)
+{
+	return _waitq_sleep_timeout(wq, usec, SYNCH_FLAGS_NON_BLOCKING);
+}
+
 /** Sleep until either wakeup, timeout or interruption occurs
  *
- * This is a sleep implementation which allows itself to time out or to be
- * interrupted from the sleep, restoring a failover context.
- *
  * Sleepers are organised in a FIFO fashion in a structure called wait queue.
  *
- * This function is really basic in that other functions as waitq_sleep()
- * and all the *_timeout() functions use it.
+ * Other functions as waitq_sleep() and all the *_timeout() functions are
+ * implemented using this function.
  *
  * @param wq    Pointer to wait queue.
@@ -208,8 +109,4 @@
  * @param flags Specify mode of the sleep.
  *
- * @param[out] blocked  On return, regardless of the return code,
- *                      `*blocked` is set to `true` iff the thread went to
- *                      sleep.
- *
  * The sleep can be interrupted only if the
  * SYNCH_FLAGS_INTERRUPTIBLE bit is specified in flags.
@@ -225,29 +122,15 @@
  * call will immediately return, reporting either success or failure.
  *
- * @return EAGAIN, meaning that the sleep failed because it was requested
- *                 as SYNCH_FLAGS_NON_BLOCKING, but there was no pending wakeup.
- * @return ETIMEOUT, meaning that the sleep timed out.
- * @return EINTR, meaning that somebody interrupted the sleeping
- *         thread. Check the value of `*blocked` to see if the thread slept,
- *         or if a pending interrupt forced it to return immediately.
+ * @return ETIMEOUT, meaning that the sleep timed out, or a nonblocking call
+ *                   returned unsuccessfully.
+ * @return EINTR, meaning that somebody interrupted the sleeping thread.
  * @return EOK, meaning that none of the above conditions occured, and the
- *              thread was woken up successfuly by `waitq_wakeup()`. Check
- *              the value of `*blocked` to see if the thread slept or if
- *              the wakeup was already pending.
- *
- */
-errno_t waitq_sleep_timeout(waitq_t *wq, uint32_t usec, unsigned int flags, bool *blocked)
+ *              thread was woken up successfuly by `waitq_wake_*()`.
+ *
+ */
+errno_t _waitq_sleep_timeout(waitq_t *wq, uint32_t usec, unsigned int flags)
 {
 	assert((!PREEMPTION_DISABLED) || (PARAM_NON_BLOCKING(flags, usec)));
-
-	ipl_t ipl = waitq_sleep_prepare(wq);
-	bool nblocked;
-	errno_t rc = waitq_sleep_timeout_unsafe(wq, usec, flags, &nblocked);
-	waitq_sleep_finish(wq, nblocked, ipl);
-
-	if (blocked != NULL) {
-		*blocked = nblocked;
-	}
-	return rc;
+	return waitq_sleep_timeout_unsafe(wq, usec, flags, waitq_sleep_prepare(wq));
 }
 
@@ -262,67 +145,16 @@
  *
  */
-ipl_t waitq_sleep_prepare(waitq_t *wq)
-{
-	ipl_t ipl;
-
-restart:
-	ipl = interrupts_disable();
-
-	if (THREAD) {  /* Needed during system initiailzation */
-		/*
-		 * Busy waiting for a delayed timeout.
-		 * This is an important fix for the race condition between
-		 * a delayed timeout and a next call to waitq_sleep_timeout().
-		 * Simply, the thread is not allowed to go to sleep if
-		 * there are timeouts in progress.
-		 *
-		 */
-		irq_spinlock_lock(&THREAD->lock, false);
-
-		if (THREAD->timeout_pending) {
-			irq_spinlock_unlock(&THREAD->lock, false);
-			interrupts_restore(ipl);
-			goto restart;
-		}
-
-		irq_spinlock_unlock(&THREAD->lock, false);
-	}
-
+wait_guard_t waitq_sleep_prepare(waitq_t *wq)
+{
+	ipl_t ipl = interrupts_disable();
 	irq_spinlock_lock(&wq->lock, false);
-	return ipl;
-}
-
-/** Finish waiting in a wait queue.
- *
- * This function restores interrupts to the state that existed prior
- * to the call to waitq_sleep_prepare(). If necessary, the wait queue
- * lock is released.
- *
- * @param wq       Wait queue.
- * @param blocked  Out parameter of waitq_sleep_timeout_unsafe().
- * @param ipl      Interrupt level returned by waitq_sleep_prepare().
- *
- */
-void waitq_sleep_finish(waitq_t *wq, bool blocked, ipl_t ipl)
-{
-	if (blocked) {
-		/*
-		 * Wait for a waitq_wakeup() or waitq_unsleep() to complete
-		 * before returning from waitq_sleep() to the caller. Otherwise
-		 * the caller might expect that the wait queue is no longer used
-		 * and deallocate it (although the wakeup on a another cpu has
-		 * not yet completed and is using the wait queue).
-		 *
-		 * Note that we have to do this for EOK and EINTR, but not
-		 * necessarily for ETIMEOUT where the timeout handler stops
-		 * using the waitq before waking us up. To be on the safe side,
-		 * ensure the waitq is not in use anymore in this case as well.
-		 */
-		waitq_complete_wakeup(wq);
-	} else {
-		irq_spinlock_unlock(&wq->lock, false);
-	}
-
-	interrupts_restore(ipl);
+	return (wait_guard_t) {
+		.ipl = ipl,
+	};
+}
+
+errno_t waitq_sleep_unsafe(waitq_t *wq, wait_guard_t guard)
+{
+	return waitq_sleep_timeout_unsafe(wq, SYNCH_NO_TIMEOUT, SYNCH_FLAGS_NONE, guard);
 }
 
@@ -330,6 +162,5 @@
  *
  * This function implements logic of sleeping in a wait queue.
- * This call must be preceded by a call to waitq_sleep_prepare()
- * and followed by a call to waitq_sleep_finish().
+ * This call must be preceded by a call to waitq_sleep_prepare().
  *
  * @param wq    See waitq_sleep_timeout().
@@ -342,252 +173,177 @@
  *
  */
-errno_t waitq_sleep_timeout_unsafe(waitq_t *wq, uint32_t usec, unsigned int flags, bool *blocked)
-{
-	*blocked = false;
+errno_t waitq_sleep_timeout_unsafe(waitq_t *wq, uint32_t usec, unsigned int flags, wait_guard_t guard)
+{
+	errno_t rc;
+
+	/*
+	 * If true, and this thread's sleep returns without a wakeup
+	 * (timed out or interrupted), waitq ignores the next wakeup.
+	 * This is necessary for futex to be able to handle those conditions.
+	 */
+	bool sleep_composable = (flags & SYNCH_FLAGS_FUTEX);
+	bool interruptible = (flags & SYNCH_FLAGS_INTERRUPTIBLE);
+
+	if (wq->closed) {
+		rc = EOK;
+		goto exit;
+	}
 
 	/* Checks whether to go to sleep at all */
-	if (wq->missed_wakeups) {
-		wq->missed_wakeups--;
-		return EOK;
-	} else {
-		if (PARAM_NON_BLOCKING(flags, usec)) {
-			/* Return immediately instead of going to sleep */
-			return EAGAIN;
+	if (wq->wakeup_balance > 0) {
+		wq->wakeup_balance--;
+
+		rc = EOK;
+		goto exit;
+	}
+
+	if (PARAM_NON_BLOCKING(flags, usec)) {
+		/* Return immediately instead of going to sleep */
+		rc = ETIMEOUT;
+		goto exit;
+	}
+
+	/* Just for debugging output. */
+	atomic_store_explicit(&THREAD->sleep_queue, wq, memory_order_relaxed);
+
+	/*
+	 * This thread_t field is synchronized exclusively via
+	 * waitq lock of the waitq currently listing it.
+	 */
+	list_append(&THREAD->wq_link, &wq->sleepers);
+
+	/* Needs to be run when interrupts are still disabled. */
+	deadline_t deadline = usec > 0 ?
+	    timeout_deadline_in_usec(usec) : DEADLINE_NEVER;
+
+	while (true) {
+		bool terminating = (thread_wait_start() == THREAD_TERMINATING);
+		if (terminating && interruptible) {
+			rc = EINTR;
+			goto exit;
 		}
-	}
-
-	/*
-	 * Now we are firmly decided to go to sleep.
-	 *
-	 */
-	irq_spinlock_lock(&THREAD->lock, false);
-
-	THREAD->sleep_composable = (flags & SYNCH_FLAGS_FUTEX);
-
-	if (flags & SYNCH_FLAGS_INTERRUPTIBLE) {
+
+		irq_spinlock_unlock(&wq->lock, false);
+
+		bool timed_out = (thread_wait_finish(deadline) == THREAD_WAIT_TIMEOUT);
+
 		/*
-		 * If the thread was already interrupted,
-		 * don't go to sleep at all.
+		 * We always need to re-lock the WQ, since concurrently running
+		 * waitq_wakeup() may still not have exitted.
+		 * If we didn't always do this, we'd risk waitq_wakeup() that woke us
+		 * up still running on another CPU even after this function returns,
+		 * and that would be an issue if the waitq is allocated locally to
+		 * wait for a one-off asynchronous event. We'd need more external
+		 * synchronization in that case, and that would be a pain.
+		 *
+		 * On the plus side, always regaining a lock simplifies cleanup.
 		 */
-		if (THREAD->interrupted) {
-			irq_spinlock_unlock(&THREAD->lock, false);
-			return EINTR;
+		irq_spinlock_lock(&wq->lock, false);
+
+		if (!link_in_use(&THREAD->wq_link)) {
+			/*
+			 * We were woken up by the desired event. Return success,
+			 * regardless of any concurrent timeout or interruption.
+			 */
+			rc = EOK;
+			goto exit;
 		}
 
-		/*
-		 * Set context that will be restored if the sleep
-		 * of this thread is ever interrupted.
-		 */
-		THREAD->sleep_interruptible = true;
-		if (!context_save(&THREAD->sleep_interruption_context)) {
-			/* Short emulation of scheduler() return code. */
-			THREAD->last_cycle = get_cycle();
-			irq_spinlock_unlock(&THREAD->lock, false);
-			return EINTR;
+		if (timed_out) {
+			rc = ETIMEOUT;
+			goto exit;
 		}
-	} else
-		THREAD->sleep_interruptible = false;
-
-	if (usec) {
-		/* We use the timeout variant. */
-		if (!context_save(&THREAD->sleep_timeout_context)) {
-			/* Short emulation of scheduler() return code. */
-			THREAD->last_cycle = get_cycle();
-			irq_spinlock_unlock(&THREAD->lock, false);
-			return ETIMEOUT;
-		}
-
-		THREAD->timeout_pending = true;
-		timeout_register(&THREAD->sleep_timeout, (uint64_t) usec,
-		    waitq_sleep_timed_out, THREAD);
-	}
-
-	list_append(&THREAD->wq_link, &wq->sleepers);
-
-	/*
-	 * Suspend execution.
-	 *
-	 */
-	THREAD->state = Sleeping;
-	THREAD->sleep_queue = wq;
-
-	/*
-	 * Must be before entry to scheduler, because there are multiple
-	 * return vectors.
-	 */
-	*blocked = true;
-
-	irq_spinlock_unlock(&THREAD->lock, false);
-
-	/* wq->lock is released in scheduler_separated_stack() */
-	scheduler();
-
-	return EOK;
-}
-
-/** Wake up first thread sleeping in a wait queue
- *
- * Wake up first thread sleeping in a wait queue. This is the SMP- and IRQ-safe
- * wrapper meant for general use.
- *
- * Besides its 'normal' wakeup operation, it attempts to unregister possible
- * timeout.
- *
- * @param wq   Pointer to wait queue.
- * @param mode Wakeup mode.
- *
- */
-void waitq_wakeup(waitq_t *wq, wakeup_mode_t mode)
+
+		/* Interrupted for some other reason. */
+	}
+
+exit:
+	if (THREAD)
+		list_remove(&THREAD->wq_link);
+
+	if (rc != EOK && sleep_composable)
+		wq->wakeup_balance--;
+
+	if (THREAD)
+		atomic_store_explicit(&THREAD->sleep_queue, NULL, memory_order_relaxed);
+
+	irq_spinlock_unlock(&wq->lock, false);
+	interrupts_restore(guard.ipl);
+	return rc;
+}
+
+static void _wake_one(waitq_t *wq)
+{
+	/* Pop one thread from the queue and wake it up. */
+	thread_t *thread = list_get_instance(list_first(&wq->sleepers), thread_t, wq_link);
+	list_remove(&thread->wq_link);
+	thread_wakeup(thread);
+}
+
+/**
+ * Meant for implementing condvar signal.
+ * Always wakes one thread if there are any sleeping,
+ * has no effect if no threads are waiting for wakeup.
+ */
+void waitq_signal(waitq_t *wq)
 {
 	irq_spinlock_lock(&wq->lock, true);
-	_waitq_wakeup_unsafe(wq, mode);
+
+	if (!list_empty(&wq->sleepers))
+		_wake_one(wq);
+
 	irq_spinlock_unlock(&wq->lock, true);
 }
 
-/** If there is a wakeup in progress actively waits for it to complete.
- *
- * The function returns once the concurrently running waitq_wakeup()
- * exits. It returns immediately if there are no concurrent wakeups
- * at the time.
- *
- * Interrupts must be disabled.
- *
- * Example usage:
- * @code
- * void callback(waitq *wq)
- * {
- *     // Do something and notify wait_for_completion() that we're done.
- *     waitq_wakeup(wq);
- * }
- * void wait_for_completion(void)
- * {
- *     waitq wg;
- *     waitq_initialize(&wq);
- *     // Run callback() in the background, pass it wq.
- *     do_asynchronously(callback, &wq);
- *     // Wait for callback() to complete its work.
- *     waitq_sleep(&wq);
- *     // callback() completed its work, but it may still be accessing
- *     // wq in waitq_wakeup(). Therefore it is not yet safe to return
- *     // from waitq_sleep() or it would clobber up our stack (where wq
- *     // is stored). waitq_sleep() ensures the wait queue is no longer
- *     // in use by invoking waitq_complete_wakeup() internally.
- *
- *     // waitq_sleep() returned, it is safe to free wq.
- * }
- * @endcode
- *
- * @param wq  Pointer to a wait queue.
- */
-static void waitq_complete_wakeup(waitq_t *wq)
-{
-	assert(interrupts_disabled());
-
-	irq_spinlock_lock(&wq->lock, false);
-	irq_spinlock_unlock(&wq->lock, false);
-}
-
-/** Internal SMP- and IRQ-unsafe version of waitq_wakeup()
- *
- * This is the internal SMP- and IRQ-unsafe version of waitq_wakeup(). It
- * assumes wq->lock is already locked and interrupts are already disabled.
- *
- * @param wq   Pointer to wait queue.
- * @param mode If mode is WAKEUP_FIRST, then the longest waiting
- *             thread, if any, is woken up. If mode is WAKEUP_ALL, then
- *             all waiting threads, if any, are woken up. If there are
- *             no waiting threads to be woken up, the missed wakeup is
- *             recorded in the wait queue.
- *
- */
-void _waitq_wakeup_unsafe(waitq_t *wq, wakeup_mode_t mode)
-{
-	size_t count = 0;
-
-	assert(interrupts_disabled());
-	assert(irq_spinlock_locked(&wq->lock));
-
-	if (wq->ignore_wakeups > 0) {
-		if (mode == WAKEUP_FIRST) {
-			wq->ignore_wakeups--;
-			return;
-		}
-		wq->ignore_wakeups = 0;
-	}
-
-loop:
-	if (list_empty(&wq->sleepers)) {
-		wq->missed_wakeups++;
-		if ((count) && (mode == WAKEUP_ALL))
-			wq->missed_wakeups--;
-
-		return;
-	}
-
-	count++;
-	thread_t *thread = list_get_instance(list_first(&wq->sleepers),
-	    thread_t, wq_link);
-
-	/*
-	 * Lock the thread prior to removing it from the wq.
-	 * This is not necessary because of mutual exclusion
-	 * (the link belongs to the wait queue), but because
-	 * of synchronization with waitq_sleep_timed_out()
-	 * and thread_interrupt_sleep().
-	 *
-	 * In order for these two functions to work, the following
-	 * invariant must hold:
-	 *
-	 * thread->sleep_queue != NULL <=> thread sleeps in a wait queue
-	 *
-	 * For an observer who locks the thread, the invariant
-	 * holds only when the lock is held prior to removing
-	 * it from the wait queue.
-	 *
-	 */
-	irq_spinlock_lock(&thread->lock, false);
-	list_remove(&thread->wq_link);
-
-	if ((thread->timeout_pending) &&
-	    (timeout_unregister(&thread->sleep_timeout)))
-		thread->timeout_pending = false;
-
-	thread->sleep_queue = NULL;
-	irq_spinlock_unlock(&thread->lock, false);
-
-	thread_ready(thread);
-
-	if (mode == WAKEUP_ALL)
-		goto loop;
-}
-
-/** Get the missed wakeups count.
- *
- * @param wq	Pointer to wait queue.
- * @return	The wait queue's missed_wakeups count.
- */
-int waitq_count_get(waitq_t *wq)
-{
-	int cnt;
-
+/**
+ * Wakes up one thread sleeping on this waitq.
+ * If there are no threads waiting, saves the wakeup so that the next sleep
+ * returns immediately. If a previous failure in sleep created a wakeup debt
+ * (see SYNCH_FLAGS_FUTEX) this debt is annulled and no thread is woken up.
+ */
+void waitq_wake_one(waitq_t *wq)
+{
 	irq_spinlock_lock(&wq->lock, true);
-	cnt = wq->missed_wakeups;
+
+	if (!wq->closed) {
+		if (wq->wakeup_balance < 0 || list_empty(&wq->sleepers))
+			wq->wakeup_balance++;
+		else
+			_wake_one(wq);
+	}
+
 	irq_spinlock_unlock(&wq->lock, true);
-
-	return cnt;
-}
-
-/** Set the missed wakeups count.
- *
- * @param wq	Pointer to wait queue.
- * @param val	New value of the missed_wakeups count.
- */
-void waitq_count_set(waitq_t *wq, int val)
+}
+
+static void _wake_all(waitq_t *wq)
+{
+	while (!list_empty(&wq->sleepers))
+		_wake_one(wq);
+}
+
+/**
+ * Wakes up all threads currently waiting on this waitq
+ * and makes all future sleeps return instantly.
+ */
+void waitq_close(waitq_t *wq)
 {
 	irq_spinlock_lock(&wq->lock, true);
-	wq->missed_wakeups = val;
+	wq->wakeup_balance = 0;
+	wq->closed = true;
+	_wake_all(wq);
 	irq_spinlock_unlock(&wq->lock, true);
 }
 
+/**
+ * Wakes up all threads currently waiting on this waitq
+ */
+void waitq_wake_all(waitq_t *wq)
+{
+	irq_spinlock_lock(&wq->lock, true);
+	wq->wakeup_balance = 0;
+	_wake_all(wq);
+	irq_spinlock_unlock(&wq->lock, true);
+}
+
 /** @}
  */
Index: kernel/generic/src/syscall/syscall.c
===================================================================
--- kernel/generic/src/syscall/syscall.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/syscall/syscall.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -141,7 +141,7 @@
 {
 	/* Do userpace accounting */
-	irq_spinlock_lock(&THREAD->lock, true);
+	ipl_t ipl = interrupts_disable();
 	thread_update_accounting(true);
-	irq_spinlock_unlock(&THREAD->lock, true);
+	interrupts_restore(ipl);
 
 #ifdef CONFIG_UDEBUG
@@ -191,7 +191,7 @@
 
 	/* Do kernel accounting */
-	irq_spinlock_lock(&THREAD->lock, true);
+	ipl = interrupts_disable();
 	thread_update_accounting(false);
-	irq_spinlock_unlock(&THREAD->lock, true);
+	interrupts_restore(ipl);
 
 	return rc;
Index: kernel/generic/src/sysinfo/stats.c
===================================================================
--- kernel/generic/src/sysinfo/stats.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/sysinfo/stats.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -119,13 +119,10 @@
 	size_t i;
 	for (i = 0; i < config.cpu_count; i++) {
-		irq_spinlock_lock(&cpus[i].lock, true);
-
 		stats_cpus[i].id = cpus[i].id;
 		stats_cpus[i].active = cpus[i].active;
 		stats_cpus[i].frequency_mhz = cpus[i].frequency_mhz;
-		stats_cpus[i].busy_cycles = cpus[i].busy_cycles;
-		stats_cpus[i].idle_cycles = cpus[i].idle_cycles;
-
-		irq_spinlock_unlock(&cpus[i].lock, true);
+
+		stats_cpus[i].busy_cycles = atomic_time_read(&cpus[i].busy_cycles);
+		stats_cpus[i].idle_cycles = atomic_time_read(&cpus[i].idle_cycles);
 	}
 
@@ -224,5 +221,5 @@
 	stats_task->virtmem = get_task_virtmem(task->as);
 	stats_task->resmem = get_task_resmem(task->as);
-	stats_task->threads = atomic_load(&task->refcount);
+	stats_task->threads = atomic_load(&task->lifecount);
 	task_get_accounting(task, &(stats_task->ucycles),
 	    &(stats_task->kcycles));
@@ -302,16 +299,17 @@
 {
 	assert(interrupts_disabled());
-	assert(irq_spinlock_locked(&thread->lock));
 
 	stats_thread->thread_id = thread->tid;
 	stats_thread->task_id = thread->task->taskid;
-	stats_thread->state = thread->state;
-	stats_thread->priority = thread->priority;
-	stats_thread->ucycles = thread->ucycles;
-	stats_thread->kcycles = thread->kcycles;
-
-	if (thread->cpu != NULL) {
+	stats_thread->state = atomic_get_unordered(&thread->state);
+	stats_thread->priority = atomic_get_unordered(&thread->priority);
+	stats_thread->ucycles = atomic_time_read(&thread->ucycles);
+	stats_thread->kcycles = atomic_time_read(&thread->kcycles);
+
+	cpu_t *cpu = atomic_get_unordered(&thread->cpu);
+
+	if (cpu != NULL) {
 		stats_thread->on_cpu = true;
-		stats_thread->cpu = thread->cpu->id;
+		stats_thread->cpu = cpu->id;
 	} else
 		stats_thread->on_cpu = false;
@@ -332,5 +330,5 @@
     bool dry_run, void *data)
 {
-	/* Messing with threads structures, avoid deadlock */
+	/* Messing with threads structures */
 	irq_spinlock_lock(&threads_lock, true);
 
@@ -364,12 +362,7 @@
 	thread_t *thread = thread_first();
 	while (thread != NULL) {
-		/* Interrupts are already disabled */
-		irq_spinlock_lock(&thread->lock, false);
-
 		/* Record the statistics and increment the index */
 		produce_stats_thread(thread, &stats_threads[i]);
 		i++;
-
-		irq_spinlock_unlock(&thread->lock, false);
 
 		thread = thread_next(thread);
@@ -518,6 +511,7 @@
 {
 	/* Initially no return value */
-	sysinfo_return_t ret;
-	ret.tag = SYSINFO_VAL_UNDEFINED;
+	sysinfo_return_t ret = {
+		.tag = SYSINFO_VAL_UNDEFINED,
+	};
 
 	/* Parse the task ID */
@@ -526,13 +520,7 @@
 		return ret;
 
-	/* Messing with task structures, avoid deadlock */
-	irq_spinlock_lock(&tasks_lock, true);
-
 	task_t *task = task_find_by_id(task_id);
-	if (task == NULL) {
-		/* No task with this ID */
-		irq_spinlock_unlock(&tasks_lock, true);
+	if (!task)
 		return ret;
-	}
 
 	if (dry_run) {
@@ -540,28 +528,21 @@
 		ret.data.data = NULL;
 		ret.data.size = sizeof(stats_task_t);
-
-		irq_spinlock_unlock(&tasks_lock, true);
 	} else {
 		/* Allocate stats_task_t structure */
-		stats_task_t *stats_task =
-		    (stats_task_t *) malloc(sizeof(stats_task_t));
-		if (stats_task == NULL) {
-			irq_spinlock_unlock(&tasks_lock, true);
-			return ret;
+		stats_task_t *stats_task = malloc(sizeof(stats_task_t));
+
+		if (stats_task != NULL) {
+			/* Correct return value */
+			ret.tag = SYSINFO_VAL_FUNCTION_DATA;
+			ret.data.data = stats_task;
+			ret.data.size = sizeof(stats_task_t);
+
+			irq_spinlock_lock(&task->lock, true);
+			produce_stats_task(task, stats_task);
+			irq_spinlock_unlock(&task->lock, true);
 		}
-
-		/* Correct return value */
-		ret.tag = SYSINFO_VAL_FUNCTION_DATA;
-		ret.data.data = (void *) stats_task;
-		ret.data.size = sizeof(stats_task_t);
-
-		/* Hand-over-hand locking */
-		irq_spinlock_exchange(&tasks_lock, &task->lock);
-
-		produce_stats_task(task, stats_task);
-
-		irq_spinlock_unlock(&task->lock, true);
-	}
-
+	}
+
+	task_release(task);
 	return ret;
 }
@@ -597,5 +578,5 @@
 		return ret;
 
-	/* Messing with threads structures, avoid deadlock */
+	/* Messing with threads structures */
 	irq_spinlock_lock(&threads_lock, true);
 
@@ -627,10 +608,7 @@
 		ret.data.size = sizeof(stats_thread_t);
 
-		/* Hand-over-hand locking */
-		irq_spinlock_exchange(&threads_lock, &thread->lock);
-
 		produce_stats_thread(thread, stats_thread);
 
-		irq_spinlock_unlock(&thread->lock, true);
+		irq_spinlock_unlock(&threads_lock, true);
 	}
 
@@ -847,6 +825,4 @@
 void kload(void *arg)
 {
-	thread_detach(THREAD);
-
 	while (true) {
 		size_t ready = atomic_load(&nrdy);
Index: kernel/generic/src/time/clock.c
===================================================================
--- kernel/generic/src/time/clock.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/time/clock.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,5 @@
 /*
  * Copyright (c) 2001-2004 Jakub Jermar
+ * Copyright (c) 2022 Jiří Zárevúcky
  * All rights reserved.
  *
@@ -58,4 +59,5 @@
 #include <ddi/ddi.h>
 #include <arch/cycle.h>
+#include <preemption.h>
 
 /* Pointer to variable with uptime */
@@ -64,11 +66,4 @@
 /** Physical memory area of the real time clock */
 static parea_t clock_parea;
-
-/** Fragment of second
- *
- * For updating  seconds correctly.
- *
- */
-static sysarg_t secfrag = 0;
 
 /** Initialize realtime clock counter
@@ -109,20 +104,18 @@
  *
  * Update it only on first processor
- * TODO: Do we really need so many write barriers?
- *
- */
-static void clock_update_counters(void)
+ */
+static void clock_update_counters(uint64_t current_tick)
 {
 	if (CPU->id == 0) {
-		secfrag += 1000000 / HZ;
-		if (secfrag >= 1000000) {
-			secfrag -= 1000000;
-			uptime->seconds1++;
-			write_barrier();
-			uptime->useconds = secfrag;
-			write_barrier();
-			uptime->seconds2 = uptime->seconds1;
-		} else
-			uptime->useconds += 1000000 / HZ;
+		uint64_t usec = (1000000 / HZ) * current_tick;
+
+		sysarg_t secs = usec / 1000000;
+		sysarg_t usecs = usec % 1000000;
+
+		uptime->seconds1 = secs;
+		write_barrier();
+		uptime->useconds = usecs;
+		write_barrier();
+		uptime->seconds2 = secs;
 	}
 }
@@ -130,9 +123,8 @@
 static void cpu_update_accounting(void)
 {
-	irq_spinlock_lock(&CPU->lock, false);
+	// FIXME: get_cycle() is unimplemented on several platforms
 	uint64_t now = get_cycle();
-	CPU->busy_cycles += now - CPU->last_cycle;
-	CPU->last_cycle = now;
-	irq_spinlock_unlock(&CPU->lock, false);
+	atomic_time_increment(&CPU->busy_cycles, now - CPU_LOCAL->last_cycle);
+	CPU_LOCAL->last_cycle = now;
 }
 
@@ -146,5 +138,10 @@
 void clock(void)
 {
-	size_t missed_clock_ticks = CPU->missed_clock_ticks;
+	size_t missed_clock_ticks = CPU_LOCAL->missed_clock_ticks;
+	CPU_LOCAL->missed_clock_ticks = 0;
+
+	CPU_LOCAL->current_clock_tick += missed_clock_ticks + 1;
+	uint64_t current_clock_tick = CPU_LOCAL->current_clock_tick;
+	clock_update_counters(current_clock_tick);
 
 	/* Account CPU usage */
@@ -156,39 +153,31 @@
 	 *
 	 */
-	size_t i;
-	for (i = 0; i <= missed_clock_ticks; i++) {
-		/* Update counters and accounting */
-		clock_update_counters();
-		cpu_update_accounting();
+
+	irq_spinlock_lock(&CPU->timeoutlock, false);
+
+	link_t *cur;
+	while ((cur = list_first(&CPU->timeout_active_list)) != NULL) {
+		timeout_t *timeout = list_get_instance(cur, timeout_t, link);
+
+		if (current_clock_tick <= timeout->deadline) {
+			break;
+		}
+
+		list_remove(cur);
+		timeout_handler_t handler = timeout->handler;
+		void *arg = timeout->arg;
+		atomic_bool *finished = &timeout->finished;
+
+		irq_spinlock_unlock(&CPU->timeoutlock, false);
+
+		handler(arg);
+
+		/* Signal that the handler is finished. */
+		atomic_store_explicit(finished, true, memory_order_release);
 
 		irq_spinlock_lock(&CPU->timeoutlock, false);
-
-		link_t *cur;
-		while ((cur = list_first(&CPU->timeout_active_list)) != NULL) {
-			timeout_t *timeout = list_get_instance(cur, timeout_t,
-			    link);
-
-			irq_spinlock_lock(&timeout->lock, false);
-			if (timeout->ticks-- != 0) {
-				irq_spinlock_unlock(&timeout->lock, false);
-				break;
-			}
-
-			list_remove(cur);
-			timeout_handler_t handler = timeout->handler;
-			void *arg = timeout->arg;
-			timeout_reinitialize(timeout);
-
-			irq_spinlock_unlock(&timeout->lock, false);
-			irq_spinlock_unlock(&CPU->timeoutlock, false);
-
-			handler(arg);
-
-			irq_spinlock_lock(&CPU->timeoutlock, false);
-		}
-
-		irq_spinlock_unlock(&CPU->timeoutlock, false);
 	}
-	CPU->missed_clock_ticks = 0;
+
+	irq_spinlock_unlock(&CPU->timeoutlock, false);
 
 	/*
@@ -198,21 +187,6 @@
 
 	if (THREAD) {
-		uint64_t ticks;
-
-		irq_spinlock_lock(&CPU->lock, false);
-		CPU->needs_relink += 1 + missed_clock_ticks;
-		irq_spinlock_unlock(&CPU->lock, false);
-
-		irq_spinlock_lock(&THREAD->lock, false);
-		if ((ticks = THREAD->ticks)) {
-			if (ticks >= 1 + missed_clock_ticks)
-				THREAD->ticks -= 1 + missed_clock_ticks;
-			else
-				THREAD->ticks = 0;
-		}
-		irq_spinlock_unlock(&THREAD->lock, false);
-
-		if (ticks == 0 && PREEMPTION_ENABLED) {
-			scheduler();
+		if (current_clock_tick >= CPU_LOCAL->preempt_deadline && PREEMPTION_ENABLED) {
+			thread_yield();
 #ifdef CONFIG_UDEBUG
 			/*
Index: kernel/generic/src/time/timeout.c
===================================================================
--- kernel/generic/src/time/timeout.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/time/timeout.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,5 @@
 /*
  * Copyright (c) 2001-2004 Jakub Jermar
+ * Copyright (c) 2022 Jiří Zárevúcky
  * All rights reserved.
  *
@@ -57,20 +58,4 @@
 }
 
-/** Reinitialize timeout
- *
- * Initialize all members except the lock.
- *
- * @param timeout Timeout to be initialized.
- *
- */
-void timeout_reinitialize(timeout_t *timeout)
-{
-	timeout->cpu = NULL;
-	timeout->ticks = 0;
-	timeout->handler = NULL;
-	timeout->arg = NULL;
-	link_initialize(&timeout->link);
-}
-
 /** Initialize timeout
  *
@@ -82,6 +67,45 @@
 void timeout_initialize(timeout_t *timeout)
 {
-	irq_spinlock_initialize(&timeout->lock, "timeout_t_lock");
-	timeout_reinitialize(timeout);
+	link_initialize(&timeout->link);
+	timeout->cpu = NULL;
+}
+
+/* Only call when interrupts are disabled. */
+deadline_t timeout_deadline_in_usec(uint32_t usec)
+{
+	if (usec == 0)
+		return 0;
+
+	return CPU_LOCAL->current_clock_tick + us2ticks(usec);
+}
+
+static void timeout_register_deadline_locked(timeout_t *timeout, deadline_t deadline,
+    timeout_handler_t handler, void *arg)
+{
+	assert(!link_in_use(&timeout->link));
+
+	*timeout = (timeout_t) {
+		.cpu = CPU,
+		.deadline = deadline,
+		.handler = handler,
+		.arg = arg,
+		.finished = ATOMIC_VAR_INIT(false),
+	};
+
+	/* Insert timeout into the active timeouts list according to timeout->deadline. */
+
+	link_t *last = list_last(&CPU->timeout_active_list);
+	if (last == NULL || timeout->deadline >= list_get_instance(last, timeout_t, link)->deadline) {
+		list_append(&timeout->link, &CPU->timeout_active_list);
+	} else {
+		for (link_t *cur = list_first(&CPU->timeout_active_list); cur != NULL;
+		    cur = list_next(cur, &CPU->timeout_active_list)) {
+
+			if (timeout->deadline < list_get_instance(cur, timeout_t, link)->deadline) {
+				list_insert_before(&timeout->link, cur);
+				break;
+			}
+		}
+	}
 }
 
@@ -102,58 +126,13 @@
 {
 	irq_spinlock_lock(&CPU->timeoutlock, true);
-	irq_spinlock_lock(&timeout->lock, false);
+	timeout_register_deadline_locked(timeout, timeout_deadline_in_usec(time), handler, arg);
+	irq_spinlock_unlock(&CPU->timeoutlock, true);
+}
 
-	if (timeout->cpu)
-		panic("Unexpected: timeout->cpu != 0.");
-
-	timeout->cpu = CPU;
-	timeout->ticks = us2ticks(time);
-
-	timeout->handler = handler;
-	timeout->arg = arg;
-
-	/*
-	 * Insert timeout into the active timeouts list according to timeout->ticks.
-	 */
-	uint64_t sum = 0;
-	timeout_t *target = NULL;
-	link_t *cur, *prev;
-	prev = NULL;
-	for (cur = list_first(&CPU->timeout_active_list);
-	    cur != NULL; cur = list_next(cur, &CPU->timeout_active_list)) {
-		target = list_get_instance(cur, timeout_t, link);
-		irq_spinlock_lock(&target->lock, false);
-
-		if (timeout->ticks < sum + target->ticks) {
-			irq_spinlock_unlock(&target->lock, false);
-			break;
-		}
-
-		sum += target->ticks;
-		irq_spinlock_unlock(&target->lock, false);
-		prev = cur;
-	}
-
-	if (prev == NULL)
-		list_prepend(&timeout->link, &CPU->timeout_active_list);
-	else
-		list_insert_after(&timeout->link, prev);
-
-	/*
-	 * Adjust timeout->ticks according to ticks
-	 * accumulated in target's predecessors.
-	 */
-	timeout->ticks -= sum;
-
-	/*
-	 * Decrease ticks of timeout's immediate succesor by timeout->ticks.
-	 */
-	if (cur != NULL) {
-		irq_spinlock_lock(&target->lock, false);
-		target->ticks -= timeout->ticks;
-		irq_spinlock_unlock(&target->lock, false);
-	}
-
-	irq_spinlock_unlock(&timeout->lock, false);
+void timeout_register_deadline(timeout_t *timeout, deadline_t deadline,
+    timeout_handler_t handler, void *arg)
+{
+	irq_spinlock_lock(&CPU->timeoutlock, true);
+	timeout_register_deadline_locked(timeout, deadline, handler, arg);
 	irq_spinlock_unlock(&CPU->timeoutlock, true);
 }
@@ -170,40 +149,26 @@
 bool timeout_unregister(timeout_t *timeout)
 {
-	DEADLOCK_PROBE_INIT(p_tolock);
+	if (atomic_load_explicit(&timeout->finished, memory_order_acquire))
+		/* The timeout fired and finished already, no need to check the list. */
+		return false;
 
-grab_locks:
-	irq_spinlock_lock(&timeout->lock, true);
-	if (!timeout->cpu) {
-		irq_spinlock_unlock(&timeout->lock, true);
-		return false;
+	assert(timeout->cpu);
+
+	irq_spinlock_lock(&timeout->cpu->timeoutlock, true);
+
+	bool success = link_in_use(&timeout->link);
+	if (success) {
+		list_remove(&timeout->link);
 	}
 
-	if (!irq_spinlock_trylock(&timeout->cpu->timeoutlock)) {
-		irq_spinlock_unlock(&timeout->lock, true);
-		DEADLOCK_PROBE(p_tolock, DEADLOCK_THRESHOLD);
-		goto grab_locks;
+	irq_spinlock_unlock(&timeout->cpu->timeoutlock, true);
+
+	if (!success) {
+		/* Timeout was fired, we need to wait for the callback to finish. */
+		while (!atomic_load_explicit(&timeout->finished, memory_order_acquire))
+			cpu_spin_hint();
 	}
 
-	/*
-	 * Now we know for sure that timeout hasn't been activated yet
-	 * and is lurking in timeout->cpu->timeout_active_list.
-	 */
-
-	link_t *cur = list_next(&timeout->link,
-	    &timeout->cpu->timeout_active_list);
-	if (cur != NULL) {
-		timeout_t *tmp = list_get_instance(cur, timeout_t, link);
-		irq_spinlock_lock(&tmp->lock, false);
-		tmp->ticks += timeout->ticks;
-		irq_spinlock_unlock(&tmp->lock, false);
-	}
-
-	list_remove(&timeout->link);
-	irq_spinlock_unlock(&timeout->cpu->timeoutlock, false);
-
-	timeout_reinitialize(timeout);
-	irq_spinlock_unlock(&timeout->lock, true);
-
-	return true;
+	return success;
 }
 
Index: kernel/generic/src/udebug/udebug.c
===================================================================
--- kernel/generic/src/udebug/udebug.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/udebug/udebug.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -94,10 +94,5 @@
 static void udebug_wait_for_go(waitq_t *wq)
 {
-	ipl_t ipl = waitq_sleep_prepare(wq);
-
-	wq->missed_wakeups = 0;  /* Enforce blocking. */
-	bool blocked;
-	(void) waitq_sleep_timeout_unsafe(wq, SYNCH_NO_TIMEOUT, SYNCH_FLAGS_NONE, &blocked);
-	waitq_sleep_finish(wq, blocked, ipl);
+	waitq_sleep(wq);
 }
 
@@ -443,8 +438,8 @@
 				/*
 				 * thread's lock must not be held when calling
-				 * waitq_wakeup.
+				 * waitq_close.
 				 *
 				 */
-				waitq_wakeup(&thread->udebug.go_wq, WAKEUP_FIRST);
+				waitq_close(&thread->udebug.go_wq);
 			}
 
Index: kernel/generic/src/udebug/udebug_ops.c
===================================================================
--- kernel/generic/src/udebug/udebug_ops.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/generic/src/udebug/udebug_ops.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -51,5 +51,5 @@
 #include <udebug/udebug.h>
 #include <udebug/udebug_ops.h>
-#include <mem.h>
+#include <memw.h>
 #include <stdlib.h>
 
@@ -83,45 +83,13 @@
 	mutex_lock(&TASK->udebug.lock);
 
-	/* thread_exists() must be called with threads_lock held */
-	irq_spinlock_lock(&threads_lock, true);
-
-	if (!thread_exists(thread)) {
-		irq_spinlock_unlock(&threads_lock, true);
+	thread = thread_try_get(thread);
+
+	if (!thread) {
 		mutex_unlock(&TASK->udebug.lock);
 		return ENOENT;
 	}
-
-	/* thread->lock is enough to ensure the thread's existence */
-	irq_spinlock_exchange(&threads_lock, &thread->lock);
 
 	/* Verify that 'thread' is a userspace thread. */
 	if (!thread->uspace) {
-		/* It's not, deny its existence */
-		irq_spinlock_unlock(&thread->lock, true);
-		mutex_unlock(&TASK->udebug.lock);
-		return ENOENT;
-	}
-
-	/* Verify debugging state. */
-	if (thread->udebug.active != true) {
-		/* Not in debugging session or undesired GO state */
-		irq_spinlock_unlock(&thread->lock, true);
-		mutex_unlock(&TASK->udebug.lock);
-		return ENOENT;
-	}
-
-	/*
-	 * Since the thread has active == true, TASK->udebug.lock
-	 * is enough to ensure its existence and that active remains
-	 * true.
-	 *
-	 */
-	irq_spinlock_unlock(&thread->lock, true);
-
-	/* Only mutex TASK->udebug.lock left. */
-
-	/* Now verify that the thread belongs to the current task. */
-	if (thread->task != TASK) {
-		/* No such thread belonging this task */
 		mutex_unlock(&TASK->udebug.lock);
 		return ENOENT;
@@ -135,4 +103,20 @@
 	mutex_lock(&thread->udebug.lock);
 
+	/* Verify debugging state. */
+	if (thread->udebug.active != true) {
+		/* Not in debugging session or undesired GO state */
+		mutex_unlock(&thread->udebug.lock);
+		mutex_unlock(&TASK->udebug.lock);
+		return ENOENT;
+	}
+
+	/* Now verify that the thread belongs to the current task. */
+	if (thread->task != TASK) {
+		/* No such thread belonging this task */
+		mutex_unlock(&thread->udebug.lock);
+		mutex_unlock(&TASK->udebug.lock);
+		return ENOENT;
+	}
+
 	/* The big task mutex is no longer needed. */
 	mutex_unlock(&TASK->udebug.lock);
@@ -153,4 +137,7 @@
 {
 	mutex_unlock(&thread->udebug.lock);
+
+	/* Drop reference from _thread_op_begin() */
+	thread_put(thread);
 }
 
@@ -281,5 +268,5 @@
 	 *
 	 */
-	waitq_wakeup(&thread->udebug.go_wq, WAKEUP_FIRST);
+	waitq_wake_all(&thread->udebug.go_wq);
 
 	_thread_op_end(thread);
@@ -393,7 +380,5 @@
 	/* FIXME: make sure the thread isn't past debug shutdown... */
 	list_foreach(TASK->threads, th_link, thread_t, thread) {
-		irq_spinlock_lock(&thread->lock, false);
 		bool uspace = thread->uspace;
-		irq_spinlock_unlock(&thread->lock, false);
 
 		/* Not interested in kernel threads. */
Index: kernel/meson.build
===================================================================
--- kernel/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -42,8 +42,4 @@
 subdir('test')
 
-## Cross-platform assembly to start a symtab.data section
-#
-symtab_section = '.section symtab.data, "a", ' + atsign + 'progbits;'
-
 kernel_include_dirs = include_directories(
 	'generic/include',
@@ -52,4 +48,5 @@
 	'..' / 'abi' / 'arch' / KARCH / 'include',
 	'..' / 'abi' / 'include',
+	'..' / 'common' / 'include',
 	'test',
 )
@@ -89,4 +86,7 @@
 	'-T', meson.current_build_dir() / '_link.ld',
 ]
+# The kernel is built as ELF but then copied as a blob of bytes and
+# the permissions are not relevant anyway (needed for binutils 2.39+).
+kernel_link_args += ldflags_ignore_rwx_segments
 
 if CONFIG_LTO
@@ -94,14 +94,29 @@
 endif
 
-if CONFIG_STRIP_BINARIES
-	# TODO: do this after disassembling
-	kernel_link_args += [ '-s' ]
-endif
-
 kernel_c_args = arch_kernel_c_args + kernel_defs + [
 	'-ffreestanding',
-	# TODO: remove this flag
-	cc.get_supported_arguments([ '-Wno-cast-function-type' ]),
+	'-fdebug-prefix-map=../../kernel/=',
+	'-fdebug-prefix-map=../../../kernel/=',
+
+	cc.get_supported_arguments([
+		# TODO: remove this flag
+		'-Wno-cast-function-type',
+
+		# When accessing specific memory addresses that are below
+		# normal page size, the compiler may assume that we actually
+		# dereferenced NULL pointer and warns us about that.
+		# But in kernel we often need to access these addresses
+		# directly hence we need to ignore these warnings.
+		#
+		# TODO: might make more sense to disable this selectively
+		# in specific files (or better yet, for specific lines).
+		'--param=min-pagesize=0',
+	]),
 ]
+
+if not CONFIG_LINE_DEBUG
+	# Keep the debug info needed to get file names for kernel stack traces.
+	kernel_c_args += [ '-gdwarf-5', '-g1', '-gno-statement-frontiers' ]
+endif
 
 if CONFIG_LTO
@@ -133,78 +148,51 @@
 all_kernel_objects = [ instrumentables, noninstrumentables ]
 
-# We iterate the build several times to get symbol table right.
-# Three times is sufficient to get correct even symbols after symtab.
+kernel_name = 'kernel.elf'
+kernel_map_name = kernel_name + '.map'
+kernel_map_path = meson.current_build_dir() / kernel_map_name
+
+kernel_elf = executable(kernel_name,
+	include_directories: kernel_include_dirs,
+	implicit_include_directories: false,
+	c_args: kernel_c_args,
+	link_args: kernel_c_args + kernel_link_args + [
+		'-Wl,-Map,' + kernel_map_path,
+	],
+	link_depends: kernel_ldscript,
+	link_whole: all_kernel_objects,
+	pie: false,
+)
+
+kernel_dbg = custom_target('kernel.dbg',
+	output: 'kernel.dbg',
+	input: kernel_elf,
+	command: [
+		objcopy,
+		'--only-keep-debug',
+		'@INPUT@',
+		'@OUTPUT@',
+	],
+)
+
+kernel_elf_stripped = custom_target(kernel_name + '.stripped',
+	output: kernel_name + '.stripped',
+	input: kernel_elf,
+	command: [
+		objcopy,
+		'--strip-unneeded',
+		'@INPUT@',
+		'@OUTPUT@',
+	],
+)
+
+rd_init_binaries += [[ kernel_elf_stripped, 'boot/kernel.elf' ]]
+install_files += [[ 'boot', kernel_elf_stripped.full_path(), 'kernel.elf' ]]
+install_deps += [ kernel_elf_stripped ]
 
 if CONFIG_SYMTAB
-	# Iterate build three times.
-	iterations = [ 1, 2, 3 ]
-
-	# Generates symbol table information as an object file.
-	genmap = find_program('tools/genmap.py')
-
-	# Symbol table dump needed for genmap.
-	kernel_syms = custom_target('kernel_syms.txt',
-		input: all_kernel_objects,
-		output: 'kernel_syms.txt',
-		command: [ objdump, '-t', '@INPUT@' ],
-		capture: true,
-	)
-else
-	# Build just once.
-	iterations = [ 1 ]
-endif
-
-# Empty symbol map for first iteration.
-kernel_map_S = custom_target('empty_map.S',
-	output: 'empty_map.S',
-	capture: true,
-	command: [ 'echo', kernel_as_prolog + symtab_section ],
-)
-
-foreach iter : iterations
-	is_last = (iter == iterations.length())
-	kernel_name = 'kernel.@0@.elf'.format(iter)
-	kernel_map_name = kernel_name + '.map'
-	kernel_map_path = meson.current_build_dir() / kernel_map_name
-
-	kernel_elf = executable(kernel_name, kernel_map_S,
-		include_directories: kernel_include_dirs,
-		implicit_include_directories: false,
-		c_args: kernel_c_args,
-		link_args: kernel_c_args + kernel_link_args + [
-			'-Wl,-Map,' + kernel_map_path,
-		],
-		link_depends: kernel_ldscript,
-		link_whole: all_kernel_objects,
-		pie: false,
-	)
-
-	# Generate symbol table if this is not the final iteration.
-	if not is_last
-
-		# TODO: Teach kernel to read its own ELF symbol table and get rid of this nonsense.
-		# Need to first make sure all architectures (even future ones with dumb bootloaders) can use ELF formatted kernel.
-
-		kernel_map_bin = custom_target(kernel_map_name + '.bin',
-			output: kernel_map_name + '.bin',
-			input: [ kernel_elf, kernel_syms ],
-			command: [ genmap, kernel_map_path, '@INPUT1@', '@OUTPUT@' ],
-		)
-
-		kernel_map_S_name = kernel_name + '.map.S'
-
-		kernel_map_S = custom_target(kernel_map_S_name,
-			input: kernel_map_bin,
-			output: kernel_map_S_name,
-			capture: true,
-			command: [ 'echo', kernel_as_prolog + symtab_section + ' .incbin "@INPUT@"' ],
-		)
-	endif
-endforeach
-
-rd_init_binaries += [[ kernel_elf, 'boot/kernel.elf' ]]
-
-install_files += [[ 'boot', kernel_elf.full_path(), 'kernel.elf' ]]
-install_deps += [ kernel_elf ]
+	rd_init_binaries += [[ kernel_dbg, 'kernel.dbg' ]]
+	install_files += [[ 'boot', kernel_dbg.full_path(), 'kernel.dbg' ]]
+	install_deps += [ kernel_dbg ]
+endif
 
 kernel_disasm = custom_target('kernel.elf.disasm',
Index: kernel/test/fault/fault1.c
===================================================================
--- kernel/test/fault/fault1.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/test/fault/fault1.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -30,4 +30,6 @@
 #include <test.h>
 
+#pragma GCC diagnostic ignored "-Warray-bounds"
+
 const char *test_fault1(void)
 {
Index: kernel/test/mm/falloc2.c
===================================================================
--- kernel/test/mm/falloc2.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/test/mm/falloc2.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -35,5 +35,5 @@
 #include <atomic.h>
 #include <proc/thread.h>
-#include <mem.h>
+#include <memw.h>
 #include <arch.h>
 
@@ -43,5 +43,4 @@
 #define THREADS      8
 
-static atomic_size_t thread_cnt;
 static atomic_size_t thread_fail;
 
@@ -56,9 +55,6 @@
 		    "Unable to allocate frames\n", THREAD->tid, CPU->id);
 		atomic_inc(&thread_fail);
-		atomic_dec(&thread_cnt);
 		return;
 	}
-
-	thread_detach(THREAD);
 
 	for (unsigned int run = 0; run < THREAD_RUNS; run++) {
@@ -110,11 +106,11 @@
 	TPRINTF("Thread #%" PRIu64 " (cpu%u): Exiting\n",
 	    THREAD->tid, CPU->id);
-	atomic_dec(&thread_cnt);
 }
 
 const char *test_falloc2(void)
 {
-	atomic_store(&thread_cnt, THREADS);
 	atomic_store(&thread_fail, 0);
+
+	thread_t *threads[THREADS] = { };
 
 	for (unsigned int i = 0; i < THREADS; i++) {
@@ -125,11 +121,13 @@
 			break;
 		}
-		thread_ready(thrd);
+		thread_start(thrd);
+		threads[i] = thrd;
 	}
 
-	while (atomic_load(&thread_cnt) > 0) {
-		TPRINTF("Threads left: %zu\n",
-		    atomic_load(&thread_cnt));
-		thread_sleep(1);
+	for (unsigned int i = 0; i < THREADS; i++) {
+		if (threads[i] != NULL)
+			thread_join(threads[i]);
+
+		TPRINTF("Threads left: %u\n", THREADS - i - 1);
 	}
 
Index: kernel/test/mm/slab1.c
===================================================================
--- kernel/test/mm/slab1.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/test/mm/slab1.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -31,5 +31,5 @@
 #include <proc/thread.h>
 #include <arch.h>
-#include <mem.h>
+#include <memw.h>
 
 #define VAL_COUNT  1024
@@ -121,5 +121,4 @@
 static void *thr_data[THREADS][THR_MEM_COUNT];
 static slab_cache_t *thr_cache;
-static semaphore_t thr_sem;
 
 static void slabtest(void *data)
@@ -127,6 +126,4 @@
 	int offs = (int) (sysarg_t) data;
 	int i, j;
-
-	thread_detach(THREAD);
 
 	TPRINTF("Starting thread #%" PRIu64 "...\n", THREAD->tid);
@@ -144,26 +141,27 @@
 
 	TPRINTF("Thread #%" PRIu64 " finished\n", THREAD->tid);
-
-	semaphore_up(&thr_sem);
 }
 
 static void testthreads(void)
 {
-	thread_t *t;
-	int i;
-
 	thr_cache = slab_cache_create("thread_cache", THR_MEM_SIZE, 0, NULL, NULL,
 	    SLAB_CACHE_NOMAGAZINE);
 
-	semaphore_initialize(&thr_sem, 0);
-	for (i = 0; i < THREADS; i++) {
-		if (!(t = thread_create(slabtest, (void *) (sysarg_t) i, TASK, THREAD_FLAG_NONE, "slabtest"))) {
+	thread_t *threads[THREADS] = { };
+
+	for (int i = 0; i < THREADS; i++) {
+		threads[i] = thread_create(slabtest, (void *) (sysarg_t) i,
+		    TASK, THREAD_FLAG_NONE, "slabtest");
+		if (threads[i]) {
+			thread_start(threads[i]);
+		} else {
 			TPRINTF("Could not create thread %d\n", i);
-		} else
-			thread_ready(t);
+		}
 	}
 
-	for (i = 0; i < THREADS; i++)
-		semaphore_down(&thr_sem);
+	for (int i = 0; i < THREADS; i++) {
+		if (threads[i] != NULL)
+			thread_join(threads[i]);
+	}
 
 	slab_cache_destroy(thr_cache);
Index: kernel/test/mm/slab2.c
===================================================================
--- kernel/test/mm/slab2.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/test/mm/slab2.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -32,5 +32,5 @@
 #include <arch.h>
 #include <mm/frame.h>
-#include <mem.h>
+#include <memw.h>
 #include <synch/condvar.h>
 #include <synch/mutex.h>
@@ -127,5 +127,4 @@
 
 static slab_cache_t *thr_cache;
-static semaphore_t thr_sem;
 static condvar_t thread_starter;
 static mutex_t starter_mutex;
@@ -136,6 +135,4 @@
 {
 	void *data = NULL, *new;
-
-	thread_detach(THREAD);
 
 	mutex_lock(&starter_mutex);
@@ -190,6 +187,4 @@
 	if (!test_quiet)
 		slab_print_list();
-
-	semaphore_up(&thr_sem);
 }
 
@@ -200,6 +195,4 @@
 	 * then release everything, then again allocate, then release
 	 */
-	thread_t *t;
-	int i;
 
 	TPRINTF("Running stress test with size %d\n", size);
@@ -209,16 +202,24 @@
 
 	thr_cache = slab_cache_create("thread_cache", size, 0, NULL, NULL, 0);
-	semaphore_initialize(&thr_sem, 0);
-	for (i = 0; i < THREADS; i++) {
-		if (!(t = thread_create(slabtest, NULL, TASK, THREAD_FLAG_NONE, "slabtest"))) {
+
+	thread_t *threads[THREADS] = { };
+
+	for (int i = 0; i < THREADS; i++) {
+		threads[i] = thread_create(slabtest, NULL,
+		    TASK, THREAD_FLAG_NONE, "slabtest");
+		if (threads[i]) {
+			thread_start(threads[i]);
+		} else {
 			TPRINTF("Could not create thread %d\n", i);
-		} else
-			thread_ready(t);
-	}
+		}
+	}
+
 	thread_sleep(1);
 	condvar_broadcast(&thread_starter);
 
-	for (i = 0; i < THREADS; i++)
-		semaphore_down(&thr_sem);
+	for (int i = 0; i < THREADS; i++) {
+		if (threads[i] != NULL)
+			thread_join(threads[i]);
+	}
 
 	slab_cache_destroy(thr_cache);
Index: kernel/test/synch/semaphore1.c
===================================================================
--- kernel/test/synch/semaphore1.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/test/synch/semaphore1.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -46,6 +46,4 @@
 static void producer(void *arg)
 {
-	thread_detach(THREAD);
-
 	waitq_sleep(&can_start);
 
@@ -58,6 +56,4 @@
 static void consumer(void *arg)
 {
-	thread_detach(THREAD);
-
 	waitq_sleep(&can_start);
 
@@ -93,16 +89,20 @@
 				thrd = thread_create(consumer, NULL, TASK,
 				    THREAD_FLAG_NONE, "consumer");
-				if (thrd)
-					thread_ready(thrd);
-				else
+				if (thrd) {
+					thread_start(thrd);
+					thread_detach(thrd);
+				} else {
 					TPRINTF("could not create consumer %d\n", i);
+				}
 			}
 			for (k = 0; k < (4 - i); k++) {
 				thrd = thread_create(producer, NULL, TASK,
 				    THREAD_FLAG_NONE, "producer");
-				if (thrd)
-					thread_ready(thrd);
-				else
+				if (thrd) {
+					thread_start(thrd);
+					thread_detach(thrd);
+				} else {
 					TPRINTF("could not create producer %d\n", i);
+				}
 			}
 		}
@@ -111,5 +111,5 @@
 
 		thread_sleep(1);
-		waitq_wakeup(&can_start, WAKEUP_ALL);
+		waitq_wake_all(&can_start);
 
 		while ((items_consumed != consumers) || (items_produced != producers)) {
Index: kernel/test/synch/semaphore2.c
===================================================================
--- kernel/test/synch/semaphore2.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/test/synch/semaphore2.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -61,6 +61,4 @@
 	int to;
 
-	thread_detach(THREAD);
-
 	waitq_sleep(&can_start);
 
@@ -94,12 +92,14 @@
 		thrd = thread_create(consumer, NULL, TASK,
 		    THREAD_FLAG_NONE, "consumer");
-		if (thrd)
-			thread_ready(thrd);
-		else
+		if (thrd) {
+			thread_start(thrd);
+			thread_detach(thrd);
+		} else {
 			TPRINTF("Error creating thread\n");
+		}
 	}
 
 	thread_usleep(20000);
-	waitq_wakeup(&can_start, WAKEUP_ALL);
+	waitq_wake_all(&can_start);
 
 	return NULL;
Index: kernel/test/thread/thread1.c
===================================================================
--- kernel/test/thread/thread1.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ kernel/test/thread/thread1.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -38,34 +38,29 @@
 
 static atomic_bool finish;
-static atomic_size_t threads_finished;
 
 static void threadtest(void *data)
 {
-	thread_detach(THREAD);
-
 	while (atomic_load(&finish)) {
 		TPRINTF("%" PRIu64 " ", THREAD->tid);
 		thread_usleep(100000);
 	}
-	atomic_inc(&threads_finished);
 }
 
 const char *test_thread1(void)
 {
-	unsigned int i;
-	size_t total = 0;
+	atomic_store(&finish, true);
 
-	atomic_store(&finish, true);
-	atomic_store(&threads_finished, 0);
+	thread_t *threads[THREADS] = { };
 
-	for (i = 0; i < THREADS; i++) {
-		thread_t *t;
-		if (!(t = thread_create(threadtest, NULL, TASK,
-		    THREAD_FLAG_NONE, "threadtest"))) {
+	for (int i = 0; i < THREADS; i++) {
+		threads[i] = thread_create(threadtest, NULL,
+		    TASK, THREAD_FLAG_NONE, "threadtest");
+
+		if (threads[i]) {
+			thread_start(threads[i]);
+		} else {
 			TPRINTF("Could not create thread %d\n", i);
 			break;
 		}
-		thread_ready(t);
-		total++;
 	}
 
@@ -74,7 +69,10 @@
 
 	atomic_store(&finish, false);
-	while (atomic_load(&threads_finished) < total) {
-		TPRINTF("Threads left: %zu\n", total - atomic_load(&threads_finished));
-		thread_sleep(1);
+
+	for (int i = 0; i < THREADS; i++) {
+		if (threads[i] != NULL)
+			thread_join(threads[i]);
+
+		TPRINTF("Threads left: %d\n", THREADS - i - 1);
 	}
 
Index: rnel/tools/genmap.py
===================================================================
--- kernel/tools/genmap.py	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,124 +1,0 @@
-#!/usr/bin/env python
-#
-# Copyright (c) 2006 Ondrej Palkovsky
-# 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.
-#
-
-"""
-Create binary symbol map out of linker map file
-"""
-
-import sys
-import struct
-import re
-
-MAXSTRING = 63
-symtabfmt = "<Q%ds" % (MAXSTRING + 1)
-
-funcline = re.compile(r'([0-9a-f]+)\s+[lg]\s+.\s+\.text\s+([0-9a-f]+)\s+(.*)$')
-bssline = re.compile(r'([0-9a-f]+)\s+[lg]\s+[a-zA-Z]\s+\.bss\s+([0-9a-f]+)\s+(.*)$')
-dataline = re.compile(r'([0-9a-f]+)\s+[lg]\s+[a-zA-Z]\s+\.data\s+([0-9a-f]+)\s+(.*)$')
-fileexp = re.compile(r'([^\s]+):\s+file format')
-startfile = re.compile(r'\.(text|bss|data)\s+(0x[0-9a-f]+)\s+0x[0-9a-f]+\s+(.*)$')
-
-def read_obdump(inp):
-	"Parse input"
-
-	funcs = {}
-	data = {}
-	bss = {}
-	fname = ''
-
-	for line in inp:
-		line = line.strip()
-		res = funcline.match(line)
-		if (res):
-			funcs.setdefault(fname, []).append((int(res.group(1), 16), res.group(3)))
-			continue
-
-		res = bssline.match(line)
-		if (res):
-			start = int(res.group(1), 16)
-			end = int(res.group(2), 16)
-			if (end):
-				bss.setdefault(fname, []).append((start, res.group(3)))
-
-		res = dataline.match(line)
-		if (res):
-			start = int(res.group(1), 16)
-			end = int(res.group(2), 16)
-			if (end):
-				data.setdefault(fname, []).append((start, res.group(3)))
-
-		res = fileexp.match(line)
-		if (res):
-			fname = res.group(1)
-			continue
-
-	return {'text' : funcs, 'bss' : bss, 'data' : data}
-
-def generate(kmapf, obmapf, out):
-	"Generate output file"
-
-	obdump = read_obdump(obmapf)
-
-	def key_sorter(x):
-		return x[0]
-
-	for line in kmapf:
-		line = line.strip()
-		res = startfile.match(line)
-
-		if ((res) and (res.group(3) in obdump[res.group(1)])):
-			offset = int(res.group(2), 16)
-			fname = res.group(3)
-			symbols = obdump[res.group(1)][fname]
-			symbols.sort(key = key_sorter)
-			for addr, symbol in symbols:
-				value = fname + ':' + symbol
-				value_bytes = value.encode('ascii')
-				data = struct.pack(symtabfmt, addr + offset, value_bytes[:MAXSTRING])
-				out.write(data)
-
-	out.write(struct.pack(symtabfmt, 0, b''))
-
-def main():
-	if (len(sys.argv) != 4):
-		print("Usage: %s <kernel.map> <nm dump> <output.bin>" % sys.argv[0])
-		return 1
-
-	kmapf = open(sys.argv[1], 'r')
-	obmapf = open(sys.argv[2], 'r')
-	out = open(sys.argv[3], 'wb')
-
-	generate(kmapf, obmapf, out)
-
-	kmapf.close()
-	obmapf.close()
-	out.close()
-
-if __name__ == '__main__':
-	sys.exit(main())
Index: meson.build
===================================================================
--- meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,5 +36,5 @@
     [ 'c', 'cpp' ],
     default_options : ['buildtype=plain', 'c_std=gnu11', 'cpp_std=c++17', 'warning_level=2', 'werror=false', 'b_staticpic=false', 'prefix=/' ],
-    meson_version: '>=0.50.1',
+    meson_version: '>=0.55.0',
 )
 
@@ -61,7 +61,4 @@
 
 ## Some architectures need a particular string at the beginning of assembly files.
-if not is_variable('kernel_as_prolog')
-	kernel_as_prolog = ''
-endif
 if not is_variable('uspace_as_prolog')
 	uspace_as_prolog = ''
@@ -75,4 +72,12 @@
 install_files = []
 install_deps = []
+
+# Exported libraries and include files
+# Format: [ type, source_file, destination ]
+# See meson/part/exports/copy-export.sh for supported file types.
+exported_devel_files = [
+    [ 'includenamedsymlink', 'libc', 'common' ]
+]
+
 # Install script for headers.
 # TODO: Make a list of directories and turn into script later.
Index: meson/arch/amd64/meson.build
===================================================================
--- meson/arch/amd64/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ meson/arch/amd64/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 #
+# Copyright (c) 2021 Jiri Svoboda
 # Copyright (c) 2019 Jiří Zárevúcky
 # All rights reserved.
@@ -44,10 +45,8 @@
 endif
 
-
-# TODO: Enable --gc-sections
-arch_kernel_link_args = [ '-Wl,-z,max-page-size=0x1000', '-nostdlib', '-Wl,--no-gc-sections' ]
+arch_kernel_link_args = [ '-Wl,-z,max-page-size=0x1000', '-nostdlib' ]
 arch_uspace_link_args = [ '-Wl,-z,max-page-size=0x1000', '-nostdlib', '-lgcc' ]
 
-
+# Binaries to be included in a barebone build
 rd_essential += [
 	'app/edit',
@@ -56,9 +55,12 @@
 
 	'srv/audio/hound',
+]
 
+# Drivers to be included in a barebone build
+rd_essential_drv += [
 	'drv/intctl/apic',
 	'drv/intctl/i8259',
 	'drv/platform/pc',
-	'drv/block/ata_bd',
+	'drv/block/isa-ide',
 	'drv/bus/pci/pciintel',
 	'drv/bus/isa',
@@ -68,2 +70,40 @@
 	'drv/hid/xtkbd',
 ]
+
+# Drivers to be included in a regular build
+rd_drv += [
+	'drv/audio/hdaudio',
+	'drv/audio/sb16',
+	'drv/block/ahci',
+	'drv/block/isa-ide',
+	'drv/block/pci-ide',
+	'drv/block/usbmast',
+	'drv/block/virtio-blk',
+	'drv/bus/isa',
+	'drv/bus/pci/pciintel',
+	'drv/bus/usb/ehci',
+	'drv/bus/usb/ohci',
+	'drv/bus/usb/uhci',
+	'drv/bus/usb/usbdiag',
+	'drv/bus/usb/usbflbk',
+	'drv/bus/usb/usbhub',
+	'drv/bus/usb/usbmid',
+	'drv/bus/usb/vhc',
+	'drv/bus/usb/xhci',
+	'drv/char/i8042',
+	'drv/char/ns8250',
+	'drv/char/pc-lpt',
+	'drv/hid/ps2mouse',
+	'drv/hid/xtkbd',
+	'drv/hid/usbhid',
+	'drv/intctl/apic',
+	'drv/intctl/i8259',
+	'drv/nic/ar9271',
+	'drv/nic/e1k',
+	'drv/nic/ne2k',
+	'drv/nic/rtl8139',
+	'drv/nic/rtl8169',
+	'drv/nic/virtio-net',
+	'drv/platform/pc',
+	'drv/time/cmos-rtc',
+]
Index: meson/arch/arm32/meson.build
===================================================================
--- meson/arch/arm32/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ meson/arch/arm32/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 #
+# Copyright (c) 2021 Jiri Svoboda
 # Copyright (c) 2019 Jiří Zárevúcky
 # All rights reserved.
@@ -41,12 +42,14 @@
 ]
 
+arch_kernel_c_args = arch_uspace_c_args + [ '-mno-unaligned-access', '-mfpu=vfpv3' ]
+arch_kernel_c_args += cc.get_supported_arguments(['-mgeneral-regs-only' ])
+arch_kernel_link_args = [ '-nostdlib', '-Wl,-z,max-page-size=0x1000' ]
+
 if CONFIG_FPU
-	# This is necessary for kernel too, to allow vmsr insn and fpexc manipulation.
-	# Use vfp32 to allow context save/restore of d16-d31 regs.
-	arch_uspace_c_args += [ '-mfloat-abi=hard' ]
+    # Need to use softfp because our libgcc.a uses it too
+    # softfp - generate code with soft-float calling conventions but allow use
+    # of "hard" float instructions
+    arch_uspace_c_args += [ '-mfloat-abi=softfp' ]
 endif
-
-arch_kernel_c_args = arch_uspace_c_args + [ '-mno-unaligned-access', '-mfpu=vfpv3' ]
-arch_kernel_link_args = [ '-nostdlib', '-Wl,-z,max-page-size=0x1000' ]
 arch_uspace_link_args = [ '-nostdlib', '-lgcc', '-Wl,-z,max-page-size=0x1000' ]
 
@@ -54,6 +57,5 @@
 arch_boot_link_args = arch_kernel_link_args
 
-
-rd_essential += [
+rd_essential_drv += [
 	'drv/bus/usb/ehci',
 	'drv/bus/usb/ohci',
@@ -73,5 +75,5 @@
 
 elif MACHINE == 'beagleboardxm' or MACHINE == 'beaglebone'
-	rd_essential += [
+	rd_essential_drv += [
 		'drv/platform/amdm37x',
 		'drv/fb/amdm37x_dispc',
@@ -79,5 +81,5 @@
 
 elif MACHINE == 'integratorcp'
-	rd_essential += [
+	rd_essential_drv += [
 		'drv/char/pl050',
 		'drv/hid/atkbd',
@@ -87,2 +89,4 @@
 	]
 endif
+
+rd_drv += rd_essential_drv
Index: meson/arch/arm64/meson.build
===================================================================
--- meson/arch/arm64/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ meson/arch/arm64/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 #
+# Copyright (c) 2021 Jiri Svoboda
 # Copyright (c) 2015 Petr Pavlu
 # All rights reserved.
@@ -27,5 +28,9 @@
 #
 
-arch_uspace_c_args = [ '-D__LE__', '-fno-omit-frame-pointer' ]
+arch_uspace_c_args = [
+	'-D__LE__',
+	'-fno-omit-frame-pointer',
+	cc.get_supported_arguments(['-mno-outline-atomics']),
+]
 arch_kernel_c_args = arch_uspace_c_args + [ '-march=armv8-a+nofp+nosimd', '-mgeneral-regs-only' ]
 arch_kernel_link_args = [ '-nostdlib' ]
@@ -42,9 +47,9 @@
 # processor is in the execution mode that has unaligned access enabled. The
 # -mstrict-align option is therefore not needed.
-arch_boot_c_args = arch_uspace_c_args + [ '-fpic', '-fvisibility=hidden', '-fno-function-sections' ]
-arch_boot_link_args = [ '-Wl,-shared', '-Wl,--no-gc-sections' ]
+arch_boot_c_args = arch_uspace_c_args + [ '-fpic', '-fvisibility=hidden' ]
+arch_boot_link_args = [ '-Wl,-shared' ] + ldflags_ignore_rwx_segments
 
 if MACHINE == 'virt'
-	rd_essential += [
+	rd_essential_drv += [
 		'drv/char/pl011',
 		'drv/intctl/gicv2',
@@ -54,5 +59,5 @@
 
 if MACHINE == 'hikey960'
-	rd_essential += [
+	rd_essential_drv += [
 		'drv/char/pl011',
 		'drv/intctl/gicv2',
@@ -60,2 +65,4 @@
 	]
 endif
+
+rd_drv += rd_essential_drv
Index: meson/arch/ia32/meson.build
===================================================================
--- meson/arch/ia32/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ meson/arch/ia32/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 #
+# Copyright (c) 2021 Jiri Svoboda
 # Copyright (c) 2019 Jiří Zárevúcky
 # All rights reserved.
@@ -35,6 +36,5 @@
 
 arch_uspace_c_args = [
-	# FIXME: enabling -march for uspace makes malloc tests crash. Investigate.
-	# _march,
+	_march,
 	'-D__LE__',
 	'-mno-tls-direct-seg-refs',
@@ -51,10 +51,8 @@
 ]
 
-
-# TODO: Enable --gc-sections.
-arch_kernel_link_args = [ '-nostdlib', '-Wl,--no-gc-sections' ]
+arch_kernel_link_args = [ '-nostdlib' ]
 arch_uspace_link_args = [ '-nostdlib', '-lgcc' ]
 
-
+# Binaries to be included in a barebone build
 rd_essential += [
 	'app/edit',
@@ -63,9 +61,12 @@
 
 	'srv/audio/hound',
+]
 
+# Drivers to be included in a barebone build
+rd_essential_drv += [
 	'drv/intctl/apic',
 	'drv/intctl/i8259',
 	'drv/platform/pc',
-	'drv/block/ata_bd',
+	'drv/block/isa-ide',
 	'drv/bus/pci/pciintel',
 	'drv/bus/isa',
@@ -75,2 +76,40 @@
 	'drv/hid/xtkbd',
 ]
+
+# Drivers to be included in a regular build
+rd_drv += [
+	'drv/audio/hdaudio',
+	'drv/audio/sb16',
+	'drv/block/ahci',
+	'drv/block/isa-ide',
+	'drv/block/pci-ide',
+	'drv/block/usbmast',
+	'drv/block/virtio-blk',
+	'drv/bus/isa',
+	'drv/bus/pci/pciintel',
+	'drv/bus/usb/ehci',
+	'drv/bus/usb/ohci',
+	'drv/bus/usb/uhci',
+	'drv/bus/usb/usbdiag',
+	'drv/bus/usb/usbflbk',
+	'drv/bus/usb/usbhub',
+	'drv/bus/usb/usbmid',
+	'drv/bus/usb/vhc',
+	'drv/bus/usb/xhci',
+	'drv/char/i8042',
+	'drv/char/ns8250',
+	'drv/char/pc-lpt',
+	'drv/hid/ps2mouse',
+	'drv/hid/xtkbd',
+	'drv/hid/usbhid',
+	'drv/intctl/apic',
+	'drv/intctl/i8259',
+	'drv/nic/ar9271',
+	'drv/nic/e1k',
+	'drv/nic/ne2k',
+	'drv/nic/rtl8139',
+	'drv/nic/rtl8169',
+	'drv/nic/virtio-net',
+	'drv/platform/pc',
+	'drv/time/cmos-rtc',
+]
Index: meson/arch/ia64/meson.build
===================================================================
--- meson/arch/ia64/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ meson/arch/ia64/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 #
+# Copyright (c) 2021 Jiri Svoboda
 # Copyright (c) 2019 Jiří Zárevúcky
 # All rights reserved.
@@ -35,11 +36,14 @@
 arch_boot_c_args = [ '-D__LE__', '-fno-unwind-tables', '-mfixed-range=f32-f127', '-mno-pic', '-mno-sdata' ]
 
+# work around GCC bug 112604
+arch_uspace_c_args += ['-fno-unswitch-loops']
+
 arch_uspace_link_args = [ '-nostdlib', '-lgcc' ]
-arch_kernel_link_args = [ '-nostdlib', '-Wl,-EL' ]
-arch_boot_link_args = []
+arch_kernel_link_args = [ '-nostdlib', '-Wl,-EL', '-Wl,--no-warn-rwx-segments']
+arch_boot_link_args = ['-Wl,--no-warn-rwx-segments']
 
 
 if MACHINE == 'i460GX'
-	rd_essential += [
+	rd_essential_drv += [
 		'drv/platform/pc',
 		'drv/bus/pci/pciintel',
@@ -50,5 +54,5 @@
 
 elif MACHINE == 'ski'
-	rd_essential += [
+	rd_essential_drv += [
 		'drv/char/ski-con',
 		'drv/platform/ski',
@@ -56,2 +60,4 @@
 
 endif
+
+rd_drv += rd_essential_drv
Index: meson/arch/mips32/meson.build
===================================================================
--- meson/arch/mips32/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ meson/arch/mips32/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 #
+# Copyright (c) 2021 Jiri Svoboda
 # Copyright (c) 2019 Jiří Zárevúcky
 # All rights reserved.
@@ -60,14 +61,11 @@
 arch_boot_link_args = []
 
-
-kernel_as_prolog = '.module softfloat;'
 uspace_as_prolog = '.module softfloat;.abicalls;'
 
-
 if MACHINE == 'bmalta' or MACHINE == 'lmalta'
-	rd_essential += [
+	rd_essential_drv += [
 		'drv/platform/malta',
 		'drv/intctl/i8259',
-		'drv/block/ata_bd',
+		'drv/block/isa-ide',
 		'drv/bus/pci/pciintel',
 		'drv/bus/isa',
@@ -79,5 +77,5 @@
 
 elif MACHINE == 'msim'
-	rd_essential += [
+	rd_essential_drv += [
 		'drv/platform/msim',
 		'drv/block/ddisk',
@@ -86,2 +84,4 @@
 
 endif
+
+rd_drv += rd_essential_drv
Index: meson/arch/ppc32/meson.build
===================================================================
--- meson/arch/ppc32/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ meson/arch/ppc32/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 #
+# Copyright (c) 2023 Jiri Svoboda
 # Copyright (c) 2019 Jiří Zárevúcky
 # All rights reserved.
@@ -27,17 +28,19 @@
 #
 
-# FIXME: enable --gc-sections
-
 _common_c_args = [ '-D__BE__', '-fno-omit-frame-pointer', '-m32', '-Wa,-a32', '-mcpu=powerpc' ]
 
-arch_kernel_c_args = _common_c_args + [ '-msoft-float', '-fno-function-sections' ]
+arch_kernel_c_args = _common_c_args + [ '-msoft-float' ]
 arch_uspace_c_args = _common_c_args + [ CONFIG_FPU ? '-mhard-float' : '-msoft-float' ]
 arch_boot_c_args = arch_kernel_c_args
-arch_kernel_link_args = [ '-nostdlib', '-Wl,-z,max-page-size=0x1000', '-Wl,--no-check-sections', '-Wl,--no-gc-sections' ]
+arch_kernel_link_args = [ '-nostdlib', '-Wl,-z,max-page-size=0x1000' ]
 arch_uspace_link_args = [ '-nostdlib', '-lgcc', '-Wl,-z,max-page-size=0x1000' ]
-arch_boot_link_args = []
-
+arch_uspace_link_args += ldflags_ignore_rwx_segments
+arch_boot_link_args = ldflags_ignore_rwx_segments
 
 rd_essential += [
+	'app/ofw'
+]
+
+rd_essential_drv += [
 	'drv/platform/mac',
 	'drv/bus/adb/cuda_adb',
@@ -54,2 +57,4 @@
 	'drv/hid/usbhid',
 ]
+
+rd_drv += rd_essential_drv
Index: meson/arch/riscv64/meson.build
===================================================================
--- meson/arch/riscv64/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ meson/arch/riscv64/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 #
+# Copyright (c) 2021 Jiri Svoboda
 # Copyright (c) 2019 Jiří Zárevúcky
 # All rights reserved.
@@ -32,6 +33,8 @@
 arch_kernel_link_args = [ '-nostdlib' ]
 arch_uspace_link_args = [ '-nostdlib', '-lgcc' ]
-arch_boot_link_args = []
+arch_boot_link_args = ldflags_ignore_rwx_segments
 
 rd_essential += [
 ]
+
+rd_drv += rd_essential_drv
Index: meson/arch/sparc64/meson.build
===================================================================
--- meson/arch/sparc64/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ meson/arch/sparc64/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 #
+# Copyright (c) 2023 Jiri Svoboda
 # Copyright (c) 2019 Jiří Zárevúcky
 # All rights reserved.
@@ -75,12 +76,15 @@
 endif
 
+rd_essential += [
+	'app/ofw'
+]
 
 if PROCESSOR == 'sun4v'
-	rd_essential += [
+	rd_essential_drv += [
 		'drv/platform/sun4v',
 		'drv/char/sun4v-con',
 	]
 else
-	rd_essential += [
+	rd_essential_drv += [
 		'drv/platform/sun4u',
 		'drv/bus/pci/pciintel',
@@ -88,4 +92,9 @@
 		'drv/intctl/obio',
 		'drv/char/ns8250',
+		'drv/char/i8042',
+		'drv/hid/ps2mouse',
+		'drv/hid/xtkbd',
 	]
 endif
+
+rd_drv += rd_essential_drv
Index: meson/part/compiler_args/meson.build
===================================================================
--- meson/part/compiler_args/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ meson/part/compiler_args/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -60,5 +60,4 @@
 	'-Wall',
 	'-Wextra',
-	'-Werror-implicit-function-declaration',
 	'-Wwrite-strings',
 	'-Wunknown-pragmas',
@@ -69,6 +68,8 @@
 
 	'-ffunction-sections',
+	'-fdata-sections',
 	'-fno-common',
-	'-fdebug-prefix-map=' + meson.source_root() + '=.',
+	'-fdebug-prefix-map=' + meson.source_root() + '/=',
+	'-fdebug-prefix-map=../../=',
 ]
 
@@ -87,9 +88,10 @@
 
 if CONFIG_LINE_DEBUG
-	extra_common_flags += [ '-gdwarf-4', '-g3' ]
+	extra_common_flags += [ '-gdwarf-5', '-g3' ]
 endif
 
 extra_cflags = extra_common_flags + [
 	'-Wmissing-prototypes',
+	'-Werror-implicit-function-declaration',
 
 	'-Wno-missing-braces',
@@ -110,4 +112,5 @@
 extra_cppflags = extra_common_flags + [
 	'-fno-exceptions',
+	'-Wno-misleading-indentation',
 	'-frtti',
 ]
@@ -128,2 +131,15 @@
 	add_project_link_arguments(extra_cflags, language : [ lang ])
 endforeach
+
+# This flag is needed at several places, hence we define it here.
+#
+# For backwards compatibility we try to detect --no-warn-rwx-segments.
+# However, the autodetection done by Meson also results in
+# "cannot find entry symbol _start; defaulting to 00000000004000b0"
+# thus the option is never supported alone. So when detecting we also
+# specify --entry=main so that the stub source provided by Meson is build
+# correctly.
+ldflags_ignore_rwx_segments = []
+if cc.has_link_argument('-Wl,--no-warn-rwx-segments,--entry=main')
+    ldflags_ignore_rwx_segments += ['-Wl,--no-warn-rwx-segments']
+endif
Index: meson/part/exports/copy-export.sh
===================================================================
--- meson/part/exports/copy-export.sh	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ meson/part/exports/copy-export.sh	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,70 @@
+#!/bin/sh
+
+#
+# Copyright (c) 2024 Vojtech Horky
+# 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.
+#
+
+set -ue
+
+target_dir="${DESTDIR:-export-dev}"
+
+rm -rf "${target_dir:?}/lib"
+rm -rf "${target_dir}/include"
+
+mkdir -p "${target_dir}/lib"
+mkdir -p "${target_dir}/include"
+
+
+while [ "$#" -gt 0 ]; do
+    case "$1" in
+        staticlib)
+            ar -t "$2" | xargs ar crs "${target_dir}/lib/$3"
+            ;;
+        include)
+            mkdir -p "${target_dir}/include/$3"
+            cp -r -L -t "${target_dir}/include/$3" "$2"/*
+            ;;
+        includesymlink)
+            (
+                cd "${target_dir}/include/$3" && ln -s "../$2" .
+            )
+            ;;
+        includenamedsymlink)
+            (
+                cd "${target_dir}/include/" && ln -s "$2" "$3"
+            )
+            ;;
+        config)
+            cp -L "$2" "${target_dir}/$3"
+            ;;
+        *)
+            echo "Unknown type $1, aborting." >&2
+            exit 1
+            ;;
+    esac
+    shift 3
+done
Index: meson/part/exports/meson.build
===================================================================
--- meson/part/exports/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ meson/part/exports/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -85,2 +85,12 @@
 	capture: true,
 )
+
+run_target('export-dev',
+    command: [
+        sh,
+        meson.source_root() / 'meson' / 'part' / 'exports' / 'copy-export.sh',
+    ] + [
+        'config', config_mk, 'config.mk',
+        'config', config_sh, 'config.sh',
+    ] + exported_devel_files
+)
Index: meson/part/extra_targets/meson.build
===================================================================
--- meson/part/extra_targets/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ meson/part/extra_targets/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -34,5 +34,5 @@
 		'--',
 		meson.build_root(),
-		config_py.path(),
+		config_py.full_path(),
 		meson.source_root() / 'HelenOS.config',
 		meson.source_root() / 'defaults',
@@ -51,5 +51,5 @@
 			'--',
 			meson.build_root(),
-			config_py.path(),
+			config_py.full_path(),
 			meson.source_root() / 'HelenOS.config',
 			meson.source_root() / 'defaults',
@@ -77,5 +77,5 @@
 		'--',
 		meson.source_root() / 'doxygen',
-		doxygen.path(),
+		doxygen.full_path(),
 		_dox_cfg,
 	])
Index: meson/part/initrd_manifest/meson.build
===================================================================
--- meson/part/initrd_manifest/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ meson/part/initrd_manifest/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -29,4 +29,8 @@
 # Init binaries. These are actually baked into the bootloader.
 rd_init = [
+	# ELF file with DWARF debug sections for kernel.
+	# Kernel detects this entry by name.
+	'kernel.dbg',
+
 	# IMPORTANT: The order of entries is important for bootloader!
 	'srv/ns',
@@ -52,5 +56,7 @@
 	'srv/hid/output',
 	'srv/klog',
+]
 
+rd_essential_drv = [
 	'drv/root/root',
 	'drv/root/virt',
@@ -58,7 +64,9 @@
 ]
 
+rd_drv = rd_essential_drv
+
 if CONFIG_FB
 	rd_essential += [
-		'app/launcher',
+		'app/taskbar',
 		'app/terminal',
 
Index: meson/part/tools/meson.build
===================================================================
--- meson/part/tools/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ meson/part/tools/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -61,9 +61,15 @@
 endif
 
+# On some distributions (e.g. Arch Linux), genisoimage is actually a symbolic
+# link to mkisofs. However, they do not accept exactly the same options.
+# Thus we check if genisoimage is a symbolic link to mkisofs and if it is so,
+# we switch to mkisofs as that is the native application available.
+fs = import('fs')
 genisoimage = find_program('genisoimage', required: false)
-if genisoimage.found()
+_mkisofs = find_program('mkisofs', required: false)
+if genisoimage.found() and not (fs.is_symlink(genisoimage.full_path()) and _mkisofs.found() and fs.is_samepath(genisoimage.full_path(), _mkisofs.full_path()))
 	genisoimage_type = 'genisoimage'
 else
-	genisoimage = find_program('mkisofs', required: false)
+	genisoimage = _mkisofs
 	if genisoimage.found()
 		genisoimage_type = 'mkisofs'
Index: tools/build-ccheck.sh
===================================================================
--- tools/build-ccheck.sh	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ tools/build-ccheck.sh	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,5 +1,5 @@
 #!/bin/sh
 #
-# Copyright (c) 2019 Jiri Svoboda
+# Copyright (c) 2023 Jiri Svoboda
 # All rights reserved.
 #
@@ -29,5 +29,5 @@
 
 SYCEK_GIT="https://github.com/jxsvoboda/sycek"
-SYCEK_REV="14f0aadad8bb2f17eef030a74b3591377ad57343"
+SYCEK_REV="e606f73fd875b5d9ff48261b23f69552a893f5d4"
 
 if [ ! -d sycek ]; then
Index: tools/conf/msim.conf
===================================================================
--- tools/conf/msim.conf	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ tools/conf/msim.conf	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -3,6 +3,6 @@
 #
 
-add dcpu cpu0
-add dcpu cpu1
+add dr4kcpu cpu0
+add dr4kcpu cpu1
 
 add rwm mainmem 0x00000000
Index: tools/ew.py
===================================================================
--- tools/ew.py	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ tools/ew.py	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -79,5 +79,5 @@
 	for termemu in emus:
 		try:
-			subprocess.check_output('which ' + termemu, shell = True)
+			subprocess.check_output('which ' + termemu, shell = True, stderr = subprocess.STDOUT)
 			return termemu
 		except:
@@ -173,5 +173,8 @@
 			raise Exception
 		if processor == 'us':
-			return 'system-sparc64', '-M sun4u --prom-env boot-args="console=devices/\\hw\\pci0\\01:01.0\\com1\\a"'
+			cmdline = '-M sun4u'
+			if is_override('nographic'):
+			    cmdline += ' --prom-env boot-args="console=devices/\\hw\\pci0\\01:01.0\\com1\\a"'
+			return 'system-sparc64', cmdline
 
 		# processor = 'sun4v'
@@ -285,4 +288,5 @@
 	if (is_override('nographic')):
 		cmdline += ' -nographic'
+		console = True
 
 	if (not console and (not is_override('nographic')) and not is_override('noserial')):
@@ -327,5 +331,5 @@
 def msim_run(platform, machine, processor):
 	hdisk_mk()
-	run_in_console('msim -c ' + TOOLS_DIR + '/conf/msim.conf', 'HelenOS/mips32 on msim')
+	run_in_console('msim -n -c ' + TOOLS_DIR + '/conf/msim.conf', 'HelenOS/mips32 on msim')
 
 def spike_run(platform, machine, processor):
@@ -410,5 +414,5 @@
 				'image' : 'image.iso',
 				'audio' : False,
-				'console' : True,
+				'console' : False,
 				'net' : False,
 				'usb' : False,
@@ -418,5 +422,8 @@
 			'sun4v' : {
 				'run' : qemu_run,
-				'image' : '-drive if=pflash,readonly=on,file=image.iso',
+				# QEMU 8.0.0 ignores the 'file' argument and instead uses 'id',
+				# which defaults to 'pflash0', but can be changed to the same value
+				# as 'file'
+				'image' : '-drive if=pflash,id=image.iso,readonly=on,file=image.iso',
 				'audio' : False,
 				'console' : True,
Index: tools/export.sh
===================================================================
--- tools/export.sh	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ tools/export.sh	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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 \
@@ -67,6 +70,15 @@
 ninja devel-headers
 
+#
+# Meson may produce thin archives. These cannot be simply copied to another
+# location. Copy them using ar instead, converting them to regular,
+# non-thin archives in the process.
+#
 mkdir -p "$EXPORT_DIR/lib"
-cp -t "$EXPORT_DIR/lib" $EXPORT_LIBS
+for lpath in $EXPORT_LIBS; do
+	dest="$EXPORT_DIR/lib/$(basename $lpath)"
+	ar -t $lpath | xargs ar crs $dest
+done
+
 rm -rf "$EXPORT_DIR/include"
 cp -R dist/include "$EXPORT_DIR/include"
Index: tools/list_profiles.sh
===================================================================
--- tools/list_profiles.sh	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ tools/list_profiles.sh	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -2,4 +2,5 @@
 
 #
+# Copyright (c) 2021 Jiri Svoboda
 # Copyright (c) 2019 Jiří Zárevúcky
 # All rights reserved.
@@ -39,4 +40,17 @@
 CONFIG_DEFAULTS="${SOURCE_DIR}/defaults"
 
-# Find all the leaf subdirectories in the defaults directory.
-find ${CONFIG_DEFAULTS} -type d -links 2 -printf "%P\n" | sort
+# Find the leaf subdirectories in the defaults directory.
+level1dirs=`ls "$CONFIG_DEFAULTS"`
+for dname in $level1dirs; do
+	l2dirs=`ls "$CONFIG_DEFAULTS"/"$dname"`
+	havel2=
+	for dn in $l2dirs; do
+		if [ -d "$CONFIG_DEFAULTS/$dname/$dn" ]; then
+			echo "$dname/$dn"
+			havel2=true
+		fi
+	done
+	if [ -z "$havel2" ]; then
+		echo "$dname"
+	fi
+done
Index: tools/mkarray.py
===================================================================
--- tools/mkarray.py	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ tools/mkarray.py	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ tools/mkarray_for_meson.sh	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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/toolchain.sh
===================================================================
--- tools/toolchain.sh	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ tools/toolchain.sh	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -31,20 +31,31 @@
 BINUTILS_GDB_GIT="https://github.com/HelenOS/binutils-gdb.git"
 
-BINUTILS_BRANCH="binutils-2_31_1-helenos"
-BINUTILS_VERSION="2.31.1"
-
-GDB_BRANCH="gdb-8_2-helenos"
-GDB_VERSION="8.2"
+BINUTILS_BRANCH="binutils-2_41-helenos"
+BINUTILS_VERSION="2.41"
+
+GDB_BRANCH="gdb-13.2-helenos"
+GDB_VERSION="13.2"
 
 GCC_GIT="https://github.com/HelenOS/gcc.git"
-GCC_BRANCH="8_2_0-helenos"
-GCC_VERSION="8.2.0"
+GCC_BRANCH="13_2_0-helenos"
+GCC_VERSION="13.2"
 
 BASEDIR="$PWD"
 SRCDIR="$(readlink -f $(dirname "$0"))"
 
+# If we install into a temporary directory and copy from there,
+# we don't have to trust the upstream makefiles respect the prefix for
+# all files, and we also have a ready-made package for distribution.
+INSTALL_DIR="${BASEDIR}/PKG"
+
 SYSTEM_INSTALL=false
-REAL_INSTALL=true
-USE_HELENOS_TARGET=true
+
+BUILD_GDB=false
+BUILD_BINUTILS=true
+BUILD_GCC=true
+
+if [ -z "$JOBS" ] ; then
+	JOBS="`nproc`"
+fi
 
 check_error() {
@@ -61,5 +72,5 @@
 	echo
 	echo "Syntax:"
-	echo " $0 [--system-wide] [--no-install] [--non-helenos-target] <platform>"
+	echo " $0 [--system-wide] [--with-gdb|--only-gdb] <platform>"
 	echo " $0 [--system-wide] --test-version [<platform>]"
 	echo
@@ -76,5 +87,4 @@
 	echo " sparc64    SPARC V9"
 	echo " all        build all targets"
-	echo " essential  build only targets currently needed for HelenOS development"
 	echo " parallel   same as 'all', but all in parallel"
 	echo " 2-way      same as 'all', but 2-way parallel"
@@ -92,11 +102,4 @@
 	echo "     variable is not set, then the default value of \$HOME/.local/share"
 	echo "     is assumed."
-	echo
-	echo "If the --no-install option is used, the toolchain still uses the"
-	echo "target installation directory as determined above, but the files"
-	echo "are actually copied into the PKG/ subdirectory during the installation"
-	echo "without affecting the actual target file system. This might be useful"
-	echo "when preparing a system-wide installation, but avoiding running this"
-	echo "script under the super user."
 	echo
 	echo "The --non-helenos-target option will build non-HelenOS-specific"
@@ -139,5 +142,5 @@
 	for PLATFORM in $PLATFORMS ; do
 		set_target_from_platform "$PLATFORM"
-		PREFIX="${CROSS_PREFIX}/bin/${HELENOS_TARGET}"
+		PREFIX="${CROSS_PREFIX}/bin/${TARGET}"
 
 		echo "== $PLATFORM =="
@@ -177,4 +180,16 @@
 }
 
+ring_bell() {
+	printf '\a'
+	sleep 0.1
+	printf '\a'
+	sleep 0.1
+	printf '\a'
+	sleep 0.1
+	printf '\a'
+	sleep 0.1
+	printf '\a'
+}
+
 show_countdown() {
 	TM="$1"
@@ -241,17 +256,17 @@
 
 check_dirs() {
-	OUTSIDE="$1"
-	BASE="$2"
-	ORIGINAL="$PWD"
-
-	cd "${BASE}"
-	check_error $? "Unable to change directory to ${BASE}."
+	cd "${BASEDIR}"
+	check_error $? "Unable to change directory to ${BASEDIR}."
 	ABS_BASE="$PWD"
-	cd "${ORIGINAL}"
-	check_error $? "Unable to change directory to ${ORIGINAL}."
-
-	mkdir -p "${OUTSIDE}"
-	cd "${OUTSIDE}"
-	check_error $? "Unable to change directory to ${OUTSIDE}."
+
+	if $SYSTEM_INSTALL && [ ! -d "${CROSS_PREFIX}" ]; then
+		ring_bell
+		( set -x ; sudo -k mkdir -p "${CROSS_PREFIX}" )
+	else
+		mkdir -p "${CROSS_PREFIX}"
+	fi
+
+	cd "${CROSS_PREFIX}"
+	check_error $? "Unable to change directory to ${CROSS_PREFIX}."
 
 	while [ "${#PWD}" -gt "${#ABS_BASE}" ]; do
@@ -266,6 +281,5 @@
 	fi
 
-	cd "${ORIGINAL}"
-	check_error $? "Unable to change directory to ${ORIGINAL}."
+	cd "${BASEDIR}"
 }
 
@@ -280,18 +294,33 @@
 	change_title "Downloading sources"
 	echo ">>> Downloading sources"
-	git clone --depth 1 -b "$BINUTILS_BRANCH" "$BINUTILS_GDB_GIT" "binutils-$BINUTILS_VERSION"
-	git clone --depth 1 -b "$GDB_BRANCH" "$BINUTILS_GDB_GIT" "gdb-$GDB_VERSION"
-	git clone --depth 1 -b "$GCC_BRANCH" "$GCC_GIT" "gcc-$GCC_VERSION"
-
-	# If the directory already existed, pull upstream changes.
-	git -C "binutils-$BINUTILS_VERSION" pull
-	git -C "gdb-$GDB_VERSION" pull
-	git -C "gcc-$GCC_VERSION" pull
-
-	change_title "Downloading GCC prerequisites"
-	echo ">>> Downloading GCC prerequisites"
-	cd "gcc-${GCC_VERSION}"
-	./contrib/download_prerequisites
-	cd ..
+
+	if $BUILD_BINUTILS ; then
+		git clone --depth 1 -b "$BINUTILS_BRANCH" "$BINUTILS_GDB_GIT" "binutils-$BINUTILS_VERSION"
+		# If the directory already existed, pull upstream changes.
+		git -C "binutils-$BINUTILS_VERSION" pull
+	fi
+
+	if $BUILD_GCC ; then
+		git clone --depth 1 -b "$GCC_BRANCH" "$GCC_GIT" "gcc-$GCC_VERSION"
+		git -C "gcc-$GCC_VERSION" pull
+
+		change_title "Downloading GCC prerequisites"
+		echo ">>> Downloading GCC prerequisites"
+		cd "gcc-${GCC_VERSION}"
+		./contrib/download_prerequisites
+		cd ..
+	fi
+
+	if $BUILD_GDB ; then
+		git clone --depth 1 -b "$GDB_BRANCH" "$BINUTILS_GDB_GIT" "gdb-$GDB_VERSION"
+		git -C "gdb-$GDB_VERSION" pull
+	fi
+
+	# This sets the CROSS_PREFIX variable
+	set_cross_prefix
+
+	DESTDIR_SPEC="DESTDIR=${INSTALL_DIR}"
+
+	check_dirs
 }
 
@@ -321,74 +350,27 @@
 	esac
 
-	HELENOS_TARGET="${GNU_ARCH}-helenos"
-
-	case "$1" in
-		"arm32")
-			LINUX_TARGET="${GNU_ARCH}-linux-gnueabi"
-			;;
-		*)
-			LINUX_TARGET="${GNU_ARCH}-linux-gnu"
-			;;
-	esac
-}
-
-build_target() {
-	PLATFORM="$1"
-
-	# This sets the *_TARGET variables
-	set_target_from_platform "$PLATFORM"
-	if $USE_HELENOS_TARGET ; then
-		TARGET="$HELENOS_TARGET"
-	else
-		TARGET="$LINUX_TARGET"
-	fi
+	TARGET="${GNU_ARCH}-helenos"
+}
+
+build_binutils() {
+	# This sets the TARGET variable
+	set_target_from_platform "$1"
 
 	WORKDIR="${BASEDIR}/${TARGET}"
-	INSTALL_DIR="${BASEDIR}/PKG"
 	BINUTILSDIR="${WORKDIR}/binutils-${BINUTILS_VERSION}"
-	GCCDIR="${WORKDIR}/gcc-${GCC_VERSION}"
-	GDBDIR="${WORKDIR}/gdb-${GDB_VERSION}"
-
-	# This sets the CROSS_PREFIX variable
-	set_cross_prefix
-
-	if [ -z "$JOBS" ] ; then
-		JOBS="`nproc`"
-	fi
-
-	PREFIX="${CROSS_PREFIX}"
 
 	echo ">>> Removing previous content"
 	cleanup_dir "${WORKDIR}"
 	mkdir -p "${WORKDIR}"
-	check_dirs "${PREFIX}" "${WORKDIR}"
-
-	if $USE_HELENOS_TARGET ; then
-		change_title "Creating build sysroot"
-		echo ">>> Creating build sysroot"
-		mkdir -p "${WORKDIR}/sysroot/include"
-		mkdir "${WORKDIR}/sysroot/lib"
-		ARCH="$PLATFORM"
-		if [ "$ARCH" = "mips32eb" ]; then
-			ARCH=mips32
-		fi
-
-		cp -r -L -t "${WORKDIR}/sysroot/include" \
-			${SRCDIR}/../abi/include/* \
-			${SRCDIR}/../uspace/lib/c/arch/${ARCH}/include/* \
-			${SRCDIR}/../uspace/lib/c/include/*
-		check_error $? "Failed to create build sysroot."
-	fi
-
-
-	echo ">>> Processing binutils (${PLATFORM})"
+
+	echo ">>> Processing binutils (${TARGET})"
 	mkdir -p "${BINUTILSDIR}"
 	cd "${BINUTILSDIR}"
 	check_error $? "Change directory failed."
 
-	change_title "binutils: configure (${PLATFORM})"
+	change_title "binutils: configure (${TARGET})"
 	CFLAGS="-Wno-error -fcommon" "${BASEDIR}/downloads/binutils-${BINUTILS_VERSION}/configure" \
 		"--target=${TARGET}" \
-		"--prefix=${PREFIX}" \
+		"--prefix=${CROSS_PREFIX}" \
 		"--program-prefix=${TARGET}-" \
 		--disable-nls \
@@ -400,28 +382,35 @@
 	check_error $? "Error configuring binutils."
 
-	change_title "binutils: make (${PLATFORM})"
+	change_title "binutils: make (${TARGET})"
 	make all -j$JOBS
 	check_error $? "Error compiling binutils."
 
-	change_title "binutils: install (${PLATFORM})"
-	make install "DESTDIR=${INSTALL_DIR}"
+	change_title "binutils: install (${TARGET})"
+	make install $DESTDIR_SPEC
 	check_error $? "Error installing binutils."
-
-
-	echo ">>> Processing GCC (${PLATFORM})"
+}
+
+build_gcc() {
+	# This sets the TARGET variable
+	set_target_from_platform "$1"
+
+	WORKDIR="${BASEDIR}/${TARGET}"
+	GCCDIR="${WORKDIR}/gcc-${GCC_VERSION}"
+
+	echo ">>> Removing previous content"
+	cleanup_dir "${WORKDIR}"
+	mkdir -p "${WORKDIR}"
+
+	echo ">>> Processing GCC (${TARGET})"
 	mkdir -p "${GCCDIR}"
 	cd "${GCCDIR}"
 	check_error $? "Change directory failed."
 
-	if $USE_HELENOS_TARGET ; then
-		SYSROOT=--with-sysroot --with-build-sysroot="${WORKDIR}/sysroot"
-	else
-		SYSROOT=--without-headers
-	fi
-
-	change_title "GCC: configure (${PLATFORM})"
-	PATH="$PATH:${INSTALL_DIR}/${PREFIX}/bin" "${BASEDIR}/downloads/gcc-${GCC_VERSION}/configure" \
+	BUILDPATH="${CROSS_PREFIX}/bin:${PATH}"
+
+	change_title "GCC: configure (${TARGET})"
+	PATH="${BUILDPATH}" "${BASEDIR}/downloads/gcc-${GCC_VERSION}/configure" \
 		"--target=${TARGET}" \
-		"--prefix=${PREFIX}" \
+		"--prefix=${CROSS_PREFIX}" \
 		"--program-prefix=${TARGET}-" \
 		--with-gnu-as \
@@ -432,72 +421,98 @@
 		--disable-shared \
 		--disable-werror \
-		$SYSROOT
+		--without-headers  # TODO: Replace with proper sysroot so we can build more libs
 	check_error $? "Error configuring GCC."
 
-	change_title "GCC: make (${PLATFORM})"
-	PATH="${PATH}:${PREFIX}/bin:${INSTALL_DIR}/${PREFIX}/bin" make all-gcc -j$JOBS
+	change_title "GCC: make (${TARGET})"
+	PATH="${BUILDPATH}" make all-gcc -j$JOBS
 	check_error $? "Error compiling GCC."
 
-	if $USE_HELENOS_TARGET ; then
-		PATH="${PATH}:${PREFIX}/bin:${INSTALL_DIR}/${PREFIX}/bin" make all-target-libgcc -j$JOBS
-		check_error $? "Error compiling libgcc."
-		# TODO: libatomic and libstdc++ need some extra care
-		#    PATH="${PATH}:${PREFIX}/bin:${INSTALL_DIR}/${PREFIX}/bin" make all-target-libatomic -j$JOBS
-		#    check_error $? "Error compiling libatomic."
-		#    PATH="${PATH}:${PREFIX}/bin:${INSTALL_DIR}/${PREFIX}/bin" make all-target-libstdc++-v3 -j$JOBS
-		#    check_error $? "Error compiling libstdc++."
-	fi
-
-	change_title "GCC: install (${PLATFORM})"
-	PATH="${PATH}:${INSTALL_DIR}/${PREFIX}/bin" make install-gcc "DESTDIR=${INSTALL_DIR}"
-	if $USE_HELENOS_TARGET ; then
-		PATH="${PATH}:${INSTALL_DIR}/${PREFIX}/bin" make install-target-libgcc "DESTDIR=${INSTALL_DIR}"
-		#    PATH="${PATH}:${INSTALL_DIR}/${PREFIX}/bin" make install-target-libatomic "DESTDIR=${INSTALL_DIR}"
-		#    PATH="${PATH}:${INSTALL_DIR}/${PREFIX}/bin" make install-target-libstdc++-v3 "DESTDIR=${INSTALL_DIR}"
-	fi
+	change_title "GCC: install (${TARGET})"
+	PATH="${BUILDPATH}" make install-gcc $DESTDIR_SPEC
 	check_error $? "Error installing GCC."
-
-
-	echo ">>> Processing GDB (${PLATFORM})"
+}
+
+build_libgcc() {
+	# This sets the TARGET variable
+	set_target_from_platform "$1"
+
+	WORKDIR="${BASEDIR}/${TARGET}"
+	GCCDIR="${WORKDIR}/gcc-${GCC_VERSION}"
+
+	# No removing previous content here, we need the previous GCC build
+
+	cd "${GCCDIR}"
+	check_error $? "Change directory failed."
+
+	BUILDPATH="${CROSS_PREFIX}/bin:${PATH}"
+
+	change_title "libgcc: make (${TARGET})"
+
+	PATH="${BUILDPATH}" make all-target-libgcc -j$JOBS
+	check_error $? "Error compiling libgcc."
+	# TODO: libatomic and libstdc++ need some extra care
+	#    PATH="${BUILDPATH}" make all-target-libatomic -j$JOBS
+	#    check_error $? "Error compiling libatomic."
+	#    PATH="${BUILDPATH}" make all-target-libstdc++-v3 -j$JOBS
+	#    check_error $? "Error compiling libstdc++."
+
+	change_title "libgcc: install (${TARGET})"
+
+	PATH="${BUILDPATH}" make install-target-libgcc $DESTDIR_SPEC
+	#    PATH="${BUILDPATH}" make install-target-libatomic $DESTDIR_SPEC
+	#    PATH="${BUILDPATH}" make install-target-libstdc++-v3 $DESTDIR_SPEC
+	check_error $? "Error installing libgcc."
+}
+
+build_gdb() {
+	# This sets the TARGET variable
+	set_target_from_platform "$1"
+
+	WORKDIR="${BASEDIR}/${TARGET}"
+	GDBDIR="${WORKDIR}/gdb-${GDB_VERSION}"
+
+	echo ">>> Removing previous content"
+	cleanup_dir "${WORKDIR}"
+	mkdir -p "${WORKDIR}"
+
+	echo ">>> Processing GDB (${TARGET})"
 	mkdir -p "${GDBDIR}"
 	cd "${GDBDIR}"
 	check_error $? "Change directory failed."
 
-	change_title "GDB: configure (${PLATFORM})"
-	CFLAGS="-fcommon" PATH="$PATH:${INSTALL_DIR}/${PREFIX}/bin" "${BASEDIR}/downloads/gdb-${GDB_VERSION}/configure" \
+	change_title "GDB: configure (${TARGET})"
+	CFLAGS="-fcommon" "${BASEDIR}/downloads/gdb-${GDB_VERSION}/configure" \
 		"--target=${TARGET}" \
-		"--prefix=${PREFIX}" \
+		"--prefix=${CROSS_PREFIX}" \
 		"--program-prefix=${TARGET}-" \
 		--enable-werror=no
 	check_error $? "Error configuring GDB."
 
-	change_title "GDB: make (${PLATFORM})"
-	PATH="${PATH}:${PREFIX}/bin:${INSTALL_DIR}/${PREFIX}/bin" make all-gdb -j$JOBS
+	change_title "GDB: make (${TARGET})"
+	make all-gdb -j$JOBS
 	check_error $? "Error compiling GDB."
 
-	change_title "GDB: make (${PLATFORM})"
-	PATH="${PATH}:${INSTALL_DIR}/${PREFIX}/bin" make install-gdb "DESTDIR=${INSTALL_DIR}"
+	change_title "GDB: install (${TARGET})"
+	make install-gdb $DESTDIR_SPEC
 	check_error $? "Error installing GDB."
-
+}
+
+install_pkg() {
+	echo ">>> Moving to the destination directory."
+	if $SYSTEM_INSTALL ; then
+		ring_bell
+		( set -x ; sudo -k cp -r -t "${CROSS_PREFIX}" "${INSTALL_DIR}${CROSS_PREFIX}/"* )
+	else
+		( set -x ; cp -r -t "${CROSS_PREFIX}" "${INSTALL_DIR}${CROSS_PREFIX}/"* )
+	fi
+}
+
+link_clang() {
 	# Symlink clang and lld to the install path.
 	CLANG="`which clang 2> /dev/null || echo "/usr/bin/clang"`"
 	LLD="`which ld.lld 2> /dev/null || echo "/usr/bin/ld.lld"`"
 
-	ln -s $CLANG "${INSTALL_DIR}/${PREFIX}/bin/${TARGET}-clang"
-	ln -s $LLD "${INSTALL_DIR}/${PREFIX}/bin/${TARGET}-ld.lld"
-
-	if $REAL_INSTALL ; then
-		echo ">>> Moving to the destination directory."
-		cp -r -t "${PREFIX}" "${INSTALL_DIR}/${PREFIX}/"*
-	fi
-
-	cd "${BASEDIR}"
-	check_error $? "Change directory failed."
-
-	echo ">>> Cleaning up"
-	cleanup_dir "${WORKDIR}"
-
-	echo
-	echo ">>> Cross-compiler for ${TARGET} installed."
+	ln -s $CLANG "${INSTALL_DIR}${CROSS_PREFIX}/bin/${TARGET}-clang"
+	ln -s $LLD "${INSTALL_DIR}${CROSS_PREFIX}/bin/${TARGET}-ld.lld"
 }
 
@@ -512,10 +527,12 @@
 			exit
 			;;
-		--no-install)
-			REAL_INSTALL=false
+		--with-gdb)
+			BUILD_GDB=true
 			shift
 			;;
-		--non-helenos-target)
-			USE_HELENOS_TARGET=false
+		--only-gdb)
+			BUILD_GDB=true
+			BUILD_BINUTILS=false
+			BUILD_GCC=false
 			shift
 			;;
@@ -529,4 +546,80 @@
 	show_usage
 fi
+
+PLATFORMS="amd64 arm32 arm64 ia32 ia64 mips32 mips32eb ppc32 riscv64 sparc64"
+
+run_one() {
+	$1 $PLATFORM
+}
+
+run_all() {
+	for x in $PLATFORMS ; do
+		$1 $x
+	done
+}
+
+run_parallel() {
+	for x in $PLATFORMS ; do
+		$1 $x &
+	done
+	wait
+}
+
+run_2way() {
+	$1 amd64 &
+	$1 arm32 &
+	wait
+
+	$1 arm64 &
+	$1 ia32 &
+	wait
+
+	$1 ia64 &
+	$1 mips32 &
+	wait
+
+	$1 mips32eb &
+	$1 ppc32 &
+	wait
+
+	$1 riscv64 &
+	$1 sparc64 &
+	wait
+}
+
+everything() {
+	RUNNER="$1"
+
+	prepare
+
+	if $BUILD_BINUTILS ; then
+		$RUNNER build_binutils
+
+		if $BUILD_GCC ; then
+			# gcc/libgcc may fail to build correctly if binutils is not installed first
+			echo ">>> Installing binutils"
+			install_pkg
+		fi
+	fi
+
+	if $BUILD_GCC ; then
+		$RUNNER build_gcc
+
+		# libgcc may fail to build correctly if gcc is not installed first
+		echo ">>> Installing GCC"
+		install_pkg
+
+		$RUNNER build_libgcc
+	fi
+
+	if $BUILD_GDB ; then
+		$RUNNER build_gdb
+	fi
+
+	echo ">>> Installing all files"
+	install_pkg
+
+	link_clang
+}
 
 case "$1" in
@@ -536,67 +629,15 @@
 		;;
 	amd64|arm32|arm64|ia32|ia64|mips32|mips32eb|ppc32|riscv64|sparc64)
-		prepare
-		build_target "$1"
+		PLATFORM="$1"
+		everything run_one
 		;;
 	"all")
-		prepare
-		build_target "amd64"
-		build_target "arm32"
-		build_target "arm64"
-		build_target "ia32"
-		build_target "ia64"
-		build_target "mips32"
-		build_target "mips32eb"
-		build_target "ppc32"
-		build_target "riscv64"
-		build_target "sparc64"
-		;;
-	"essential")
-		prepare
-		build_target "amd64"
-		build_target "arm32"
-		build_target "arm64"
-		build_target "ia32"
-		build_target "ia64"
-		build_target "mips32"
-		build_target "mips32eb"
-		build_target "ppc32"
-		build_target "sparc64"
+		everything run_all
 		;;
 	"parallel")
-		prepare
-		build_target "amd64" &
-		build_target "arm32" &
-		build_target "arm64" &
-		build_target "ia32" &
-		build_target "ia64" &
-		build_target "mips32" &
-		build_target "mips32eb" &
-		build_target "ppc32" &
-		build_target "riscv64" &
-		build_target "sparc64" &
-		wait
+		everything run_parallel
 		;;
 	"2-way")
-		prepare
-		build_target "amd64" &
-		build_target "arm32" &
-		wait
-
-		build_target "arm64" &
-		build_target "ia32" &
-		wait
-
-		build_target "ia64" &
-		build_target "mips32" &
-		wait
-
-		build_target "mips32eb" &
-		build_target "ppc32" &
-		wait
-
-		build_target "riscv64" &
-		build_target "sparc64" &
-		wait
+		everything run_2way
 		;;
 	*)
Index: tools/xcw/bin/helenos-pkg-config
===================================================================
--- tools/xcw/bin/helenos-pkg-config	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ tools/xcw/bin/helenos-pkg-config	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,5 +1,6 @@
-#!/bin/bash
+#!/usr/bin/env python3
+
 #
-# Copyright (c) 2020 Jiri Svoboda
+# Copyright (c) 2024 Vojtech Horky
 # All rights reserved.
 #
@@ -32,77 +33,84 @@
 #
 
-XCW="$(dirname "$0")"
-BUILD_ROOT="$(dirname "$(dirname "$(dirname "$XCW")")")"
-if [ -z "$EXPORT_DIR" ]; then
-	EXPORT_DIR="$BUILD_ROOT/export"
-fi
 
-INCLUDE_DIR="$EXPORT_DIR/include"
-LIB_DIR="$EXPORT_DIR/lib"
+import argparse
+import os
+import sys
 
-libmath_cflags="-I$INCLUDE_DIR/libmath"
-libmath_libs="$LIB_DIR/libmath.a"
+# Package dependencies: always list only immediate dependencies so that
+# we can get proper ordering for static libraries
+CONFIG_DEPENDENCIES = {
+    'libui': ['libdisplay', 'libcongfx', 'libgfxfont', 'libriff', 'libmemgfx', 'libconsole'],
+    'libdisplay': ['libgfx', 'libipcgfx', 'libinput'],
+    'libconsole': ['libinput', 'liboutput'],
+    'libipcfgfx': ['libgfx'],
+    'libgfximage': ['libpixconv'],
+}
 
-libui_cflags="-I$INCLUDE_DIR/libui -I$INCLUDE_DIR/libdisplay -I$INCLUDE_DIR/libgfx -I$INCLUDE_DIR/libipcgfx -I$INCLUDE_DIR/libcongfx"
-libui_libs="$LIB_DIR/libui.a $LIB_DIR/libdisplay.a $LIB_DIR/libipcgfx.a $LIB_DIR/libcongfx.a $LIB_DIR/libgfx.a $LIB_DIR/libgfxfont.a $LIB_DIR/libriff.a $LIB_DIR/libmemgfx.a"
+CONFIG_CFLAGS = {
+}
 
-libgfximage_cflags="-I$INCLUDE_DIR/libgfximage"
-libgfximage_libs="$LIB_DIR/libgfximage.a $LIB_DIR/libpixconv.a"
+CONFIG_LIBS = {
+}
 
-libhound_cflags="-I$INCLUDE_DIR/libhound"
-libhound_libs="$LIB_DIR/libhound.a"
+def get_with_dependencies(package, dependencies_first=True):
+    deps = CONFIG_DEPENDENCIES.get(package, [])
+    all_deps = [
+        i
+        for d in deps
+        for i in get_with_dependencies(d, dependencies_first)
+    ]
+    result = (all_deps if dependencies_first else []) + [package] + ([] if dependencies_first else all_deps)
+    seen = set()
+    return [
+        i
+        for i in result
+        if not (i in seen or seen.add(i))
+    ]
 
-libpcm_cflags="-I$INCLUDE_DIR/libpcm"
-libpcm_libs="$LIB_DIR/libpcm.a"
+def get_build_root():
+    dn = lambda x : os.path.dirname(x)
+    return dn(dn(dn(dn(os.path.abspath(sys.argv[0])))))
 
-libgfx_cflags="-I$INCLUDE_DIR/libgfx"
-libgfx_libs="$LIB_DIR/libgfx.a"
+def main():
+    args = argparse.ArgumentParser(description='pkg-config-like tool for HelenOS')
+    args.add_argument(
+        '--cflags',
+        dest='cflags',
+        action='store_true',
+        default=False,
+        help='Print required C compiler flags to stdout.'
+    )
+    args.add_argument(
+        '--libs',
+        dest='libs',
+        action='store_true',
+        default=False,
+        help='Print required linker flags to stdout.'
+    )
+    args.add_argument('packages', metavar='PACKAGE', nargs='+')
 
-libipcgfx_cflags="-I$INCLUDE_DIR/libipcgfx"
-libipcgfx_libs="$LIB_DIR/libipcgfx.a"
+    config = args.parse_args()
 
-libdisplay_cflags="-I$INCLUDE_DIR/libdisplay"
-libdisplay_libs="$LIB_DIR/libdisplay.a"
+    export_dir = os.getenv('EXPORT_DIR', get_build_root() + '/export')
 
-action=none
-pkg=
+    result = []
+    for package in config.packages:
+        if config.cflags:
+            for i in get_with_dependencies(package, False):
+                if i in CONFIG_CFLAGS:
+                    result.extend(CONFIG_CFLAGS[i])
+                else:
+                    result.append(f'-I{export_dir}/include/{i}')
+        if config.libs:
+            for i in get_with_dependencies(package, False):
+                if i in CONFIG_LIBS:
+                    result.extend(CONFIG_LIBS[i])
+                else:
+                    result.append(f'{export_dir}/lib/{i}.a')
 
-while [ ".$1" != . ] ; do
-	case ".$1" in
-	(.--cflags) action=cflags;;
-	(.--libs) action=libs;;
-	(.-*) echo "Uknwown option $1" >&2; exit 1;;
-	(.*)
-	    case "$1" in
-	    (libui) ;;
-	    (libgfximage) ;;
-	    (libmath) ;;
-	    (libhound) ;;
-	    (libpcm) ;;
-	    (libgfx) ;;
-	    (libipcgfx) ;;
-	    (libdisplay) ;;
-	    (*) echo "Unknown package $1" >&2; exit 1;;
-	    esac
+    print(' '.join(result))
 
-	    echo "$pkg" | grep -w "$1" >/dev/null 2>&1
-	    if [ $? -ne 0 ] ; then
-		    pkg="$pkg $1"
-	    fi;;
-	esac
-	shift 1
-done
 
-if [ ."$pkg" = . ]; then
-	echo "Package name(s) required." >&2
-	exit 1
-fi
-
-for p in $pkg ; do
-	case "$action" in
-	(cflags) eval "printf ' %s' \$${p}_cflags";;
-	(libs) eval "printf ' %s' \$${p}_libs";;
-	esac
-done
-
-echo
+if __name__ == '__main__':
+    main()
Index: uspace/app/aboutos/aboutos.c
===================================================================
--- uspace/app/aboutos/aboutos.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/aboutos/aboutos.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/aboutos/aboutos.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/aboutos/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,4 @@
+/** @addtogroup aboutos aboutos
+ * @brief About HelenOS
+ * @ingroup apps
+ */
Index: uspace/app/aboutos/meson.build
===================================================================
--- uspace/app/aboutos/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/aboutos/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/barber/barber.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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,11 +403,19 @@
 	}
 
+	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);
-	params.caption = "";
+	params.caption = "Barber Pole";
 	params.placement = ui_wnd_place_bottom_right;
 	/*
@@ -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 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/barber/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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/bdsh/cmds/modules/help/help.c
===================================================================
--- uspace/app/bdsh/cmds/modules/help/help.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/bdsh/cmds/modules/help/help.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -131,19 +131,23 @@
 	    "Don't panic!\n\n"
 
-	    "This is Bdsh, the Brain dead shell, currently "
-	    "the primary user interface to HelenOS. Bdsh allows you to enter "
+	    "This is Bdsh, the Brain dead shell, the HelenOS "
+	    "command-line interface. Bdsh allows you to enter "
 	    "commands and supports history (Up, Down arrow keys), "
 	    "line editing (Left Arrow, Right Arrow, Home, End, Backspace), "
 	    "selection (Shift + movement keys), copy and paste (Ctrl-C, "
-	    "Ctrl-V), similar to common desktop environments.\n\n"
+	    "Ctrl-V). You can also click your mouse within the input line "
+	    "to seek and use your mouse wheel to scroll through history.\n\n"
 
 	    "The most basic filesystem commands are Bdsh builtins. Type "
 	    "'help commands' [Enter] to see the list of Bdsh builtin commands. "
-	    "Other commands are external executables located in the /app and "
-	    "/srv directories. Type 'ls /app' [Enter] and 'ls /srv' [Enter] "
-	    "to see their list. You can execute an external command simply "
-	    "by entering its name (e.g. type 'tetris' [Enter]).\n\n"
-
-	    "HelenOS has virtual consoles (VCs). You can switch between "
+	    "Other commands are external executables located in the /app "
+	    "directory. Type 'ls /app' [Enter] to see their list. "
+	    "You can execute an external command simply "
+	    "by entering its name. E.g., type 'nav' [Enter] to start "
+	    "Navigator, HelenOS interactive file manager).\n\n"
+
+	    "If you are not running in GUI mode, (where you can start "
+	    "multiple Terminal windows,) HelenOS console supports "
+	    "virtual consoles (VCs). You can switch between "
 	    "these using the F1-F11 keys.\n\n"
 
Index: uspace/app/blkdump/blkdump.c
===================================================================
--- uspace/app/blkdump/blkdump.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/blkdump/blkdump.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,5 +1,5 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2011 Martin Sucha
- * Copyright (c) 2013 Jiri Svoboda
  * All rights reserved.
  *
@@ -150,5 +150,5 @@
 	}
 
-	rc = block_init(service_id, 2048);
+	rc = block_init(service_id);
 	if (rc != EOK)  {
 		printf(NAME ": Error initializing libblock.\n");
Index: uspace/app/calculator/calculator.c
===================================================================
--- uspace/app/calculator/calculator.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/calculator/calculator.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2016 Martin Decky
  * All rights reserved.
@@ -46,6 +46,7 @@
 #include <ui/entry.h>
 #include <ui/fixed.h>
+#include <ui/menu.h>
 #include <ui/menubar.h>
-#include <ui/menu.h>
+#include <ui/menudd.h>
 #include <ui/menuentry.h>
 #include <ui/pbutton.h>
@@ -186,4 +187,7 @@
 	calc_t *calc = (calc_t *) arg;
 
+	if (ui_window_def_kbd(window, event) == ui_claimed)
+		return;
+
 	if (event->type == KEY_PRESS && (event->mods & KM_CTRL) != 0) {
 		switch (event->key) {
@@ -787,5 +791,5 @@
 int main(int argc, char *argv[])
 {
-	const char *display_spec = UI_DISPLAY_DEFAULT;
+	const char *display_spec = UI_ANY_DEFAULT;
 	ui_t *ui;
 	ui_resource_t *ui_res;
@@ -893,5 +897,5 @@
 	}
 
-	rc = ui_menu_create(calc.menubar, "File", &mfile);
+	rc = ui_menu_dd_create(calc.menubar, "~F~ile", NULL, &mfile);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -899,5 +903,5 @@
 	}
 
-	rc = ui_menu_entry_create(mfile, "Exit", "Alt-F4", &mexit);
+	rc = ui_menu_entry_create(mfile, "E~x~it", "Alt-F4", &mexit);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -907,5 +911,5 @@
 	ui_menu_entry_set_cb(mexit, calc_file_exit, (void *) &calc);
 
-	rc = ui_menu_create(calc.menubar, "Edit", &medit);
+	rc = ui_menu_dd_create(calc.menubar, "~E~dit", NULL, &medit);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -913,5 +917,5 @@
 	}
 
-	rc = ui_menu_entry_create(medit, "Copy", "Ctrl-C", &mcopy);
+	rc = ui_menu_entry_create(medit, "~C~opy", "Ctrl-C", &mcopy);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -921,5 +925,5 @@
 	ui_menu_entry_set_cb(mcopy, calc_edit_copy, (void *) &calc);
 
-	rc = ui_menu_entry_create(medit, "Paste", "Ctrl-V", &mpaste);
+	rc = ui_menu_entry_create(medit, "~P~aste", "Ctrl-V", &mpaste);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
Index: uspace/app/corecfg/meson.build
===================================================================
--- uspace/app/corecfg/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/corecfg/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 #
-# Copyright (c) 2013 Jiri Svoboda
+# Copyright (c) 2023 Jiri Svoboda
 # All rights reserved.
 #
@@ -27,3 +27,4 @@
 #
 
+deps = [ 'corecfg' ]
 src = files('corecfg.c')
Index: uspace/app/disp/disp.c
===================================================================
--- uspace/app/disp/disp.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/disp/disp.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,531 @@
+/*
+ * 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 disp
+ * @{
+ */
+/** @file Display configuration utility.
+ *
+ * Configures the display service.
+ */
+
+#include <errno.h>
+#include <dispcfg.h>
+#include <io/table.h>
+#include <loc.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <str.h>
+#include <str_error.h>
+
+#define NAME  "disp"
+
+static void print_syntax(void)
+{
+	printf("%s: Display configuration utility.\n", NAME);
+	printf("Syntax:\n");
+	printf("  %s list-seat\n", NAME);
+	printf("  %s create-seat <name>\n", NAME);
+	printf("  %s delete-seat <name>\n", NAME);
+	printf("  %s assign-dev <device> <seat>\n", NAME);
+	printf("  %s unassign-dev <device>\n", NAME);
+	printf("  %s list-dev <seat>\n", NAME);
+}
+
+/** Find seat by name.
+ *
+ * @param dispcfg Display configuration
+ * @param name Seat name
+ * @param rseat_id Place to store seat ID
+ * @return EOK on success or an error code
+ */
+static errno_t seat_find_by_name(dispcfg_t *dispcfg, const char *name,
+    sysarg_t *rseat_id)
+{
+	dispcfg_seat_list_t *seat_list;
+	dispcfg_seat_info_t *sinfo;
+
+	size_t i;
+	errno_t rc;
+
+	rc = dispcfg_get_seat_list(dispcfg, &seat_list);
+	if (rc != EOK) {
+		printf(NAME ": Failed getting seat list.\n");
+		return rc;
+	}
+
+	for (i = 0; i < seat_list->nseats; i++) {
+		rc = dispcfg_get_seat_info(dispcfg, seat_list->seats[i],
+		    &sinfo);
+		if (rc != EOK)
+			continue;
+
+		if (str_cmp(sinfo->name, name) == 0) {
+			*rseat_id = seat_list->seats[i];
+			dispcfg_free_seat_info(sinfo);
+			return EOK;
+		}
+
+		dispcfg_free_seat_info(sinfo);
+	}
+
+	return ENOENT;
+}
+
+/** Create seat subcommand.
+ *
+ * @param dcfg_svc Display configuration service name
+ * @param argc Number of arguments
+ * @param argv Arguments
+ * @return EOK on success or an erro code
+ */
+static errno_t create_seat(const char *dcfg_svc, int argc, char *argv[])
+{
+	dispcfg_t *dispcfg;
+	char *seat_name;
+	sysarg_t seat_id;
+	errno_t rc;
+
+	if (argc < 1) {
+		printf(NAME ": Missing arguments.\n");
+		print_syntax();
+		return EINVAL;
+	}
+
+	if (argc > 1) {
+		printf(NAME ": Too many arguments.\n");
+		print_syntax();
+		return EINVAL;
+	}
+
+	seat_name = argv[0];
+
+	rc = dispcfg_open(dcfg_svc, NULL, NULL, &dispcfg);
+	if (rc != EOK) {
+		printf(NAME ": Failed connecting to display configuration "
+		    "service: %s.\n", str_error(rc));
+		return rc;
+	}
+
+	rc = dispcfg_seat_create(dispcfg, seat_name, &seat_id);
+	if (rc != EOK) {
+		printf(NAME ": Failed creating seat '%s' (%s)\n",
+		    seat_name, str_error(rc));
+		dispcfg_close(dispcfg);
+		return EIO;
+	}
+
+	(void)seat_id;
+	dispcfg_close(dispcfg);
+	return EOK;
+}
+
+/** Delete seat subcommand.
+ *
+ * @param dcfg_svc Display configuration service name
+ * @param argc Number of arguments
+ * @param argv Arguments
+ * @return EOK on success or an erro code
+ */
+static errno_t delete_seat(const char *dcfg_svc, int argc, char *argv[])
+{
+	dispcfg_t *dispcfg;
+	char *seat_name;
+	sysarg_t seat_id;
+	errno_t rc;
+
+	if (argc < 1) {
+		printf(NAME ": Missing arguments.\n");
+		print_syntax();
+		return EINVAL;
+	}
+
+	if (argc > 1) {
+		printf(NAME ": Too many arguments.\n");
+		print_syntax();
+		return EINVAL;
+	}
+
+	seat_name = argv[0];
+
+	rc = dispcfg_open(dcfg_svc, NULL, NULL, &dispcfg);
+	if (rc != EOK) {
+		printf(NAME ": Failed connecting to display configuration "
+		    "service: %s.\n", str_error(rc));
+		return rc;
+	}
+
+	rc = seat_find_by_name(dispcfg, seat_name, &seat_id);
+	if (rc != EOK) {
+		printf(NAME ": Seat '%s' not found.\n", seat_name);
+		dispcfg_close(dispcfg);
+		return ENOENT;
+	}
+
+	rc = dispcfg_seat_delete(dispcfg, seat_id);
+	if (rc != EOK) {
+		printf(NAME ": Failed deleting seat '%s': %s\n", seat_name,
+		    str_error(rc));
+		dispcfg_close(dispcfg);
+		return EIO;
+	}
+
+	dispcfg_close(dispcfg);
+	return EOK;
+}
+
+/** List seats subcommand.
+ *
+ * @param dcfg_svc Display configuration service name
+ * @param argc Number of arguments
+ * @param argv Arguments
+ * @return EOK on success or an erro code
+ */
+static errno_t list_seat(const char *dcfg_svc, int argc, char *argv[])
+{
+	dispcfg_t *dispcfg;
+	dispcfg_seat_list_t *seat_list;
+	dispcfg_seat_info_t *sinfo;
+	table_t *table = NULL;
+	size_t i;
+	errno_t rc;
+
+	if (argc > 0) {
+		printf(NAME ": Too many arguments.\n");
+		print_syntax();
+		return EINVAL;
+	}
+
+	rc = dispcfg_open(dcfg_svc, NULL, NULL, &dispcfg);
+	if (rc != EOK) {
+		printf(NAME ": Failed connecting to display configuration "
+		    "service: %s.\n", str_error(rc));
+		return rc;
+	}
+
+	rc = dispcfg_get_seat_list(dispcfg, &seat_list);
+	if (rc != EOK) {
+		printf(NAME ": Failed getting seat list.\n");
+		dispcfg_close(dispcfg);
+		return rc;
+	}
+
+	rc = table_create(&table);
+	if (rc != EOK) {
+		printf("Memory allocation failed.\n");
+		dispcfg_close(dispcfg);
+		goto out;
+	}
+
+	table_header_row(table);
+	table_printf(table, "Seat Name\n");
+
+	for (i = 0; i < seat_list->nseats; i++) {
+		rc = dispcfg_get_seat_info(dispcfg, seat_list->seats[i],
+		    &sinfo);
+		if (rc != EOK) {
+			printf("Failed getting properties of seat %zu.\n",
+			    (size_t)seat_list->seats[i]);
+			continue;
+		}
+
+		table_printf(table, "%s\n", sinfo->name);
+
+		dispcfg_free_seat_info(sinfo);
+	}
+
+	if (seat_list->nseats != 0) {
+		rc = table_print_out(table, stdout);
+		if (rc != EOK) {
+			printf("Error printing table.\n");
+			goto out;
+		}
+	}
+
+	rc = EOK;
+out:
+	table_destroy(table);
+	dispcfg_close(dispcfg);
+	return rc;
+}
+
+/** Assign device subcommand.
+ *
+ * @param dcfg_svc Display configuration service name
+ * @param argc Number of arguments
+ * @param argv Arguments
+ * @return EOK on success or an erro code
+ */
+static errno_t dev_assign(const char *dcfg_svc, int argc, char *argv[])
+{
+	dispcfg_t *dispcfg;
+	char *dev_name;
+	service_id_t svc_id;
+	char *seat_name;
+	sysarg_t seat_id;
+	errno_t rc;
+
+	if (argc < 2) {
+		printf(NAME ": Missing arguments.\n");
+		print_syntax();
+		return EINVAL;
+	}
+
+	if (argc > 2) {
+		printf(NAME ": Too many arguments.\n");
+		print_syntax();
+		return EINVAL;
+	}
+
+	dev_name = argv[0];
+	seat_name = argv[1];
+
+	rc = loc_service_get_id(dev_name, &svc_id, 0);
+	if (rc != EOK) {
+		printf(NAME ": Device service '%s' not found.\n",
+		    dev_name);
+		return ENOENT;
+	}
+
+	rc = dispcfg_open(dcfg_svc, NULL, NULL, &dispcfg);
+	if (rc != EOK) {
+		printf(NAME ": Failed connecting to display configuration "
+		    "service: %s.\n", str_error(rc));
+		return rc;
+	}
+
+	rc = seat_find_by_name(dispcfg, seat_name, &seat_id);
+	if (rc != EOK) {
+		printf(NAME ": Seat '%s' not found.\n", seat_name);
+		dispcfg_close(dispcfg);
+		return ENOENT;
+	}
+
+	rc = dispcfg_dev_assign(dispcfg, svc_id, seat_id);
+	if (rc != EOK) {
+		printf(NAME ": Failed assigning device '%s' to seat '%s': "
+		    "%s\n", dev_name, seat_name, str_error(rc));
+		dispcfg_close(dispcfg);
+		return EIO;
+	}
+
+	dispcfg_close(dispcfg);
+	return EOK;
+}
+
+/** Unassign device subcommand.
+ *
+ * @param dcfg_svc Display configuration service name
+ * @param argc Number of arguments
+ * @param argv Arguments
+ * @return EOK on success or an erro code
+ */
+static errno_t dev_unassign(const char *dcfg_svc, int argc, char *argv[])
+{
+	dispcfg_t *dispcfg;
+	char *dev_name;
+	service_id_t svc_id;
+	errno_t rc;
+
+	if (argc < 1) {
+		printf(NAME ": Missing arguments.\n");
+		print_syntax();
+		return EINVAL;
+	}
+
+	if (argc > 1) {
+		printf(NAME ": Too many arguments.\n");
+		print_syntax();
+		return EINVAL;
+	}
+
+	dev_name = argv[0];
+
+	rc = loc_service_get_id(dev_name, &svc_id, 0);
+	if (rc != EOK) {
+		printf(NAME ": Device service '%s' not found.\n",
+		    dev_name);
+		return ENOENT;
+	}
+
+	rc = dispcfg_open(dcfg_svc, NULL, NULL, &dispcfg);
+	if (rc != EOK) {
+		printf(NAME ": Failed connecting to display configuration "
+		    "service: %s.\n", str_error(rc));
+		return rc;
+	}
+
+	rc = dispcfg_dev_unassign(dispcfg, svc_id);
+	if (rc != EOK) {
+		printf(NAME ": Failed unassigning device '%s': %s\n",
+		    dev_name, str_error(rc));
+		dispcfg_close(dispcfg);
+		return EIO;
+	}
+
+	dispcfg_close(dispcfg);
+	return EOK;
+}
+
+/** List dev subcommand.
+ *
+ * @param dcfg_svc Display configuration service name
+ * @param argc Number of arguments
+ * @param argv Arguments
+ * @return EOK on success or an erro code
+ */
+static errno_t list_dev(const char *dcfg_svc, int argc, char *argv[])
+{
+	dispcfg_t *dispcfg;
+	char *seat_name;
+	sysarg_t seat_id;
+	dispcfg_dev_list_t *dev_list;
+	size_t i;
+	char *svc_name;
+	table_t *table = NULL;
+	errno_t rc;
+
+	if (argc < 1) {
+		printf(NAME ": Missing arguments.\n");
+		print_syntax();
+		return EINVAL;
+	}
+
+	if (argc > 1) {
+		printf(NAME ": Too many arguments.\n");
+		print_syntax();
+		return EINVAL;
+	}
+
+	seat_name = argv[0];
+
+	rc = dispcfg_open(dcfg_svc, NULL, NULL, &dispcfg);
+	if (rc != EOK) {
+		printf(NAME ": Failed connecting to display configuration "
+		    "service: %s.\n", str_error(rc));
+		return rc;
+	}
+
+	rc = seat_find_by_name(dispcfg, seat_name, &seat_id);
+	if (rc != EOK) {
+		printf(NAME ": Seat '%s' not found.\n", seat_name);
+		dispcfg_close(dispcfg);
+		return ENOENT;
+	}
+
+	rc = dispcfg_get_asgn_dev_list(dispcfg, seat_id, &dev_list);
+	if (rc != EOK) {
+		printf(NAME ": Failed getting seat list.\n");
+		dispcfg_close(dispcfg);
+		return rc;
+	}
+
+	rc = table_create(&table);
+	if (rc != EOK) {
+		printf("Memory allocation failed.\n");
+		dispcfg_free_dev_list(dev_list);
+		dispcfg_close(dispcfg);
+		return rc;
+	}
+
+	table_header_row(table);
+	table_printf(table, "Device Name\n");
+
+	for (i = 0; i < dev_list->ndevs; i++) {
+		rc = loc_service_get_name(dev_list->devs[i], &svc_name);
+		if (rc != EOK) {
+			printf("Failed getting name of service %zu\n",
+			    (size_t)dev_list->devs[i]);
+			continue;
+		}
+
+		table_printf(table, "%s\n", svc_name);
+		free(svc_name);
+	}
+
+	if (dev_list->ndevs != 0) {
+		rc = table_print_out(table, stdout);
+		if (rc != EOK) {
+			printf("Error printing table.\n");
+			table_destroy(table);
+			dispcfg_free_dev_list(dev_list);
+			dispcfg_close(dispcfg);
+			return rc;
+		}
+	}
+
+	dispcfg_close(dispcfg);
+	return EOK;
+}
+
+int main(int argc, char *argv[])
+{
+	const char *dispcfg_svc = DISPCFG_DEFAULT;
+	errno_t rc;
+
+	if (argc < 2 || str_cmp(argv[1], "-h") == 0) {
+		print_syntax();
+		return 0;
+	}
+
+	if (str_cmp(argv[1], "list-seat") == 0) {
+		rc = list_seat(dispcfg_svc, argc - 2, argv + 2);
+		if (rc != EOK)
+			return 1;
+	} else if (str_cmp(argv[1], "create-seat") == 0) {
+		rc = create_seat(dispcfg_svc, argc - 2, argv + 2);
+		if (rc != EOK)
+			return 1;
+	} else if (str_cmp(argv[1], "delete-seat") == 0) {
+		rc = delete_seat(dispcfg_svc, argc - 2, argv + 2);
+		if (rc != EOK)
+			return 1;
+	} else if (str_cmp(argv[1], "assign-dev") == 0) {
+		rc = dev_assign(dispcfg_svc, argc - 2, argv + 2);
+		if (rc != EOK)
+			return 1;
+	} else if (str_cmp(argv[1], "unassign-dev") == 0) {
+		rc = dev_unassign(dispcfg_svc, argc - 2, argv + 2);
+		if (rc != EOK)
+			return 1;
+	} else if (str_cmp(argv[1], "list-dev") == 0) {
+		rc = list_dev(dispcfg_svc, argc - 2, argv + 2);
+		if (rc != EOK)
+			return 1;
+	} else {
+		printf(NAME ": Unknown command '%s'.\n", argv[1]);
+		print_syntax();
+		return 1;
+	}
+
+	return 0;
+}
+
+/** @}
+ */
Index: uspace/app/disp/doc/doxygroups.h
===================================================================
--- uspace/app/disp/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/disp/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,4 @@
+/** @addtogroup disp disp
+ * @brief Display configuration utility
+ * @ingroup apps
+ */
Index: uspace/app/disp/meson.build
===================================================================
--- uspace/app/disp/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/disp/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,30 @@
+#
+# 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.
+#
+
+deps = [ 'dispcfg' ]
+src = files('disp.c')
Index: uspace/app/display-cfg/display-cfg.c
===================================================================
--- uspace/app/display-cfg/display-cfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/display-cfg/display-cfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,218 @@
+/*
+ * 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 display-cfg
+ * @{
+ */
+/** @file Display configuration utility (UI)
+ */
+
+#include <gfx/coord.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ui/fixed.h>
+#include <ui/resource.h>
+#include <ui/tabset.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include "display-cfg.h"
+#include "seats.h"
+
+static void wnd_close(ui_window_t *, void *);
+
+static ui_window_cb_t window_cb = {
+	.close = wnd_close
+};
+
+/** Window close button was clicked.
+ *
+ * @param window Window
+ * @param arg Argument (dcfg)
+ */
+static void wnd_close(ui_window_t *window, void *arg)
+{
+	display_cfg_t *dcfg = (display_cfg_t *) arg;
+
+	ui_quit(dcfg->ui);
+}
+
+/** Create display configuration dialog.
+ *
+ * @param display_spec Display specification
+ * @param dcfg_svc Display configuration service name or DISPCFG_DEFAULT
+ * @param rdcfg Place to store pointer to new display configuration
+ * @return EOK on success or an error code
+ */
+errno_t display_cfg_create(const char *display_spec, display_cfg_t **rdcfg)
+{
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	display_cfg_t *dcfg = NULL;
+	gfx_rect_t rect;
+	ui_resource_t *ui_res;
+	errno_t rc;
+
+	dcfg = calloc(1, sizeof(display_cfg_t));
+	if (dcfg == NULL) {
+		printf("Out of memory.\n");
+		return ENOMEM;
+	}
+
+	rc = ui_create(display_spec, &ui);
+	if (rc != EOK) {
+		printf("Error creating UI on display %s.\n", display_spec);
+		goto error;
+	}
+
+	ui_wnd_params_init(&params);
+	params.caption = "Display Configuration";
+	if (ui_is_textmode(ui)) {
+		params.rect.p0.x = 0;
+		params.rect.p0.y = 0;
+		params.rect.p1.x = 70;
+		params.rect.p1.y = 23;
+	} else {
+		params.rect.p0.x = 0;
+		params.rect.p0.y = 0;
+		params.rect.p1.x = 470;
+		params.rect.p1.y = 350;
+	}
+
+	dcfg->ui = ui;
+
+	rc = ui_window_create(ui, &params, &window);
+	if (rc != EOK) {
+		printf("Error creating window.\n");
+		goto error;
+	}
+
+	ui_window_set_cb(window, &window_cb, (void *)dcfg);
+	dcfg->window = window;
+
+	ui_res = ui_window_get_res(window);
+
+	rc = ui_fixed_create(&dcfg->fixed);
+	if (rc != EOK) {
+		printf("Error creating fixed layout.\n");
+		return rc;
+	}
+
+	rc = ui_tab_set_create(ui_res, &dcfg->tabset);
+	if (rc != EOK) {
+		printf("Error creating tab set.\n");
+		return rc;
+	}
+
+	ui_window_get_app_rect(window, &rect);
+	ui_tab_set_set_rect(dcfg->tabset, &rect);
+
+	rc = ui_fixed_add(dcfg->fixed, ui_tab_set_ctl(dcfg->tabset));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		return rc;
+	}
+
+	rc = dcfg_seats_create(dcfg, &dcfg->seats);
+	if (rc != EOK)
+		goto error;
+
+	ui_window_add(window, ui_fixed_ctl(dcfg->fixed));
+
+	*rdcfg = dcfg;
+	return EOK;
+error:
+	if (dcfg->seats != NULL)
+		dcfg_seats_destroy(dcfg->seats);
+	if (dcfg->tabset != NULL)
+		ui_tab_set_destroy(dcfg->tabset);
+	if (dcfg->fixed != NULL)
+		ui_fixed_destroy(dcfg->fixed);
+	if (dcfg->ui != NULL)
+		ui_destroy(ui);
+	free(dcfg);
+	return rc;
+}
+
+/** Open display configuration service.
+ *
+ * @param dcfg Display configuration dialog
+ * @param dcfg_svc Display configuration service name or DISPCFG_DEFAULT
+ * @return EOK on success or an error code
+ */
+errno_t display_cfg_open(display_cfg_t *dcfg, const char *dcfg_svc)
+{
+	errno_t rc;
+
+	rc = dispcfg_open(dcfg_svc, NULL, NULL, &dcfg->dispcfg);
+	if (rc != EOK) {
+		printf("Error opening display configuration service.\n");
+		goto error;
+	}
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Populate display configuration from isplay configuration service.
+ *
+ * @param dcfg Display configuration dialog
+ * @return EOK on success or an error code
+ */
+errno_t display_cfg_populate(display_cfg_t *dcfg)
+{
+	errno_t rc;
+
+	rc = dcfg_seats_populate(dcfg->seats);
+	if (rc != EOK)
+		return rc;
+
+	rc = ui_window_paint(dcfg->window);
+	if (rc != EOK) {
+		printf("Error painting window.\n");
+		return rc;
+	}
+
+	return EOK;
+}
+
+/** Destroy display configuration dialog.
+ *
+ * @param dcfg Display configuration dialog
+ */
+void display_cfg_destroy(display_cfg_t *dcfg)
+{
+	if (dcfg->dispcfg != NULL)
+		dispcfg_close(dcfg->dispcfg);
+	ui_window_destroy(dcfg->window);
+	ui_destroy(dcfg->ui);
+}
+
+/** @}
+ */
Index: uspace/app/display-cfg/display-cfg.h
===================================================================
--- uspace/app/display-cfg/display-cfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/display-cfg/display-cfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,49 @@
+/*
+ * 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 display-cfg
+ * @{
+ */
+/**
+ * @file Display configuration utility (in UI)
+ */
+
+#ifndef DISPLAY_CFG_H
+#define DISPLAY_CFG_H
+
+#include "types/display-cfg.h"
+
+extern errno_t display_cfg_create(const char *, display_cfg_t **);
+extern errno_t display_cfg_open(display_cfg_t *, const char *);
+extern errno_t display_cfg_populate(display_cfg_t *);
+extern void display_cfg_destroy(display_cfg_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/display-cfg/doc/doxygroups.h
===================================================================
--- uspace/app/display-cfg/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/display-cfg/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,4 @@
+/** @addtogroup display-cfg display-cfg
+ * @brief Display configuration utility (UI)
+ * @ingroup apps
+ */
Index: uspace/app/display-cfg/main.c
===================================================================
--- uspace/app/display-cfg/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/display-cfg/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,101 @@
+/*
+ * 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 display-cfg
+ * @{
+ */
+/** @file Display configuration utility (UI) main
+ */
+
+#include <stdio.h>
+#include <str.h>
+#include <ui/ui.h>
+#include "display-cfg.h"
+
+static void print_syntax(void)
+{
+	printf("Syntax: display-cfg [-d <display-spec>]\n");
+}
+
+int main(int argc, char *argv[])
+{
+	const char *display_spec = UI_ANY_DEFAULT;
+	display_cfg_t *dcfg;
+	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 {
+			printf("Invalid option '%s'.\n", argv[i]);
+			print_syntax();
+			return 1;
+		}
+	}
+
+	if (i < argc) {
+		print_syntax();
+		return 1;
+	}
+
+	rc = display_cfg_create(display_spec, &dcfg);
+	if (rc != EOK)
+		return 1;
+
+	rc = display_cfg_open(dcfg, DISPCFG_DEFAULT);
+	if (rc != EOK) {
+		display_cfg_destroy(dcfg);
+		printf("Error opening display configuration service '%s'.\n",
+		    DISPCFG_DEFAULT);
+		return 1;
+	}
+
+	rc = display_cfg_populate(dcfg);
+	if (rc != EOK) {
+		display_cfg_destroy(dcfg);
+		printf("Error reading display configuration.\n");
+		return 1;
+	}
+
+	ui_run(dcfg->ui);
+	display_cfg_destroy(dcfg);
+
+	return 0;
+}
+
+/** @}
+ */
Index: uspace/app/display-cfg/meson.build
===================================================================
--- uspace/app/display-cfg/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/display-cfg/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,42 @@
+#
+# 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.
+#
+
+deps = [ 'dispcfg', 'ui' ]
+src = files(
+	'display-cfg.c',
+	'main.c',
+	'seats.c'
+)
+
+test_src = files(
+	'display-cfg.c',
+	'seats.c',
+	'test/display-cfg.c',
+	'test/main.c',
+	'test/seats.c'
+)
Index: uspace/app/display-cfg/seats.c
===================================================================
--- uspace/app/display-cfg/seats.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/display-cfg/seats.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,1126 @@
+/*
+ * 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 display-cfg
+ * @{
+ */
+/** @file Seat configuration tab
+ */
+
+#include <gfx/coord.h>
+#include <loc.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <str.h>
+#include <ui/control.h>
+#include <ui/label.h>
+#include <ui/list.h>
+#include <ui/pbutton.h>
+#include <ui/promptdialog.h>
+#include <ui/resource.h>
+#include <ui/selectdialog.h>
+#include <ui/tab.h>
+#include <ui/window.h>
+#include "display-cfg.h"
+#include "seats.h"
+
+static errno_t dcfg_seats_asgn_dev_list_populate(dcfg_seats_t *);
+static errno_t dcfg_seats_avail_dev_list_populate(dcfg_seats_t *,
+    ui_select_dialog_t *);
+static dcfg_seats_entry_t *dcfg_seats_get_selected(dcfg_seats_t *);
+static void dcfg_seats_list_selected(ui_list_entry_t *, void *);
+static void dcfg_add_seat_clicked(ui_pbutton_t *, void *);
+static void dcfg_remove_seat_clicked(ui_pbutton_t *, void *);
+static void dcfg_add_device_clicked(ui_pbutton_t *, void *);
+static void dcfg_remove_device_clicked(ui_pbutton_t *, void *);
+
+/** Seat list callbacks */
+ui_list_cb_t dcfg_seats_list_cb = {
+	.selected = dcfg_seats_list_selected
+};
+
+/** Add seat button callbacks */
+ui_pbutton_cb_t dcfg_add_seat_button_cb = {
+	.clicked = dcfg_add_seat_clicked
+};
+
+/** Remove seat button callbacks */
+ui_pbutton_cb_t dcfg_remove_seat_button_cb = {
+	.clicked = dcfg_remove_seat_clicked
+};
+
+/** Add device button callbacks */
+ui_pbutton_cb_t dcfg_add_device_button_cb = {
+	.clicked = dcfg_add_device_clicked
+};
+
+/** Remove device button callbacks */
+ui_pbutton_cb_t dcfg_remove_device_button_cb = {
+	.clicked = dcfg_remove_device_clicked
+};
+
+static void add_seat_dialog_bok(ui_prompt_dialog_t *, void *, const char *);
+static void add_seat_dialog_bcancel(ui_prompt_dialog_t *, void *);
+static void add_seat_dialog_close(ui_prompt_dialog_t *, void *);
+
+/** Add seat dialog callbacks */
+ui_prompt_dialog_cb_t add_seat_dialog_cb = {
+	.bok = add_seat_dialog_bok,
+	.bcancel = add_seat_dialog_bcancel,
+	.close = add_seat_dialog_close
+};
+
+static void add_device_dialog_bok(ui_select_dialog_t *, void *, void *);
+static void add_device_dialog_bcancel(ui_select_dialog_t *, void *);
+static void add_device_dialog_close(ui_select_dialog_t *, void *);
+
+/** Add device dialog callbacks */
+ui_select_dialog_cb_t add_device_dialog_cb = {
+	.bok = add_device_dialog_bok,
+	.bcancel = add_device_dialog_bcancel,
+	.close = add_device_dialog_close
+};
+
+/** Create seat configuration tab
+ *
+ * @param dcfg Display configuration dialog
+ * @param rseats Place to store pointer to new seat configuration tab
+ * @return EOK on success or an error code
+ */
+errno_t dcfg_seats_create(display_cfg_t *dcfg, dcfg_seats_t **rseats)
+{
+	ui_resource_t *ui_res;
+	dcfg_seats_t *seats;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	ui_res = ui_window_get_res(dcfg->window);
+
+	seats = calloc(1, sizeof(dcfg_seats_t));
+	if (dcfg == NULL) {
+		printf("Out of memory.\n");
+		return ENOMEM;
+	}
+
+	seats->dcfg = dcfg;
+
+	/* 'Seats' tab */
+
+	rc = ui_tab_create(dcfg->tabset, "Seats", &seats->tab);
+	if (rc != EOK)
+		goto error;
+
+	rc = ui_fixed_create(&seats->fixed);
+	if (rc != EOK) {
+		printf("Error creating fixed layout.\n");
+		goto error;
+	}
+
+	/* 'Configured seats:' label */
+
+	rc = ui_label_create(ui_res, "Configured seats:", &seats->seats_label);
+	if (rc != EOK) {
+		printf("Error creating label.\n");
+		goto error;
+	}
+
+	if (ui_resource_is_textmode(ui_res)) {
+		rect.p0.x = 4;
+		rect.p0.y = 4;
+		rect.p1.x = 36;
+		rect.p1.y = 5;
+	} else {
+		rect.p0.x = 20;
+		rect.p0.y = 60;
+		rect.p1.x = 360;
+		rect.p1.y = 80;
+	}
+
+	ui_label_set_rect(seats->seats_label, &rect);
+
+	rc = ui_fixed_add(seats->fixed, ui_label_ctl(seats->seats_label));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	/* List of seats */
+
+	rc = ui_list_create(dcfg->window, false, &seats->seat_list);
+	if (rc != EOK) {
+		printf("Error creating list.\n");
+		goto error;
+	}
+
+	if (ui_resource_is_textmode(ui_res)) {
+		rect.p0.x = 4;
+		rect.p0.y = 5;
+		rect.p1.x = 56;
+		rect.p1.y = 10;
+	} else {
+		rect.p0.x = 20;
+		rect.p0.y = 80;
+		rect.p1.x = 360;
+		rect.p1.y = 180;
+	}
+
+	ui_list_set_rect(seats->seat_list, &rect);
+
+	rc = ui_fixed_add(seats->fixed, ui_list_ctl(seats->seat_list));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	ui_list_set_cb(seats->seat_list, &dcfg_seats_list_cb, (void *)seats);
+
+	/* 'Add...' seat button */
+
+	rc = ui_pbutton_create(ui_res, "Add...", &seats->add_seat);
+	if (rc != EOK) {
+		printf("Error creating button.\n");
+		goto error;
+	}
+
+	if (ui_resource_is_textmode(ui_res)) {
+		rect.p0.x = 58;
+		rect.p0.y = 5;
+		rect.p1.x = 68;
+		rect.p1.y = 6;
+	} else {
+		rect.p0.x = 370;
+		rect.p0.y = 80;
+		rect.p1.x = 450;
+		rect.p1.y = 105;
+	}
+
+	ui_pbutton_set_rect(seats->add_seat, &rect);
+
+	rc = ui_fixed_add(seats->fixed, ui_pbutton_ctl(seats->add_seat));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	ui_pbutton_set_cb(seats->add_seat, &dcfg_add_seat_button_cb,
+	    (void *)seats);
+
+	/* 'Remove' seat button */
+
+	rc = ui_pbutton_create(ui_res, "Remove", &seats->remove_seat);
+	if (rc != EOK) {
+		printf("Error creating button.\n");
+		goto error;
+	}
+
+	if (ui_resource_is_textmode(ui_res)) {
+		rect.p0.x = 58;
+		rect.p0.y = 7;
+		rect.p1.x = 68;
+		rect.p1.y = 8;
+	} else {
+		rect.p0.x = 370;
+		rect.p0.y = 110;
+		rect.p1.x = 450;
+		rect.p1.y = 135;
+	}
+
+	ui_pbutton_set_rect(seats->remove_seat, &rect);
+
+	rc = ui_fixed_add(seats->fixed, ui_pbutton_ctl(seats->remove_seat));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	ui_pbutton_set_cb(seats->remove_seat, &dcfg_remove_seat_button_cb,
+	    (void *)seats);
+
+	/* 'Devices assigned to seat 'xxx':' label */
+
+	rc = ui_label_create(ui_res, "Devices assigned to seat 'xxx':",
+	    &seats->devices_label);
+	if (rc != EOK) {
+		printf("Error creating label.\n");
+		goto error;
+	}
+
+	if (ui_resource_is_textmode(ui_res)) {
+		rect.p0.x = 4;
+		rect.p0.y = 11;
+		rect.p1.x = 36;
+		rect.p1.y = 12;
+	} else {
+		rect.p0.x = 20;
+		rect.p0.y = 200;
+		rect.p1.x = 360;
+		rect.p1.y = 220;
+	}
+
+	ui_label_set_rect(seats->devices_label, &rect);
+
+	rc = ui_fixed_add(seats->fixed, ui_label_ctl(seats->devices_label));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	/* List of devices */
+
+	rc = ui_list_create(dcfg->window, false, &seats->device_list);
+	if (rc != EOK) {
+		printf("Error creating list.\n");
+		goto error;
+	}
+
+	if (ui_resource_is_textmode(ui_res)) {
+		rect.p0.x = 4;
+		rect.p0.y = 12;
+		rect.p1.x = 56;
+		rect.p1.y = 17;
+	} else {
+		rect.p0.x = 20;
+		rect.p0.y = 220;
+		rect.p1.x = 360;
+		rect.p1.y = 320;
+	}
+
+	ui_list_set_rect(seats->device_list, &rect);
+
+	rc = ui_fixed_add(seats->fixed, ui_list_ctl(seats->device_list));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	/* 'Add...' device button */
+
+	rc = ui_pbutton_create(ui_res, "Add...", &seats->add_device);
+	if (rc != EOK) {
+		printf("Error creating button.\n");
+		goto error;
+	}
+
+	if (ui_resource_is_textmode(ui_res)) {
+		rect.p0.x = 58;
+		rect.p0.y = 12;
+		rect.p1.x = 68;
+		rect.p1.y = 13;
+	} else {
+		rect.p0.x = 370;
+		rect.p0.y = 220;
+		rect.p1.x = 450;
+		rect.p1.y = 245;
+	}
+
+	ui_pbutton_set_rect(seats->add_device, &rect);
+
+	rc = ui_fixed_add(seats->fixed, ui_pbutton_ctl(seats->add_device));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	ui_pbutton_set_cb(seats->add_device, &dcfg_add_device_button_cb,
+	    (void *)seats);
+
+	/* 'Remove' device button */
+
+	rc = ui_pbutton_create(ui_res, "Remove", &seats->remove_device);
+	if (rc != EOK) {
+		printf("Error creating button.\n");
+		goto error;
+	}
+
+	if (ui_resource_is_textmode(ui_res)) {
+		rect.p0.x = 58;
+		rect.p0.y = 14;
+		rect.p1.x = 68;
+		rect.p1.y = 15;
+	} else {
+		rect.p0.x = 370;
+		rect.p0.y = 250;
+		rect.p1.x = 450;
+		rect.p1.y = 275;
+	}
+
+	ui_pbutton_set_rect(seats->remove_device, &rect);
+
+	rc = ui_fixed_add(seats->fixed, ui_pbutton_ctl(seats->remove_device));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	ui_pbutton_set_cb(seats->remove_device, &dcfg_remove_device_button_cb,
+	    (void *)seats);
+
+	ui_tab_add(seats->tab, ui_fixed_ctl(seats->fixed));
+
+	*rseats = seats;
+	return EOK;
+error:
+	if (seats->remove_device != NULL)
+		ui_pbutton_destroy(seats->remove_device);
+	if (seats->add_device != NULL)
+		ui_pbutton_destroy(seats->add_device);
+	if (seats->devices_label != NULL)
+		ui_label_destroy(seats->devices_label);
+	if (seats->device_list != NULL)
+		ui_list_destroy(seats->device_list);
+	if (seats->remove_seat != NULL)
+		ui_pbutton_destroy(seats->remove_seat);
+	if (seats->add_seat != NULL)
+		ui_pbutton_destroy(seats->add_seat);
+	if (seats->seats_label != NULL)
+		ui_label_destroy(seats->seats_label);
+	if (seats->seat_list != NULL)
+		ui_list_destroy(seats->seat_list);
+	if (seats->fixed != NULL)
+		ui_fixed_destroy(seats->fixed);
+	free(dcfg);
+	return rc;
+}
+
+/** Populate seats tab with display configuration service data
+ *
+ * @param dcfg Display configuration dialog
+ * @param rseats Place to store pointer to new seat configuration tab
+ * @return EOK on success or an error code
+ */
+errno_t dcfg_seats_populate(dcfg_seats_t *seats)
+{
+	dcfg_seats_entry_t *entry;
+	errno_t rc;
+
+	rc = dcfg_seats_list_populate(seats);
+	if (rc != EOK)
+		return rc;
+
+	/*
+	 * Update "Devices assigned to seat 'xxx'" label and populate
+	 * assigned devices list.
+	 */
+	entry = dcfg_seats_get_selected(seats);
+	dcfg_seats_list_selected(entry->lentry, (void *)entry);
+	return EOK;
+}
+
+/** Destroy display configuration dialog.
+ *
+ * @param dcfg Display configuration dialog
+ */
+void dcfg_seats_destroy(dcfg_seats_t *seats)
+{
+	ui_list_entry_t *lentry;
+	dcfg_seats_entry_t *entry;
+	dcfg_devices_entry_t *dentry;
+
+	lentry = ui_list_first(seats->device_list);
+	while (lentry != NULL) {
+		dentry = (dcfg_devices_entry_t *)ui_list_entry_get_arg(lentry);
+		free(dentry->name);
+		free(dentry);
+		ui_list_entry_delete(lentry);
+		lentry = ui_list_first(seats->device_list);
+	}
+
+	lentry = ui_list_first(seats->seat_list);
+	while (lentry != NULL) {
+		entry = (dcfg_seats_entry_t *)ui_list_entry_get_arg(lentry);
+		free(entry->name);
+		free(entry);
+		ui_list_entry_delete(lentry);
+		lentry = ui_list_first(seats->seat_list);
+	}
+
+	/* This will automatically destroy all controls in the tab */
+	ui_tab_destroy(seats->tab);
+	free(seats);
+}
+
+/** Insert new entry into seats list.
+ *
+ * @param seats Seat configuration tab
+ * @param name Seat name
+ * @param seat_id Seat ID
+ * @param rentry Place to store pointer to new entry or NULL
+ * @return EOK on success or an error code
+ */
+errno_t dcfg_seats_insert(dcfg_seats_t *seats, const char *name,
+    sysarg_t seat_id, dcfg_seats_entry_t **rentry)
+{
+	dcfg_seats_entry_t *entry;
+	ui_list_entry_attr_t attr;
+	errno_t rc;
+
+	entry = calloc(1, sizeof(dcfg_seats_entry_t));
+	if (entry == NULL)
+		return ENOMEM;
+
+	entry->seats = seats;
+	entry->seat_id = seat_id;
+	entry->name = str_dup(name);
+	if (entry->name == NULL) {
+		free(entry);
+		return ENOMEM;
+	}
+
+	ui_list_entry_attr_init(&attr);
+	attr.caption = name;
+	attr.arg = (void *)entry;
+	rc = ui_list_entry_append(seats->seat_list, &attr, &entry->lentry);
+	if (rc != EOK) {
+		free(entry->name);
+		free(entry);
+		return rc;
+	}
+
+	if (rentry != NULL)
+		*rentry = entry;
+	return EOK;
+}
+
+/** Populate seat list.
+ *
+ * @param seats Seat configuration tab
+ * @return EOK on success or an error code
+ */
+errno_t dcfg_seats_list_populate(dcfg_seats_t *seats)
+{
+	size_t i;
+	dispcfg_seat_list_t *seat_list = NULL;
+	dispcfg_seat_info_t *sinfo = NULL;
+	errno_t rc;
+
+	rc = dispcfg_get_seat_list(seats->dcfg->dispcfg, &seat_list);
+	if (rc != EOK)
+		goto error;
+
+	for (i = 0; i < seat_list->nseats; i++) {
+		rc = dispcfg_get_seat_info(seats->dcfg->dispcfg,
+		    seat_list->seats[i], &sinfo);
+		if (rc != EOK)
+			goto error;
+
+		rc = dcfg_seats_insert(seats, sinfo->name, seat_list->seats[i],
+		    NULL);
+		if (rc != EOK)
+			goto error;
+
+		dispcfg_free_seat_info(sinfo);
+		sinfo = NULL;
+	}
+
+	dispcfg_free_seat_list(seat_list);
+	return EOK;
+error:
+	if (sinfo != NULL)
+		dispcfg_free_seat_info(sinfo);
+	if (seat_list != NULL)
+		dispcfg_free_seat_list(seat_list);
+	return rc;
+}
+
+/** Insert new entry into devices list.
+ *
+ * @param seats Seat configuration tab
+ * @param name Device name
+ * @param svc_id Service ID
+ * @return EOK on success or an error code
+ */
+errno_t dcfg_devices_insert(dcfg_seats_t *seats, const char *name,
+    service_id_t svc_id)
+{
+	dcfg_devices_entry_t *entry;
+	ui_list_entry_attr_t attr;
+	errno_t rc;
+
+	entry = calloc(1, sizeof(dcfg_devices_entry_t));
+	if (entry == NULL)
+		return ENOMEM;
+
+	entry->seats = seats;
+	entry->svc_id = svc_id;
+	entry->name = str_dup(name);
+	if (entry->name == NULL) {
+		free(entry);
+		return ENOMEM;
+	}
+
+	ui_list_entry_attr_init(&attr);
+	attr.caption = name;
+	attr.arg = (void *)entry;
+	rc = ui_list_entry_append(seats->device_list, &attr, &entry->lentry);
+	if (rc != EOK) {
+		free(entry->name);
+		free(entry);
+		return rc;
+	}
+
+	return EOK;
+}
+
+/** Insert new entry into available devices list.
+ *
+ * @param seats Seat configuration tab
+ * @param dialog 'Add Device' dialog
+ * @param name Device name
+ * @param svc_id Service ID
+ * @return EOK on success or an error code
+ */
+errno_t dcfg_avail_devices_insert(dcfg_seats_t *seats,
+    ui_select_dialog_t *dialog, const char *name, service_id_t svc_id)
+{
+	dcfg_devices_entry_t *entry;
+	ui_list_entry_attr_t attr;
+	errno_t rc;
+
+	entry = calloc(1, sizeof(dcfg_devices_entry_t));
+	if (entry == NULL)
+		return ENOMEM;
+
+	entry->seats = seats;
+	entry->svc_id = svc_id;
+	entry->name = str_dup(name);
+	if (entry->name == NULL) {
+		free(entry);
+		return ENOMEM;
+	}
+
+	ui_list_entry_attr_init(&attr);
+	attr.caption = name;
+	attr.arg = (void *)entry;
+	rc = ui_select_dialog_append(dialog, &attr);
+	if (rc != EOK) {
+		free(entry->name);
+		free(entry);
+		return rc;
+	}
+
+	return EOK;
+}
+
+/** Populate assigned device list.
+ *
+ * @param seats Seat configuration tab
+ * @return EOK on success or an error code
+ */
+static errno_t dcfg_seats_asgn_dev_list_populate(dcfg_seats_t *seats)
+{
+	size_t i;
+	dispcfg_dev_list_t *dev_list = NULL;
+	char *svc_name = NULL;
+	dcfg_seats_entry_t *seats_entry;
+	sysarg_t seat_id;
+	errno_t rc;
+
+	/* Get active seat entry */
+	seats_entry = dcfg_seats_get_selected(seats);
+	seat_id = seats_entry->seat_id;
+
+	rc = dispcfg_get_asgn_dev_list(seats->dcfg->dispcfg, seat_id, &dev_list);
+	if (rc != EOK)
+		goto error;
+
+	for (i = 0; i < dev_list->ndevs; i++) {
+		rc = loc_service_get_name(dev_list->devs[i], &svc_name);
+		if (rc != EOK)
+			goto error;
+
+		rc = dcfg_devices_insert(seats, svc_name, dev_list->devs[i]);
+		if (rc != EOK)
+			goto error;
+
+		free(svc_name);
+		svc_name = NULL;
+	}
+
+	dispcfg_free_dev_list(dev_list);
+	return EOK;
+error:
+	if (svc_name != NULL)
+		free(svc_name);
+	if (dev_list != NULL)
+		dispcfg_free_dev_list(dev_list);
+	return rc;
+}
+
+/** Populate available device list in 'Add Device' dialog.
+ *
+ * @param seats Seat configuration tab
+ * @param dialog 'Add Device' dialog
+ * @return EOK on success or an error code
+ */
+static errno_t dcfg_seats_avail_dev_list_populate(dcfg_seats_t *seats,
+    ui_select_dialog_t *dialog)
+{
+	size_t i, j, k;
+	category_id_t cat_id;
+	service_id_t *kbd_svcs = NULL;
+	size_t nkbd_svcs;
+	service_id_t *mouse_svcs = NULL;
+	size_t nmouse_svcs;
+	dispcfg_seat_list_t *seat_list = NULL;
+	dispcfg_dev_list_t *adev_list;
+	char *svc_name = NULL;
+	errno_t rc;
+
+	/* Get list of keyboard devices */
+
+	rc = loc_category_get_id("keyboard", &cat_id, 0);
+	if (rc != EOK) {
+		printf("Error getting category ID.\n");
+		goto error;
+	}
+
+	rc = loc_category_get_svcs(cat_id, &kbd_svcs, &nkbd_svcs);
+	if (rc != EOK) {
+		printf("Error getting service list.\n");
+		goto error;
+	}
+
+	/* Get list of mouse devices */
+
+	rc = loc_category_get_id("mouse", &cat_id, 0);
+	if (rc != EOK) {
+		printf("Error getting category ID.\n");
+		goto error;
+	}
+
+	rc = loc_category_get_svcs(cat_id, &mouse_svcs, &nmouse_svcs);
+	if (rc != EOK) {
+		printf("Error getting service list.\n");
+		goto error;
+	}
+
+	/* Filter out assigned devices */
+	rc = dispcfg_get_seat_list(seats->dcfg->dispcfg, &seat_list);
+	if (rc != EOK) {
+		printf("Error getting seat list.\n");
+		goto error;
+	}
+
+	for (i = 0; i < seat_list->nseats; i++) {
+		rc = dispcfg_get_asgn_dev_list(seats->dcfg->dispcfg,
+		    seat_list->seats[i], &adev_list);
+		if (rc != EOK) {
+			printf("Error getting device list.\n");
+			goto error;
+		}
+
+		for (j = 0; j < adev_list->ndevs; j++) {
+			/* Filter out assigned keyboard devices */
+			for (k = 0; k < nkbd_svcs; k++) {
+				if (kbd_svcs[k] == adev_list->devs[j])
+					kbd_svcs[k] = 0;
+			}
+
+			/* Filter out assigned mouse devices */
+			for (k = 0; k < nmouse_svcs; k++) {
+				if (mouse_svcs[k] == adev_list->devs[j])
+					mouse_svcs[k] = 0;
+			}
+		}
+
+		dispcfg_free_dev_list(adev_list);
+	}
+
+	dispcfg_free_seat_list(seat_list);
+	seat_list = NULL;
+
+	/* Add keyboard devices */
+
+	for (i = 0; i < nkbd_svcs; i++) {
+		if (kbd_svcs[i] == 0)
+			continue;
+
+		rc = loc_service_get_name(kbd_svcs[i], &svc_name);
+		if (rc != EOK)
+			goto error;
+
+		rc = dcfg_avail_devices_insert(seats, dialog, svc_name,
+		    kbd_svcs[i]);
+		if (rc != EOK)
+			goto error;
+
+		free(svc_name);
+		svc_name = NULL;
+	}
+
+	/* Add mouse devices */
+
+	for (i = 0; i < nmouse_svcs; i++) {
+		if (mouse_svcs[i] == 0)
+			continue;
+
+		rc = loc_service_get_name(mouse_svcs[i], &svc_name);
+		if (rc != EOK)
+			goto error;
+
+		rc = dcfg_avail_devices_insert(seats, dialog, svc_name,
+		    mouse_svcs[i]);
+		if (rc != EOK)
+			goto error;
+
+		free(svc_name);
+		svc_name = NULL;
+	}
+
+	free(kbd_svcs);
+	free(mouse_svcs);
+	return EOK;
+error:
+	if (svc_name != NULL)
+		free(svc_name);
+	if (seat_list != NULL)
+		dispcfg_free_seat_list(seat_list);
+	if (kbd_svcs != NULL)
+		free(kbd_svcs);
+	if (mouse_svcs != NULL)
+		free(mouse_svcs);
+	return rc;
+}
+
+/** Get selected seat entry.
+ *
+ * @param seats Seat configuration tab
+ * @return Selected entry
+ */
+static dcfg_seats_entry_t *dcfg_seats_get_selected(dcfg_seats_t *seats)
+{
+	ui_list_entry_t *lentry;
+
+	lentry = ui_list_get_cursor(seats->seat_list);
+	return (dcfg_seats_entry_t *)ui_list_entry_get_arg(lentry);
+}
+
+/** Get selected device entry.
+ *
+ * @param seats Seat configuration tab
+ * @return Selected entry
+ */
+static dcfg_devices_entry_t *dcfg_devices_get_selected(dcfg_seats_t *seats)
+{
+	ui_list_entry_t *lentry;
+
+	lentry = ui_list_get_cursor(seats->device_list);
+	return (dcfg_devices_entry_t *)ui_list_entry_get_arg(lentry);
+}
+
+/** Entry in seats list is selected.
+ *
+ * @param lentry UI list entry
+ * @param arg Argument (dcfg_seats_entry_t *)
+ */
+static void dcfg_seats_list_selected(ui_list_entry_t *lentry, void *arg)
+{
+	dcfg_seats_entry_t *entry = (dcfg_seats_entry_t *)arg;
+	dcfg_devices_entry_t *dentry;
+	ui_list_entry_t *le;
+	char *caption;
+	errno_t rc;
+	int rv;
+
+	(void) lentry;
+
+	/* Update 'Devices assigned to seat 'xxx':' label */
+
+	rv = asprintf(&caption, "Devices assigned to seat '%s':", entry->name);
+	if (rv < 0) {
+		printf("Out of memory.\n");
+		return;
+	}
+
+	rc = ui_label_set_text(entry->seats->devices_label, caption);
+	if (rc != EOK) {
+		printf("Error setting label.\n");
+		return;
+	}
+
+	free(caption);
+
+	(void) ui_control_paint(ui_label_ctl(entry->seats->devices_label));
+
+	/* Clear device list */
+	le = ui_list_first(entry->seats->device_list);
+	while (le != NULL) {
+		dentry = (dcfg_devices_entry_t *)ui_list_entry_get_arg(le);
+		free(dentry->name);
+		free(dentry);
+		ui_list_entry_delete(le);
+		le = ui_list_first(entry->seats->device_list);
+	}
+
+	/* Re-populate it */
+	(void) dcfg_seats_asgn_dev_list_populate(entry->seats);
+	(void) ui_control_paint(ui_list_ctl(entry->seats->device_list));
+}
+
+/** "Add' seat button clicked.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (dcfg_seats_entry_t *)
+ */
+static void dcfg_add_seat_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	dcfg_seats_t *seats = (dcfg_seats_t *)arg;
+	ui_prompt_dialog_params_t pdparams;
+	errno_t rc;
+
+	ui_prompt_dialog_params_init(&pdparams);
+	pdparams.caption = "Add Seat";
+	pdparams.prompt = "New Seat Name";
+
+	rc = ui_prompt_dialog_create(seats->dcfg->ui, &pdparams,
+	    &seats->add_seat_dlg);
+	if (rc != EOK)
+		printf("Error creating dialog.\n");
+
+	ui_prompt_dialog_set_cb(seats->add_seat_dlg, &add_seat_dialog_cb,
+	    (void *)seats);
+}
+
+/** "Remove' seat button clicked.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (dcfg_seats_entry_t *)
+ */
+static void dcfg_remove_seat_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	dcfg_seats_t *seats = (dcfg_seats_t *)arg;
+	dcfg_seats_entry_t *entry;
+	errno_t rc;
+
+	(void)pbutton;
+	entry = dcfg_seats_get_selected(seats);
+
+	rc = dispcfg_seat_delete(seats->dcfg->dispcfg, entry->seat_id);
+	if (rc != EOK) {
+		/*
+		 * EBUSY is returned when we attempt to delete the last
+		 * seat. No need to complain about it.
+		 */
+		if (rc == EBUSY)
+			return;
+
+		printf("Error removing seat '%s'.\n", entry->name);
+		return;
+	}
+
+	ui_list_entry_delete(entry->lentry);
+	free(entry->name);
+	free(entry);
+
+	(void) ui_control_paint(ui_list_ctl(seats->seat_list));
+
+	/* Since selected seat changed we need to update device list */
+	entry = dcfg_seats_get_selected(seats);
+	dcfg_seats_list_selected(entry->lentry, (void *)entry);
+}
+
+/** Add seat dialog OK button was pressed.
+ *
+ * @param dialog Add seat dialog
+ * @param arg Argument (dcfg_seats_t *)
+ * @param text Submitted text
+ */
+void add_seat_dialog_bok(ui_prompt_dialog_t *dialog, void *arg,
+    const char *text)
+{
+	dcfg_seats_t *seats = (dcfg_seats_t *)arg;
+	sysarg_t seat_id;
+	dcfg_seats_entry_t *entry;
+	errno_t rc;
+
+	seats->add_seat_dlg = NULL;
+	ui_prompt_dialog_destroy(dialog);
+
+	rc = dispcfg_seat_create(seats->dcfg->dispcfg, text, &seat_id);
+	if (rc != EOK) {
+		printf("Error creating seat '%s'.\n", text);
+		return;
+	}
+
+	rc = dcfg_seats_insert(seats, text, seat_id, &entry);
+	if (rc != EOK)
+		return;
+
+	(void) ui_control_paint(ui_list_ctl(seats->seat_list));
+
+	/* Select new seat and update device list */
+	ui_list_set_cursor(seats->seat_list, entry->lentry);
+	dcfg_seats_list_selected(entry->lentry, (void *)entry);
+}
+
+/** Add seat dialog Cancel button was pressed.
+ *
+ * @param dialog Add seat dialog
+ * @param arg Argument (dcfg_seats_t *)
+ */
+void add_seat_dialog_bcancel(ui_prompt_dialog_t *dialog, void *arg)
+{
+	dcfg_seats_t *seats = (dcfg_seats_t *)arg;
+
+	seats->add_seat_dlg = NULL;
+	ui_prompt_dialog_destroy(dialog);
+}
+
+/** Add seat dialog close request.
+ *
+ * @param dialog Add seat dialog
+ * @param arg Argument (dcfg_seats_t *)
+ */
+void add_seat_dialog_close(ui_prompt_dialog_t *dialog, void *arg)
+{
+	dcfg_seats_t *seats = (dcfg_seats_t *)arg;
+
+	seats->add_seat_dlg = NULL;
+	ui_prompt_dialog_destroy(dialog);
+}
+
+/** "Add' device button clicked.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (dcfg_seats_entry_t *)
+ */
+static void dcfg_add_device_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	dcfg_seats_t *seats = (dcfg_seats_t *)arg;
+	ui_select_dialog_params_t sdparams;
+	errno_t rc;
+
+	ui_select_dialog_params_init(&sdparams);
+	sdparams.caption = "Add Device";
+	sdparams.prompt = "Device Name";
+
+	rc = ui_select_dialog_create(seats->dcfg->ui, &sdparams,
+	    &seats->add_device_dlg);
+	if (rc != EOK)
+		printf("Error creating dialog.\n");
+
+	ui_select_dialog_set_cb(seats->add_device_dlg, &add_device_dialog_cb,
+	    (void *)seats);
+
+	(void) dcfg_seats_avail_dev_list_populate(seats,
+	    seats->add_device_dlg);
+	ui_select_dialog_paint(seats->add_device_dlg);
+}
+
+/** "Remove' device button clicked.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (dcfg_seats_entry_t *)
+ */
+static void dcfg_remove_device_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	dcfg_seats_t *seats = (dcfg_seats_t *)arg;
+	dcfg_devices_entry_t *entry;
+	errno_t rc;
+
+	(void)pbutton;
+	entry = dcfg_devices_get_selected(seats);
+
+	rc = dispcfg_dev_unassign(seats->dcfg->dispcfg, entry->svc_id);
+	if (rc != EOK) {
+		printf("Error removing device '%s'.\n", entry->name);
+		return;
+	}
+
+	ui_list_entry_delete(entry->lentry);
+	free(entry->name);
+	free(entry);
+
+	(void) ui_control_paint(ui_list_ctl(seats->device_list));
+}
+
+/** Add device dialog OK button was pressed.
+ *
+ * @param dialog Add device dialog
+ * @param arg Argument (dcfg_seats_t *)
+ * @param text Submitted text
+ */
+void add_device_dialog_bok(ui_select_dialog_t *dialog, void *arg,
+    void *earg)
+{
+	dcfg_seats_t *seats = (dcfg_seats_t *)arg;
+	dcfg_devices_entry_t *entry;
+	dcfg_seats_entry_t *seat;
+	errno_t rc;
+
+	seat = dcfg_seats_get_selected(seats);
+	entry = (dcfg_devices_entry_t *)earg;
+
+	seats->add_device_dlg = NULL;
+	ui_select_dialog_destroy(dialog);
+
+	rc = dispcfg_dev_assign(seats->dcfg->dispcfg, entry->svc_id,
+	    seat->seat_id);
+	if (rc != EOK) {
+		printf("Error assigning device '%s' seat '%s'.\n",
+		    entry->name, seat->name);
+		return;
+	}
+
+	rc = dcfg_devices_insert(seats, entry->name, entry->svc_id);
+	if (rc != EOK) {
+		printf("Error inserting device to list.\n");
+		return;
+	}
+
+	(void) ui_control_paint(ui_list_ctl(seats->device_list));
+}
+
+/** Add device dialog Cancel button was pressed.
+ *
+ * @param dialog Add device dialog
+ * @param arg Argument (dcfg_seats_t *)
+ */
+void add_device_dialog_bcancel(ui_select_dialog_t *dialog, void *arg)
+{
+	dcfg_seats_t *seats = (dcfg_seats_t *)arg;
+
+	seats->add_device_dlg = NULL;
+	ui_select_dialog_destroy(dialog);
+}
+
+/** Add device dialog close request.
+ *
+ * @param dialog Add device dialog
+ * @param arg Argument (dcfg_seats_t *)
+ */
+void add_device_dialog_close(ui_select_dialog_t *dialog, void *arg)
+{
+	dcfg_seats_t *seats = (dcfg_seats_t *)arg;
+
+	seats->add_device_dlg = NULL;
+	ui_select_dialog_destroy(dialog);
+}
+
+/** @}
+ */
Index: uspace/app/display-cfg/seats.h
===================================================================
--- uspace/app/display-cfg/seats.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/display-cfg/seats.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,56 @@
+/*
+ * 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 display-cfg
+ * @{
+ */
+/**
+ * @file Seat configuration tab
+ */
+
+#ifndef SEATS_H
+#define SEATS_H
+
+#include "types/display-cfg.h"
+#include "types/seats.h"
+
+extern errno_t dcfg_seats_create(display_cfg_t *, dcfg_seats_t **);
+extern errno_t dcfg_seats_populate(dcfg_seats_t *);
+extern void dcfg_seats_destroy(dcfg_seats_t *);
+extern errno_t dcfg_seats_insert(dcfg_seats_t *, const char *, sysarg_t,
+    dcfg_seats_entry_t **);
+extern errno_t dcfg_devices_insert(dcfg_seats_t *, const char *,
+    service_id_t);
+extern errno_t dcfg_avail_devices_insert(dcfg_seats_t *seats,
+    ui_select_dialog_t *dialog, const char *name, service_id_t svc_id);
+extern errno_t dcfg_seats_list_populate(dcfg_seats_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/display-cfg/test/display-cfg.c
===================================================================
--- uspace/app/display-cfg/test/display-cfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/display-cfg/test/display-cfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <pcut/pcut.h>
+#include <ui/ui.h>
+#include "../display-cfg.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(display_cfg);
+
+/** Test display_cfg_create() and display_cfg_destroy() */
+PCUT_TEST(create_destroy)
+{
+	display_cfg_t *dcfg;
+	errno_t rc;
+
+	rc = display_cfg_create(UI_DISPLAY_NULL, &dcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_cfg_destroy(dcfg);
+}
+
+PCUT_EXPORT(display_cfg);
Index: uspace/app/display-cfg/test/main.c
===================================================================
--- uspace/app/display-cfg/test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/display-cfg/test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+#include <pcut/pcut.h>
+
+PCUT_INIT;
+
+PCUT_IMPORT(display_cfg);
+PCUT_IMPORT(seats);
+
+PCUT_MAIN();
Index: uspace/app/display-cfg/test/seats.c
===================================================================
--- uspace/app/display-cfg/test/seats.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/display-cfg/test/seats.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,274 @@
+/*
+ * 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.
+ */
+
+#include <async.h>
+#include <dispcfg.h>
+#include <dispcfg_srv.h>
+#include <errno.h>
+#include <pcut/pcut.h>
+#include <str.h>
+#include <testdc.h>
+#include "../display-cfg.h"
+#include "../seats.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(seats);
+
+static const char *test_dispcfg_server = "test-dispcfg";
+static const char *test_dispcfg_svc = "test/dispcfg";
+
+/** Test dcfg_seats_create() and dcfg_seats_destroy() */
+PCUT_TEST(create_destroy)
+{
+	display_cfg_t *dcfg;
+	dcfg_seats_t *seats;
+	errno_t rc;
+
+	rc = display_cfg_create(UI_DISPLAY_NULL, &dcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dcfg_seats_create(dcfg, &seats);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	dcfg_seats_destroy(seats);
+	display_cfg_destroy(dcfg);
+}
+
+/** dcfg_seats_insert() inserts an entry into the seat list */
+PCUT_TEST(seats_insert)
+{
+	display_cfg_t *dcfg;
+	dcfg_seats_t *seats;
+	dcfg_seats_entry_t *entry = NULL;
+	errno_t rc;
+
+	rc = display_cfg_create(UI_DISPLAY_NULL, &dcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dcfg_seats_create(dcfg, &seats);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dcfg_seats_insert(seats, "Alice", 42, &entry);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	PCUT_ASSERT_STR_EQUALS("Alice", entry->name);
+	PCUT_ASSERT_INT_EQUALS(42, entry->seat_id);
+
+	dcfg_seats_destroy(seats);
+	display_cfg_destroy(dcfg);
+}
+
+/** dcfg_seats_list_populate() populates seat list */
+PCUT_TEST(seats_list_populate)
+{
+	display_cfg_t *dcfg;
+	dcfg_seats_t *seats;
+	errno_t rc;
+	service_id_t sid;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = display_cfg_create(UI_DISPLAY_NULL, &dcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = display_cfg_open(dcfg, test_dispcfg_svc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dcfg_seats_create(dcfg, &seats);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/*
+	 * dcfg_seat_list_populate() calls dispcfg_get_seat_list()
+	 * and dispcfg_get_seat_info()
+	 */
+	resp.rc = EOK;
+	resp.get_seat_list_rlist = calloc(1, sizeof(dispcfg_seat_list_t));
+	PCUT_ASSERT_NOT_NULL(resp.get_seat_list_rlist);
+	resp.get_seat_list_rlist->nseats = 1;
+	resp.get_seat_list_rlist->seats = calloc(1, sizeof(sysarg_t));
+	PCUT_ASSERT_NOT_NULL(resp.get_seat_list_rlist->seats);
+	resp.get_seat_list_rlist->seats[0] = 42;
+
+	resp.get_seat_info_rinfo = calloc(1, sizeof(dispcfg_seat_info_t));
+	PCUT_ASSERT_NOT_NULL(resp.get_seat_info_rinfo);
+	resp.get_seat_info_rinfo->name = str_dup("Alice");
+	PCUT_ASSERT_NOT_NULL(resp.get_seat_info_rinfo->name);
+
+	rc = dcfg_seats_list_populate(seats);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	dcfg_seats_destroy(seats);
+	display_cfg_destroy(dcfg);
+
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** dcfg_devices_insert() inserts an entry into the device list */
+PCUT_TEST(devices_insert)
+{
+	display_cfg_t *dcfg;
+	dcfg_seats_t *seats;
+	ui_list_entry_t *lentry;
+	dcfg_devices_entry_t *entry;
+	errno_t rc;
+
+	rc = display_cfg_create(UI_DISPLAY_NULL, &dcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dcfg_seats_create(dcfg, &seats);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dcfg_devices_insert(seats, "mydevice", 42);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	lentry = ui_list_first(seats->device_list);
+	PCUT_ASSERT_NOT_NULL(lentry);
+	entry = (dcfg_devices_entry_t *)ui_list_entry_get_arg(lentry);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	PCUT_ASSERT_STR_EQUALS("mydevice", entry->name);
+	PCUT_ASSERT_INT_EQUALS(42, entry->svc_id);
+
+	dcfg_seats_destroy(seats);
+	display_cfg_destroy(dcfg);
+}
+
+/** dcfg_avail_devices_insert() inserts entry into available devices list */
+PCUT_TEST(avail_devices_insert)
+{
+	display_cfg_t *dcfg;
+	dcfg_seats_t *seats;
+	ui_list_entry_t *lentry;
+	dcfg_devices_entry_t *entry;
+	ui_select_dialog_t *dialog;
+	ui_select_dialog_params_t sdparams;
+	errno_t rc;
+
+	rc = display_cfg_create(UI_DISPLAY_NULL, &dcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dcfg_seats_create(dcfg, &seats);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_select_dialog_params_init(&sdparams);
+	sdparams.caption = "Dialog";
+	sdparams.prompt = "Select";
+
+	rc = ui_select_dialog_create(seats->dcfg->ui, &sdparams, &dialog);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dcfg_avail_devices_insert(seats, dialog, "mydevice", 42);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	lentry = ui_list_first(ui_select_dialog_list(dialog));
+	PCUT_ASSERT_NOT_NULL(lentry);
+	entry = (dcfg_devices_entry_t *)ui_list_entry_get_arg(lentry);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	PCUT_ASSERT_STR_EQUALS("mydevice", entry->name);
+	PCUT_ASSERT_INT_EQUALS(42, entry->svc_id);
+
+	ui_select_dialog_destroy(dialog);
+	dcfg_seats_destroy(seats);
+	display_cfg_destroy(dcfg);
+}
+
+PCUT_TEST(asgn_dev_list_populate)
+{
+}
+
+PCUT_TEST(avail_dev_list_populate)
+{
+}
+
+PCUT_TEST(seats_get_selected)
+{
+}
+
+PCUT_TEST(devices_get_selected)
+{
+}
+
+PCUT_TEST(seats_list_selected)
+{
+}
+
+PCUT_TEST(add_seat_clicked)
+{
+}
+
+PCUT_TEST(remove_seat_clicked)
+{
+}
+
+PCUT_TEST(add_seat_dialog_bok)
+{
+}
+
+PCUT_TEST(add_seat_dialog_bcancel)
+{
+}
+
+PCUT_TEST(add_seat_dialog_close)
+{
+}
+
+PCUT_TEST(add_device_clicked)
+{
+}
+
+PCUT_TEST(remove_device_clicked)
+{
+}
+
+PCUT_TEST(add_device_dialog_bok)
+{
+}
+
+PCUT_TEST(add_device_dialog_bcancel)
+{
+}
+
+PCUT_TEST(add_device_dialog_close)
+{
+}
+
+PCUT_EXPORT(seats);
Index: uspace/app/display-cfg/types/display-cfg.h
===================================================================
--- uspace/app/display-cfg/types/display-cfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/display-cfg/types/display-cfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,65 @@
+/*
+ * 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 display-cfg
+ * @{
+ */
+/**
+ * @file Display configuration utility (UI) types
+ */
+
+#ifndef TYPES_DISPLAY_CFG_H
+#define TYPES_DISPLAY_CFG_H
+
+#include <dispcfg.h>
+#include <ui/fixed.h>
+#include <ui/label.h>
+#include <ui/tabset.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+
+/** Display configuration utility (UI) */
+typedef struct display_cfg {
+	/** Display configuration session */
+	dispcfg_t *dispcfg;
+	/** UI */
+	ui_t *ui;
+	/** Containing window */
+	ui_window_t *window;
+	/** Fixed layout */
+	ui_fixed_t *fixed;
+	/** Tab set */
+	ui_tab_set_t *tabset;
+	/** Seat configuration tab */
+	struct dcfg_seats *seats;
+} display_cfg_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/display-cfg/types/seats.h
===================================================================
--- uspace/app/display-cfg/types/seats.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/display-cfg/types/seats.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,106 @@
+/*
+ * 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 display-cfg
+ * @{
+ */
+/**
+ * @file Seat configuration tab
+ */
+
+#ifndef TYPES_SEATS_H
+#define TYPES_SEATS_H
+
+#include <loc.h>
+#include <types/common.h>
+#include <ui/fixed.h>
+#include <ui/label.h>
+#include <ui/list.h>
+#include <ui/pbutton.h>
+#include <ui/promptdialog.h>
+#include <ui/selectdialog.h>
+#include <ui/tab.h>
+
+/** Seat configuration tab */
+typedef struct dcfg_seats {
+	/** Containing display configuration */
+	struct display_cfg *dcfg;
+	/** UI tab */
+	ui_tab_t *tab;
+	/** Fixed layout */
+	ui_fixed_t *fixed;
+	/** 'Configured seats' label */
+	ui_label_t *seats_label;
+	/** List of configured seats */
+	ui_list_t *seat_list;
+	/** Add seat button */
+	ui_pbutton_t *add_seat;
+	/** Remove seat button */
+	ui_pbutton_t *remove_seat;
+	/** Add seat dialog */
+	ui_prompt_dialog_t *add_seat_dlg;
+	/** 'Devices assigned to xxx' label */
+	ui_label_t *devices_label;
+	/** List of assigned devices */
+	ui_list_t *device_list;
+	/** Add device button */
+	ui_pbutton_t *add_device;
+	/** Remove device button */
+	ui_pbutton_t *remove_device;
+	/** Add device dialog */
+	ui_select_dialog_t *add_device_dlg;
+} dcfg_seats_t;
+
+/** Entry in seat list */
+typedef struct {
+	/** Containing seat configuration tab */
+	struct dcfg_seats *seats;
+	/** List entry */
+	ui_list_entry_t *lentry;
+	/** Seat ID */
+	sysarg_t seat_id;
+	/** Seat name */
+	char *name;
+} dcfg_seats_entry_t;
+
+/** Entry in device list */
+typedef struct {
+	/** Containing seat configuration tab */
+	struct dcfg_seats *seats;
+	/** List entry */
+	ui_list_entry_t *lentry;
+	/** Service ID */
+	service_id_t svc_id;
+	/** Device name */
+	char *name;
+} dcfg_devices_entry_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/dltest/dltest.c
===================================================================
--- uspace/app/dltest/dltest.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/dltest/dltest.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2016 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -571,4 +571,29 @@
 #ifdef DLTEST_LINKED
 
+/** Test if we can read the correct value of a public pointer variable.
+ *
+ * dl_public_ptr_var is initialized in libdltest to point to dl_public_var.
+ * This is done using a relocation. The main program (unless compiled with
+ * PIC or PIE) will contain a copy of dl_public_ptr_var. This needs
+ * to be copied using a COPY relocation. The relocations in the main
+ * program need to be processed after the relocations in the shared
+ * libraries (so that we copy the correct value).
+ */
+static bool test_public_ptr_var(void)
+{
+	int *ptr;
+
+	printf("Read dl_public_ptr_var directly...\n");
+	ptr = dl_public_ptr_var;
+
+	if (ptr != &dl_public_var) {
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
 /** Test directly calling function that returns a constant */
 static bool test_lnk_dl_get_constant(void)
@@ -920,4 +945,5 @@
 
 #ifndef STATIC_EXE
+
 	if (!test_dlfcn_dl_get_private_fib_var())
 		return 1;
@@ -974,4 +1000,7 @@
 
 	if (!test_lnk_read_public_uvar())
+		return 1;
+
+	if (!test_public_ptr_var())
 		return 1;
 
Index: uspace/app/edit/edit.c
===================================================================
--- uspace/app/edit/edit.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/edit/edit.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2012 Martin Sucha
  * All rights reserved.
@@ -60,4 +60,5 @@
 #include <ui/menu.h>
 #include <ui/menubar.h>
+#include <ui/menudd.h>
 #include <ui/menuentry.h>
 #include <ui/promptdialog.h>
@@ -237,9 +238,21 @@
 
 static void edit_wnd_close(ui_window_t *, void *);
+static void edit_wnd_focus(ui_window_t *, void *, unsigned);
 static void edit_wnd_kbd_event(ui_window_t *, void *, kbd_event_t *);
+static void edit_wnd_unfocus(ui_window_t *, void *, unsigned);
 
 static ui_window_cb_t edit_window_cb = {
 	.close = edit_wnd_close,
-	.kbd = edit_wnd_kbd_event
+	.focus = edit_wnd_focus,
+	.kbd = edit_wnd_kbd_event,
+	.unfocus = edit_wnd_unfocus
+};
+
+static void edit_menubar_activate(ui_menu_bar_t *, void *);
+static void edit_menubar_deactivate(ui_menu_bar_t *, void *);
+
+static ui_menu_bar_cb_t edit_menubar_cb = {
+	.activate = edit_menubar_activate,
+	.deactivate = edit_menubar_deactivate
 };
 
@@ -430,5 +443,7 @@
 	}
 
-	rc = ui_menu_create(edit->menubar, "File", &mfile);
+	ui_menu_bar_set_cb(edit->menubar, &edit_menubar_cb, (void *) edit);
+
+	rc = ui_menu_dd_create(edit->menubar, "~F~ile", NULL, &mfile);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -436,5 +451,5 @@
 	}
 
-	rc = ui_menu_entry_create(mfile, "Save", "Ctrl-S", &msave);
+	rc = ui_menu_entry_create(mfile, "~S~ave", "Ctrl-S", &msave);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -444,5 +459,5 @@
 	ui_menu_entry_set_cb(msave, edit_file_save, (void *) edit);
 
-	rc = ui_menu_entry_create(mfile, "Save As", "Ctrl-E", &msaveas);
+	rc = ui_menu_entry_create(mfile, "Save ~A~s", "Ctrl-E", &msaveas);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -458,5 +473,5 @@
 	}
 
-	rc = ui_menu_entry_create(mfile, "Exit", "Ctrl-Q", &mexit);
+	rc = ui_menu_entry_create(mfile, "E~x~it", "Ctrl-Q", &mexit);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -466,5 +481,5 @@
 	ui_menu_entry_set_cb(mexit, edit_file_exit, (void *) edit);
 
-	rc = ui_menu_create(edit->menubar, "Edit", &medit);
+	rc = ui_menu_dd_create(edit->menubar, "~E~dit", NULL, &medit);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -472,5 +487,5 @@
 	}
 
-	rc = ui_menu_entry_create(medit, "Cut", "Ctrl-X", &mcut);
+	rc = ui_menu_entry_create(medit, "Cu~t~", "Ctrl-X", &mcut);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -480,5 +495,5 @@
 	ui_menu_entry_set_cb(mcut, edit_edit_cut, (void *) edit);
 
-	rc = ui_menu_entry_create(medit, "Copy", "Ctrl-C", &mcopy);
+	rc = ui_menu_entry_create(medit, "~C~opy", "Ctrl-C", &mcopy);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -488,5 +503,5 @@
 	ui_menu_entry_set_cb(mcopy, edit_edit_copy, (void *) edit);
 
-	rc = ui_menu_entry_create(medit, "Paste", "Ctrl-V", &mpaste);
+	rc = ui_menu_entry_create(medit, "~P~aste", "Ctrl-V", &mpaste);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -496,5 +511,5 @@
 	ui_menu_entry_set_cb(mpaste, edit_edit_paste, (void *) edit);
 
-	rc = ui_menu_entry_create(medit, "Delete", "Del", &mdelete);
+	rc = ui_menu_entry_create(medit, "~D~elete", "Del", &mdelete);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -510,5 +525,5 @@
 	}
 
-	rc = ui_menu_entry_create(medit, "Select All", "Ctrl-A", &mselall);
+	rc = ui_menu_entry_create(medit, "Select ~A~ll", "Ctrl-A", &mselall);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -518,5 +533,5 @@
 	ui_menu_entry_set_cb(mselall, edit_edit_select_all, (void *) edit);
 
-	rc = ui_menu_create(edit->menubar, "Search", &msearch);
+	rc = ui_menu_dd_create(edit->menubar, "~S~earch", NULL, &msearch);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -524,5 +539,5 @@
 	}
 
-	rc = ui_menu_entry_create(msearch, "Find", "Ctrl-F", &mfind);
+	rc = ui_menu_entry_create(msearch, "~F~ind", "Ctrl-F", &mfind);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -532,5 +547,5 @@
 	ui_menu_entry_set_cb(mfind, edit_search_find, (void *) edit);
 
-	rc = ui_menu_entry_create(msearch, "Reverse Find", "Ctrl-Shift-F", &mfindr);
+	rc = ui_menu_entry_create(msearch, "~R~everse Find", "Ctrl-Shift-F", &mfindr);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -540,5 +555,5 @@
 	ui_menu_entry_set_cb(mfindr, edit_search_reverse_find, (void *) edit);
 
-	rc = ui_menu_entry_create(msearch, "Find Next", "Ctrl-N", &mfindn);
+	rc = ui_menu_entry_create(msearch, "Find ~N~ext", "Ctrl-N", &mfindn);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -554,5 +569,5 @@
 	}
 
-	rc = ui_menu_entry_create(msearch, "Go To Line", "Ctrl-L", &mgoto);
+	rc = ui_menu_entry_create(msearch, "Go To ~L~ine", "Ctrl-L", &mgoto);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -978,5 +993,5 @@
 
 		bcnt -= off;
-		memcpy(buf, buf + off, bcnt);
+		memmove(buf, buf + off, bcnt);
 
 		insert_char(c);
@@ -1259,4 +1274,5 @@
 
 	gfx_text_fmt_init(&fmt);
+	fmt.font = font;
 	fmt.color = pane->color;
 
@@ -1318,5 +1334,5 @@
 					return rc;
 
-				rc = gfx_puttext(font, &tpos, &fmt, cbuf);
+				rc = gfx_puttext(&tpos, &fmt, cbuf);
 				if (rc != EOK)
 					return rc;
@@ -1408,6 +1424,6 @@
 	 */
 	while (true) {
-		int rc = asprintf(&text, "%d, %d (%d): File '%s'. Ctrl-Q Quit  Ctrl-S Save  "
-		    "Ctrl-E Save As", coord.row, coord.column, last_row, fname);
+		int rc = asprintf(&text, "%d, %d (%d): File '%s'. Ctrl-Q Quit  "
+		    "F10 Menu", coord.row, coord.column, last_row, fname);
 		if (rc < 0) {
 			n = 0;
@@ -2219,4 +2235,19 @@
 }
 
+/** Window focus event
+ *
+ * @param window Window
+ * @param arg Argument (edit_t *)
+ * @param focus Focus number
+ */
+static void edit_wnd_focus(ui_window_t *window, void *arg, unsigned focus)
+{
+	edit_t *edit = (edit_t *)arg;
+
+	(void)edit;
+	pane_caret_display(&pane);
+	cursor_setvis(true);
+}
+
 /** Window keyboard event
  *
@@ -2229,4 +2260,7 @@
 {
 	pane.keymod = event->mods;
+
+	if (ui_window_def_kbd(window, event) == ui_claimed)
+		return;
 
 	if (event->type == KEY_PRESS) {
@@ -2237,4 +2271,45 @@
 }
 
+/** Window unfocus event
+ *
+ * @param window Window
+ * @param arg Argument (edit_t *)
+ * @param focus Focus number
+ */
+static void edit_wnd_unfocus(ui_window_t *window, void *arg, unsigned focus)
+{
+	edit_t *edit = (edit_t *) arg;
+
+	(void)edit;
+	cursor_setvis(false);
+}
+
+/** Menu bar activate event
+ *
+ * @param mbar Menu bar
+ * @param arg Argument (edit_t *)
+ */
+static void edit_menubar_activate(ui_menu_bar_t *mbar, void *arg)
+{
+	edit_t *edit = (edit_t *)arg;
+
+	(void)edit;
+	cursor_setvis(false);
+}
+
+/** Menu bar deactivate event
+ *
+ * @param mbar Menu bar
+ * @param arg Argument (edit_t *)
+ */
+static void edit_menubar_deactivate(ui_menu_bar_t *mbar, void *arg)
+{
+	edit_t *edit = (edit_t *)arg;
+
+	(void)edit;
+	pane_caret_display(&pane);
+	cursor_setvis(true);
+}
+
 /** File / Save menu entry selected.
  *
@@ -2402,13 +2477,9 @@
 {
 	edit_t *edit = (edit_t *)arg;
-	gfx_context_t *gc = ui_window_get_gc(edit->window);
 	char *cname;
 	errno_t rc;
 
+	(void)edit;
 	ui_file_dialog_destroy(dialog);
-	// TODO Smarter cursor management
-	pane.rflags |= REDRAW_CARET;
-	(void) pane_update(&pane);
-	gfx_cursor_set_visible(gc, true);
 
 	cname = str_dup(fname);
@@ -2436,11 +2507,7 @@
 {
 	edit_t *edit = (edit_t *)arg;
-	gfx_context_t *gc = ui_window_get_gc(edit->window);
-
+
+	(void)edit;
 	ui_file_dialog_destroy(dialog);
-	// TODO Smarter cursor management
-	pane.rflags |= REDRAW_CARET;
-	(void) pane_update(&pane);
-	gfx_cursor_set_visible(gc, true);
 }
 
@@ -2453,11 +2520,7 @@
 {
 	edit_t *edit = (edit_t *)arg;
-	gfx_context_t *gc = ui_window_get_gc(edit->window);
-
+
+	(void)edit;
 	ui_file_dialog_destroy(dialog);
-	// TODO Smarter cursor management
-	pane.rflags |= REDRAW_CARET;
-	(void) pane_update(&pane);
-	gfx_cursor_set_visible(gc, true);
 }
 
@@ -2472,5 +2535,4 @@
 {
 	edit_t *edit = (edit_t *) arg;
-	gfx_context_t *gc = ui_window_get_gc(edit->window);
 	char *endptr;
 	int line;
@@ -2484,8 +2546,6 @@
 
 	caret_move_absolute(line, pane.ideal_column, dir_before, false);
-	// TODO Smarter cursor management
+	(void)edit;
 	(void) pane_update(&pane);
-	gfx_cursor_set_visible(gc, true);
-	(void) gfx_update(gc);
 }
 
@@ -2498,11 +2558,7 @@
 {
 	edit_t *edit = (edit_t *) arg;
-	gfx_context_t *gc = ui_window_get_gc(edit->window);
-
+
+	(void)edit;
 	ui_prompt_dialog_destroy(dialog);
-	// TODO Smarter cursor management
-	pane.rflags |= REDRAW_CARET;
-	(void) pane_update(&pane);
-	gfx_cursor_set_visible(gc, true);
 }
 
@@ -2515,11 +2571,7 @@
 {
 	edit_t *edit = (edit_t *) arg;
-	gfx_context_t *gc = ui_window_get_gc(edit->window);
-
+
+	(void)edit;
 	ui_prompt_dialog_destroy(dialog);
-	// TODO Smarter cursor management
-	pane.rflags |= REDRAW_CARET;
-	(void) pane_update(&pane);
-	gfx_cursor_set_visible(gc, true);
 }
 
@@ -2534,8 +2586,8 @@
 {
 	edit_t *edit = (edit_t *) arg;
-	gfx_context_t *gc = ui_window_get_gc(edit->window);
 	char *pattern;
 	bool reverse;
 
+	(void)edit;
 	ui_prompt_dialog_destroy(dialog);
 
@@ -2554,8 +2606,5 @@
 	search(pattern, reverse);
 
-	// TODO Smarter cursor management
 	(void) pane_update(&pane);
-	gfx_cursor_set_visible(gc, true);
-	(void) gfx_update(gc);
 }
 
@@ -2568,11 +2617,7 @@
 {
 	edit_t *edit = (edit_t *) arg;
-	gfx_context_t *gc = ui_window_get_gc(edit->window);
-
+
+	(void)edit;
 	ui_prompt_dialog_destroy(dialog);
-	// TODO Smarter cursor management
-	pane.rflags |= REDRAW_CARET;
-	(void) pane_update(&pane);
-	gfx_cursor_set_visible(gc, true);
 }
 
@@ -2585,11 +2630,7 @@
 {
 	edit_t *edit = (edit_t *) arg;
-	gfx_context_t *gc = ui_window_get_gc(edit->window);
-
+
+	(void)edit;
 	ui_prompt_dialog_destroy(dialog);
-	// TODO Smarter cursor management
-	pane.rflags |= REDRAW_CARET;
-	(void) pane_update(&pane);
-	gfx_cursor_set_visible(gc, true);
 }
 
Index: uspace/app/fontedit/fontedit.c
===================================================================
--- uspace/app/fontedit/fontedit.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/fontedit/fontedit.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -167,4 +167,38 @@
 }
 
+/** Adjust font underline Y0.
+ *
+ * @param fedit Font editor
+ */
+static void font_edit_adjust_underline_y0(font_edit_t *fedit,
+    gfx_coord_t change)
+{
+	gfx_font_metrics_t fmetrics;
+
+	gfx_font_get_metrics(fedit->font, &fmetrics);
+	fmetrics.underline_y0 += change;
+	(void) gfx_font_set_metrics(fedit->font, &fmetrics);
+
+	printf("New underline Y0: %d\n", fmetrics.underline_y0);
+	font_edit_paint(fedit);
+}
+
+/** Adjust font underline Y1.
+ *
+ * @param fedit Font editor
+ */
+static void font_edit_adjust_underline_y1(font_edit_t *fedit,
+    gfx_coord_t change)
+{
+	gfx_font_metrics_t fmetrics;
+
+	gfx_font_get_metrics(fedit->font, &fmetrics);
+	fmetrics.underline_y1 += change;
+	(void) gfx_font_set_metrics(fedit->font, &fmetrics);
+
+	printf("New underline Y1: %d\n", fmetrics.underline_y1);
+	font_edit_paint(fedit);
+}
+
 /** Handle font editor close event.
  *
@@ -314,4 +348,16 @@
 		font_edit_adjust_leading(fedit, +1);
 		break;
+	case KC_U:
+		font_edit_adjust_underline_y0(fedit, -1);
+		break;
+	case KC_I:
+		font_edit_adjust_underline_y0(fedit, +1);
+		break;
+	case KC_O:
+		font_edit_adjust_underline_y1(fedit, -1);
+		break;
+	case KC_P:
+		font_edit_adjust_underline_y1(fedit, +1);
+		break;
 	case KC_X:
 		(void) gfx_glyph_bmp_clear(fedit->gbmp);
@@ -457,4 +503,5 @@
 
 	gfx_text_fmt_init(&fmt);
+	fmt.font = fedit->font;
 	fmt.color = color;
 
@@ -462,5 +509,5 @@
 	pos.y = y;
 
-	return gfx_puttext(fedit->font, &pos, &fmt, str);
+	return gfx_puttext(&pos, &fmt, str);
 }
 
@@ -521,4 +568,5 @@
 	gfx_color_t *color = NULL;
 	gfx_rect_t rect;
+	gfx_rect_t rect2;
 	gfx_rect_t grect;
 	gfx_font_metrics_t fmetrics;
@@ -564,4 +612,24 @@
 	    fmetrics.leading, &rect);
 	rect.p1.x += 100;
+
+	rc = gfx_fill_rect(fedit->gc, &rect);
+	if (rc != EOK)
+		goto error;
+
+	gfx_color_delete(color);
+
+	/* Display underline */
+
+	rc = gfx_color_new_rgb_i16(0x4000, 0x4000, 0, &color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_set_color(fedit->gc, color);
+	if (rc != EOK)
+		goto error;
+
+	font_edit_gpix_to_disp(fedit, 0, fmetrics.underline_y0, &rect);
+	font_edit_gpix_to_disp(fedit, 10, fmetrics.underline_y1, &rect2);
+	rect.p1 = rect2.p0;
 
 	rc = gfx_fill_rect(fedit->gc, &rect);
@@ -719,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 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/gfxdemo/gfxdemo.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -51,4 +51,5 @@
 #include <ui/window.h>
 #include <ui/wdecor.h>
+#include "gfxdemo.h"
 
 static void wnd_close_event(void *);
@@ -68,8 +69,14 @@
 };
 
+static void demo_kbd_event(kbd_event_t *);
+
 static bool quit = false;
+static FIBRIL_MUTEX_INITIALIZE(quit_lock);
+static FIBRIL_CONDVAR_INITIALIZE(quit_cv);
 static gfx_typeface_t *tface;
 static gfx_font_t *font;
 static gfx_coord_t vpad;
+static console_ctrl_t *con = NULL;
+static ui_t *ui;
 
 /** Determine if we are running in text mode.
@@ -83,4 +90,40 @@
 	// XXX Need a proper way to determine text mode
 	return w <= 80;
+}
+
+/** Sleep until timeout or quit request.
+ *
+ * @param msec Number of microseconds to sleep for
+ */
+static void demo_msleep(unsigned msec)
+{
+	errno_t rc;
+	usec_t usec;
+	cons_event_t cevent;
+
+	if (ui != NULL)
+		ui_unlock(ui);
+	fibril_mutex_lock(&quit_lock);
+	if (!quit) {
+		if (con != NULL) {
+			usec = (usec_t)msec * 1000;
+			while (usec > 0 && !quit) {
+				rc = console_get_event_timeout(con, &cevent, &usec);
+				if (rc == EOK) {
+					if (cevent.type == CEV_KEY) {
+						fibril_mutex_unlock(&quit_lock);
+						demo_kbd_event(&cevent.ev.key);
+						fibril_mutex_lock(&quit_lock);
+					}
+				}
+			}
+		} else {
+			(void) fibril_condvar_wait_timeout(&quit_cv, &quit_lock,
+			    (usec_t)msec * 1000);
+		}
+	}
+	fibril_mutex_unlock(&quit_lock);
+	if (ui != NULL)
+		ui_lock(ui);
 }
 
@@ -238,4 +281,5 @@
 
 		gfx_text_fmt_init(&fmt);
+		fmt.font = font;
 		fmt.color = color;
 		fmt.halign = gfx_halign_center;
@@ -243,6 +287,6 @@
 
 		pos.x = w / 2;
-		pos.y = h - 1;
-		rc = gfx_puttext(font, &pos, &fmt, text);
+		pos.y = h;
+		rc = gfx_puttext(&pos, &fmt, text);
 		if (rc != EOK) {
 			printf("Error rendering text.\n");
@@ -315,6 +359,5 @@
 		gfx_color_delete(color);
 
-		fibril_usleep(500 * 1000);
-
+		demo_msleep(500);
 		if (quit)
 			break;
@@ -350,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));
 		}
@@ -386,5 +429,5 @@
 			k = i * i + j * j;
 			pixelmap_put_pixel(&pixelmap, i, j,
-			    PIXEL(0, k, k, k));
+			    PIXEL(255, k, k, k));
 		}
 	}
@@ -421,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));
 		}
 	}
@@ -477,6 +520,6 @@
 			if (rc != EOK)
 				goto error;
-			fibril_usleep(250 * 1000);
-
+
+			demo_msleep(250);
 			if (quit)
 				goto out;
@@ -538,6 +581,5 @@
 		}
 
-		fibril_usleep(500 * 1000);
-
+		demo_msleep(500);
 		if (quit)
 			break;
@@ -579,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);
@@ -599,6 +641,5 @@
 		}
 
-		fibril_usleep(500 * 1000);
-
+		demo_msleep(500);
 		if (quit)
 			break;
@@ -689,9 +730,10 @@
 
 	gfx_text_fmt_init(&fmt);
+	fmt.font = font;
 	fmt.color = color;
 
 	pos.x = rect.p0.x;
 	pos.y = rect.p0.y;
-	rc = gfx_puttext(font, &pos, &fmt, "Top left");
+	rc = gfx_puttext(&pos, &fmt, "Top left");
 	if (rc != EOK) {
 		printf("Error rendering text.\n");
@@ -702,5 +744,5 @@
 	pos.y = rect.p0.y;
 	fmt.halign = gfx_halign_center;
-	rc = gfx_puttext(font, &pos, &fmt, "Top center");
+	rc = gfx_puttext(&pos, &fmt, "Top center");
 	if (rc != EOK)
 		goto error;
@@ -709,5 +751,5 @@
 	pos.y = rect.p0.y;
 	fmt.halign = gfx_halign_right;
-	rc = gfx_puttext(font, &pos, &fmt, "Top right");
+	rc = gfx_puttext(&pos, &fmt, "Top right");
 	if (rc != EOK)
 		goto error;
@@ -718,5 +760,5 @@
 	pos.y = (rect.p0.y + rect.p1.y - 1) / 2;
 	fmt.halign = gfx_halign_left;
-	rc = gfx_puttext(font, &pos, &fmt, "Center left");
+	rc = gfx_puttext(&pos, &fmt, "Center left");
 	if (rc != EOK)
 		goto error;
@@ -725,5 +767,5 @@
 	pos.y = (rect.p0.y + rect.p1.y - 1) / 2;
 	fmt.halign = gfx_halign_center;
-	rc = gfx_puttext(font, &pos, &fmt, "Center");
+	rc = gfx_puttext(&pos, &fmt, "Center");
 	if (rc != EOK)
 		goto error;
@@ -732,5 +774,5 @@
 	pos.y = (rect.p0.y + rect.p1.y - 1) / 2;
 	fmt.halign = gfx_halign_right;
-	rc = gfx_puttext(font, &pos, &fmt, "Center right");
+	rc = gfx_puttext(&pos, &fmt, "Center right");
 	if (rc != EOK)
 		goto error;
@@ -741,5 +783,5 @@
 	pos.y = rect.p1.y - 1;
 	fmt.halign = gfx_halign_left;
-	rc = gfx_puttext(font, &pos, &fmt, "Bottom left");
+	rc = gfx_puttext(&pos, &fmt, "Bottom left");
 	if (rc != EOK)
 		goto error;
@@ -748,5 +790,5 @@
 	pos.y = rect.p1.y;
 	fmt.halign = gfx_halign_center;
-	rc = gfx_puttext(font, &pos, &fmt, "Bottom center");
+	rc = gfx_puttext(&pos, &fmt, "Bottom center");
 	if (rc != EOK)
 		goto error;
@@ -755,5 +797,5 @@
 	pos.y = rect.p1.y;
 	fmt.halign = gfx_halign_right;
-	rc = gfx_puttext(font, &pos, &fmt, "Bottom right");
+	rc = gfx_puttext(&pos, &fmt, "Bottom right");
 	if (rc != EOK)
 		goto error;
@@ -762,4 +804,5 @@
 
 	gfx_text_fmt_init(&fmt);
+	fmt.font = font;
 
 	for (i = 0; i < 8; i++) {
@@ -776,8 +819,9 @@
 
 		fmt.color = color;
+		fmt.underline = !fmt.underline;
 
 		pos.x = w / 20;
 		pos.y = (6 + i) * h / 15;
-		rc = gfx_puttext(font, &pos, &fmt, "The quick brown fox jumps over the lazy dog.");
+		rc = gfx_puttext(&pos, &fmt, "The quick brown fox jumps over the lazy dog.");
 		if (rc != EOK)
 			goto error;
@@ -787,5 +831,86 @@
 
 	for (i = 0; i < 10; i++) {
-		fibril_usleep(500 * 1000);
+		demo_msleep(500);
+		if (quit)
+			break;
+	}
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Run text abbreviation demo on a graphic context.
+ *
+ * @param gc Graphic context
+ * @param w Width
+ * @param h Height
+ */
+static errno_t demo_text_abbr(gfx_context_t *gc, gfx_coord_t w, gfx_coord_t h)
+{
+	gfx_color_t *color = NULL;
+	gfx_rect_t rect;
+	gfx_coord2_t pos;
+	gfx_text_fmt_t fmt;
+	int i;
+	errno_t rc;
+
+	if (quit)
+		return EOK;
+
+	rc = demo_begin(gc, w, h, "Text abbreviation");
+	if (rc != EOK)
+		goto error;
+
+	for (i = 0; i < 11; i++) {
+
+		rc = gfx_color_new_rgb_i16(0, 0, 0x8000, &color);
+		if (rc != EOK)
+			goto error;
+
+		rc = gfx_set_color(gc, color);
+		if (rc != EOK)
+			goto error;
+
+		rect.p0.x = w / 20;
+		rect.p0.y = (2 + 2 * i) * h / 25;
+		rect.p1.x = w - w / 20 - w * i / 12;
+		rect.p1.y = (3 + 2 * i) * h / 25;
+
+		rc = gfx_fill_rect(gc, &rect);
+		if (rc != EOK)
+			goto error;
+
+		gfx_color_delete(color);
+
+		if (demo_is_text(w, h)) {
+			rc = gfx_color_new_ega(0x1f, &color);
+			if (rc != EOK)
+				goto error;
+		} else {
+			rc = gfx_color_new_rgb_i16(0xffff, 0xffff, 0xffff,
+			    &color);
+			if (rc != EOK)
+				goto error;
+		}
+
+		gfx_text_fmt_init(&fmt);
+		fmt.font = font;
+		fmt.color = color;
+		fmt.abbreviate = true;
+		fmt.width = rect.p1.x - rect.p0.x;
+
+		pos.x = rect.p0.x;
+		pos.y = rect.p0.y;
+		rc = gfx_puttext(&pos, &fmt,
+		    "The quick brow fox jumps over the lazy dog!");
+		if (rc != EOK) {
+			printf("Error rendering text.\n");
+			goto error;
+		}
+	}
+
+	for (i = 0; i < 10; i++) {
+		demo_msleep(500);
 		if (quit)
 			break;
@@ -884,6 +1009,5 @@
 		}
 
-		fibril_usleep(500 * 1000);
-
+		demo_msleep(500);
 		if (quit)
 			break;
@@ -932,4 +1056,8 @@
 			goto error;
 
+		rc = demo_text_abbr(gc, w, h);
+		if (rc != EOK)
+			goto error;
+
 		rc = demo_clip(gc, w, h);
 		if (rc != EOK)
@@ -947,15 +1075,17 @@
 static errno_t demo_console(void)
 {
-	console_ctrl_t *con = NULL;
 	console_gc_t *cgc = NULL;
 	gfx_context_t *gc;
-	errno_t rc;
-
-	printf("Init console..\n");
+	sysarg_t cols, rows;
+	errno_t rc;
+
 	con = console_init(stdin, stdout);
 	if (con == NULL)
 		return EIO;
 
-	printf("Create console GC\n");
+	rc = console_get_size(con, &cols, &rows);
+	if (rc != EOK)
+		return rc;
+
 	rc = console_gc_create(con, stdout, &cgc);
 	if (rc != EOK)
@@ -964,5 +1094,5 @@
 	gc = console_gc_get_ctx(cgc);
 
-	rc = demo_loop(gc, 80, 25);
+	rc = demo_loop(gc, cols, rows);
 	if (rc != EOK)
 		return rc;
@@ -973,4 +1103,16 @@
 
 	return EOK;
+}
+
+static errno_t demo_ui_fibril(void *arg)
+{
+	demo_ui_args_t *args = (demo_ui_args_t *)arg;
+	errno_t rc;
+
+	ui_lock(args->ui);
+	rc = demo_loop(args->gc, args->dims.x, args->dims.y);
+	ui_unlock(args->ui);
+	ui_quit(args->ui);
+	return rc;
 }
 
@@ -978,5 +1120,4 @@
 static errno_t demo_ui(const char *display_spec)
 {
-	ui_t *ui = NULL;
 	ui_wnd_params_t params;
 	ui_window_t *window = NULL;
@@ -985,11 +1126,19 @@
 	gfx_rect_t wrect;
 	gfx_coord2_t off;
-	errno_t rc;
-
-	printf("Init UI..\n");
+	gfx_rect_t ui_rect;
+	gfx_coord2_t dims;
+	demo_ui_args_t args;
+	fid_t fid;
+	errno_t rc;
 
 	rc = ui_create(display_spec, &ui);
 	if (rc != EOK) {
 		printf("Error initializing UI (%s)\n", display_spec);
+		goto error;
+	}
+
+	rc = ui_get_rect(ui, &ui_rect);
+	if (rc != EOK) {
+		printf("Error getting display size.\n");
 		goto error;
 	}
@@ -1003,11 +1152,23 @@
 	params.caption = "GFX Demo";
 
+	/* Do not decorate the window in fullscreen mode */
+	if (ui_is_fullscreen(ui))
+		params.style &= ~ui_wds_decorated;
+
 	/*
 	 * Compute window rectangle such that application area corresponds
 	 * 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);
+
+	gfx_rect_dims(&ui_rect, &dims);
+
+	/* Make sure window is not larger than the entire screen */
+	if (params.rect.p1.x > dims.x)
+		params.rect.p1.x = dims.x;
+	if (params.rect.p1.y > dims.y)
+		params.rect.p1.y = dims.y;
 
 	rc = ui_window_create(ui, &params, &window);
@@ -1025,10 +1186,23 @@
 	}
 
-	task_retval(0);
-
-	rc = demo_loop(gc, rect.p1.x, rect.p1.y);
-	if (rc != EOK)
-		goto error;
-
+	ui_window_get_app_rect(window, &rect);
+	gfx_rect_dims(&rect, &dims);
+
+	if (!ui_is_fullscreen(ui))
+		task_retval(0);
+
+	args.gc = gc;
+	args.dims = dims;
+	args.ui = ui;
+
+	fid = fibril_create(demo_ui_fibril, (void *)&args);
+	if (fid == 0) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	fibril_add_ready(fid);
+
+	ui_run(ui);
 	ui_window_destroy(window);
 	ui_destroy(ui);
@@ -1052,6 +1226,4 @@
 	errno_t rc;
 
-	printf("Init display..\n");
-
 	rc = display_open(display_svc, &display);
 	if (rc != EOK) {
@@ -1065,4 +1237,5 @@
 	params.rect.p1.x = 400;
 	params.rect.p1.y = 300;
+	params.caption = "GFX Demo";
 
 	rc = display_window_create(display, &params, &wnd_cb, NULL, &window);
@@ -1094,28 +1267,54 @@
 }
 
+static void demo_quit(void)
+{
+	fibril_mutex_lock(&quit_lock);
+	quit = true;
+	fibril_mutex_unlock(&quit_lock);
+	fibril_condvar_broadcast(&quit_cv);
+}
+
 static void wnd_close_event(void *arg)
 {
-	printf("Close event\n");
-	quit = true;
+	demo_quit();
+}
+
+static void demo_kbd_event(kbd_event_t *event)
+{
+	if (event->type == KEY_PRESS) {
+		/* Ctrl-Q */
+		if ((event->mods & KM_CTRL) != 0 &&
+		    (event->mods & KM_ALT) == 0 &&
+		    (event->mods & KM_SHIFT) == 0 &&
+		    event->key == KC_Q) {
+			demo_quit();
+		}
+
+		/* Escape */
+		if ((event->mods & KM_CTRL) == 0 &&
+		    (event->mods & KM_ALT) == 0 &&
+		    (event->mods & KM_SHIFT) == 0 &&
+		    event->key == KC_ESCAPE) {
+			demo_quit();
+		}
+	}
 }
 
 static void wnd_kbd_event(void *arg, kbd_event_t *event)
 {
-	printf("Keyboard event type=%d key=%d\n", event->type, event->key);
-	if (event->type == KEY_PRESS)
-		quit = true;
+	(void)arg;
+	demo_kbd_event(event);
 }
 
 static void uiwnd_close_event(ui_window_t *window, void *arg)
 {
-	printf("Close event\n");
-	quit = true;
+	demo_quit();
 }
 
 static void uiwnd_kbd_event(ui_window_t *window, void *arg, kbd_event_t *event)
 {
-	printf("Keyboard event type=%d key=%d\n", event->type, event->key);
-	if (event->type == KEY_PRESS)
-		quit = true;
+	(void)window;
+	(void)arg;
+	demo_kbd_event(event);
 }
 
@@ -1129,4 +1328,5 @@
 	errno_t rc;
 	const char *display_svc = DISPLAY_DEFAULT;
+	const char *ui_display_spec = UI_ANY_DEFAULT;
 	int i;
 
@@ -1141,5 +1341,5 @@
 			}
 
-			display_svc = argv[i++];
+			display_svc = ui_display_spec = argv[i++];
 		} else {
 			printf("Invalid option '%s'.\n", argv[i]);
@@ -1158,5 +1358,5 @@
 			return 1;
 	} else if (str_cmp(argv[i], "ui") == 0) {
-		rc = demo_ui(display_svc);
+		rc = demo_ui(ui_display_spec);
 		if (rc != EOK)
 			return 1;
Index: uspace/app/gfxdemo/gfxdemo.h
===================================================================
--- uspace/app/gfxdemo/gfxdemo.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/gfxdemo/gfxdemo.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,52 @@
+/*
+ * 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 gfxdemo
+ * @{
+ */
+/**
+ * @file Graphic demo
+ */
+
+#ifndef GFXDEMO_H
+#define GFXDEMO_H
+
+#include <gfx/context.h>
+#include <gfx/coord.h>
+#include <ui/ui.h>
+
+typedef struct {
+	gfx_context_t *gc;
+	gfx_coord2_t dims;
+	ui_t *ui;
+} demo_ui_args_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/hbench/benchlist.c
===================================================================
--- uspace/app/hbench/benchlist.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/hbench/benchlist.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2018 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2018 Vojtech Horky
  * All rights reserved.
@@ -42,8 +42,13 @@
 	&benchmark_fibril_mutex,
 	&benchmark_file_read,
+	&benchmark_rand_read,
+	&benchmark_seq_read,
 	&benchmark_malloc1,
 	&benchmark_malloc2,
 	&benchmark_ns_ping,
-	&benchmark_ping_pong
+	&benchmark_ping_pong,
+	&benchmark_read1k,
+	&benchmark_taskgetid,
+	&benchmark_write1k,
 };
 
Index: uspace/app/hbench/disk/randread.c
===================================================================
--- uspace/app/hbench/disk/randread.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/hbench/disk/randread.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,146 @@
+/*
+ * 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 hbench
+ * @{
+ */
+
+#include <block.h>
+#include <loc.h>
+#include <str_error.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "../hbench.h"
+
+/** Execute disk random read benchmark. */
+static bool runner(bench_env_t *env, bench_run_t *run, uint64_t size)
+{
+	const char *disk;
+	const char *nbstr;
+	service_id_t svcid;
+	size_t block_size;
+	aoff64_t dev_nblocks;
+	aoff64_t baddr;
+	bool block_inited = false;
+	char *buf = NULL;
+	uint64_t i;
+	errno_t rc;
+	int nitem;
+	unsigned nb;
+
+	disk = bench_env_param_get(env, "disk", NULL);
+	if (disk == NULL) {
+		bench_run_fail(run, "You must specify 'disk' parameter.");
+		goto error;
+	}
+
+	nbstr = bench_env_param_get(env, "nb", "1");
+	nitem = sscanf(nbstr, "%u", &nb);
+	if (nitem < 1) {
+		bench_run_fail(run, "'nb' must be an integer number of blocks.");
+		goto error;
+	}
+
+	rc = loc_service_get_id(disk, &svcid, 0);
+	if (rc != EOK) {
+		bench_run_fail(run, "failed resolving device '%s'", disk);
+		goto error;
+	}
+
+	rc = block_init(svcid);
+	if (rc != EOK) {
+		bench_run_fail(run, "failed opening block device '%s'",
+		    disk);
+	}
+
+	block_inited = true;
+
+	rc = block_get_bsize(svcid, &block_size);
+	if (rc != EOK) {
+		bench_run_fail(run, "error determining device block size.");
+		goto error;
+	}
+
+	rc = block_get_nblocks(svcid, &dev_nblocks);
+	if (rc != EOK) {
+		bench_run_fail(run, "failed to obtain block device size.\n");
+		goto error;
+	}
+
+	if (dev_nblocks < nb) {
+		bench_run_fail(run, "device is smaller than %u blocks.\n",
+		    nb);
+		goto error;
+	}
+
+	buf = malloc(block_size * nb);
+	if (buf == NULL) {
+		bench_run_fail(run, "failed to allocate buffer (%zu bytes)",
+		    block_size * nb);
+		goto error;
+	}
+
+	bench_run_start(run);
+	for (i = 0; i < size; i++) {
+		/* Generate pseudo-random block address */
+		baddr = (rand() + rand() * RAND_MAX) % (dev_nblocks - nb + 1);
+
+		rc = block_read_direct(svcid, baddr, nb, buf);
+		if (rc != EOK) {
+			bench_run_fail(run, "failed to read blockd %llu-%llu: "
+			    "%s", (unsigned long long)baddr,
+			    (unsigned long long)(baddr + nb - 1),
+			    str_error(errno));
+			goto error;
+		}
+	}
+
+	bench_run_stop(run);
+	block_fini(svcid);
+	free(buf);
+
+	return true;
+error:
+	if (buf != NULL)
+		free(buf);
+	if (block_inited)
+		block_fini(svcid);
+	return false;
+}
+
+benchmark_t benchmark_rand_read = {
+	.name = "rand_read",
+	.desc = "Random disk read (must set 'disk' parameter).",
+	.entry = &runner,
+	.setup = NULL,
+	.teardown = NULL
+};
+
+/**
+ * @}
+ */
Index: uspace/app/hbench/disk/seqread.c
===================================================================
--- uspace/app/hbench/disk/seqread.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/hbench/disk/seqread.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,145 @@
+/*
+ * 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 hbench
+ * @{
+ */
+
+#include <block.h>
+#include <loc.h>
+#include <str_error.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "../hbench.h"
+
+/** Execute disk sequential read benchmark. */
+static bool runner(bench_env_t *env, bench_run_t *run, uint64_t size)
+{
+	const char *disk;
+	const char *nbstr;
+	service_id_t svcid;
+	size_t block_size;
+	aoff64_t dev_nblocks;
+	aoff64_t baddr;
+	bool block_inited = false;
+	char *buf = NULL;
+	uint64_t i;
+	errno_t rc;
+	int nitem;
+	unsigned nb;
+
+	disk = bench_env_param_get(env, "disk", NULL);
+	if (disk == NULL) {
+		bench_run_fail(run, "Yo must specify 'disk' parameter.");
+		goto error;
+	}
+
+	nbstr = bench_env_param_get(env, "nb", "1");
+	nitem = sscanf(nbstr, "%u", &nb);
+	if (nitem < 1) {
+		bench_run_fail(run, "'nb' must be an integer number of blocks.");
+		goto error;
+	}
+
+	rc = loc_service_get_id(disk, &svcid, 0);
+	if (rc != EOK) {
+		bench_run_fail(run, "failed resolving device '%s'", disk);
+		goto error;
+	}
+
+	rc = block_init(svcid);
+	if (rc != EOK) {
+		bench_run_fail(run, "failed opening block device '%s'",
+		    disk);
+	}
+
+	block_inited = true;
+
+	rc = block_get_bsize(svcid, &block_size);
+	if (rc != EOK) {
+		bench_run_fail(run, "error determining device block size.");
+		goto error;
+	}
+
+	rc = block_get_nblocks(svcid, &dev_nblocks);
+	if (rc != EOK) {
+		bench_run_fail(run, "failed to obtain block device size.\n");
+		goto error;
+	}
+
+	if (dev_nblocks < nb) {
+		bench_run_fail(run, "device is smaller than %u blocks.\n",
+		    nb);
+		goto error;
+	}
+
+	buf = malloc(block_size);
+	if (buf == NULL) {
+		bench_run_fail(run, "failed to allocate buffer (%zu bytes)",
+		    block_size);
+		goto error;
+	}
+
+	bench_run_start(run);
+	for (i = 0; i < size; i++) {
+		baddr = i % (dev_nblocks - nb + 1);
+
+		rc = block_read_direct(svcid, baddr, 1, buf);
+		if (rc != EOK) {
+			bench_run_fail(run, "failed to read blocks %llu-%llu: "
+			    "%s", (unsigned long long)baddr,
+			    (unsigned long long)(baddr + nb - 1),
+			    str_error(errno));
+			goto error;
+		}
+	}
+
+	bench_run_stop(run);
+	block_fini(svcid);
+	free(buf);
+
+	return true;
+error:
+	if (buf != NULL)
+		free(buf);
+	if (block_inited)
+		block_fini(svcid);
+	return false;
+}
+
+benchmark_t benchmark_seq_read = {
+	.name = "seq_read",
+	.desc = "Sequential disk read (must set 'disk' parameter).",
+	.entry = &runner,
+	.setup = NULL,
+	.teardown = NULL
+};
+
+/**
+ * @}
+ */
Index: uspace/app/hbench/env.c
===================================================================
--- uspace/app/hbench/env.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/hbench/env.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -81,5 +81,5 @@
 }
 
-static hash_table_ops_t param_hash_table_ops = {
+static const hash_table_ops_t param_hash_table_ops = {
 	.hash = param_hash,
 	.key_hash = param_key_hash,
@@ -97,5 +97,5 @@
 
 	env->run_count = DEFAULT_RUN_COUNT;
-	env->minimal_run_duration_nanos = MSEC2NSEC(DEFAULT_MIN_RUN_DURATION_SEC);
+	env->minimal_run_duration_nanos = MSEC2NSEC(DEFAULT_MIN_RUN_DURATION_MSEC);
 
 	return EOK;
Index: uspace/app/hbench/hbench.h
===================================================================
--- uspace/app/hbench/hbench.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/hbench/hbench.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2018 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2019 Vojtech Horky
  * All rights reserved.
@@ -43,5 +43,5 @@
 
 #define DEFAULT_RUN_COUNT 10
-#define DEFAULT_MIN_RUN_DURATION_SEC 10
+#define DEFAULT_MIN_RUN_DURATION_MSEC 1000
 
 /** Single run information.
@@ -135,8 +135,13 @@
 extern benchmark_t benchmark_fibril_mutex;
 extern benchmark_t benchmark_file_read;
+extern benchmark_t benchmark_rand_read;
+extern benchmark_t benchmark_seq_read;
 extern benchmark_t benchmark_malloc1;
 extern benchmark_t benchmark_malloc2;
 extern benchmark_t benchmark_ns_ping;
 extern benchmark_t benchmark_ping_pong;
+extern benchmark_t benchmark_read1k;
+extern benchmark_t benchmark_taskgetid;
+extern benchmark_t benchmark_write1k;
 
 #endif
Index: uspace/app/hbench/ipc/read1k.c
===================================================================
--- uspace/app/hbench/ipc/read1k.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/hbench/ipc/read1k.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,102 @@
+/*
+ * 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 hbench
+ * @{
+ */
+
+#include <stdio.h>
+#include <ipc_test.h>
+#include <async.h>
+#include <errno.h>
+#include <str_error.h>
+#include "../hbench.h"
+
+enum {
+	rw_buf_size = 1024
+};
+
+static ipc_test_t *test = NULL;
+static uint8_t rw_buf[rw_buf_size];
+
+static bool setup(bench_env_t *env, bench_run_t *run)
+{
+	errno_t rc;
+
+	rc = ipc_test_create(&test);
+	if (rc != EOK) {
+		return bench_run_fail(run,
+		    "failed contacting IPC test server (have you run /srv/test/ipc-test?): %s (%d)",
+		    str_error(rc), rc);
+	}
+
+	rc = ipc_test_set_rw_buf_size(test, rw_buf_size);
+	if (rc != EOK) {
+		return bench_run_fail(run,
+		    "failed setting read/write buffer size.");
+	}
+
+	return true;
+}
+
+static bool teardown(bench_env_t *env, bench_run_t *run)
+{
+	ipc_test_destroy(test);
+	return true;
+}
+
+static bool runner(bench_env_t *env, bench_run_t *run, uint64_t niter)
+{
+	errno_t rc;
+
+	bench_run_start(run);
+
+	for (uint64_t count = 0; count < niter; count++) {
+		rc = ipc_test_read(test, rw_buf, rw_buf_size);
+
+		if (rc != EOK) {
+			return bench_run_fail(run, "failed reading buffer: %s (%d)",
+			    str_error(rc), rc);
+		}
+	}
+
+	bench_run_stop(run);
+
+	return true;
+}
+
+benchmark_t benchmark_read1k = {
+	.name = "read1k",
+	.desc = "IPC read 1kB buffer benchmark",
+	.entry = &runner,
+	.setup = &setup,
+	.teardown = &teardown
+};
+
+/** @}
+ */
Index: uspace/app/hbench/ipc/write1k.c
===================================================================
--- uspace/app/hbench/ipc/write1k.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/hbench/ipc/write1k.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,102 @@
+/*
+ * 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 hbench
+ * @{
+ */
+
+#include <stdio.h>
+#include <ipc_test.h>
+#include <async.h>
+#include <errno.h>
+#include <str_error.h>
+#include "../hbench.h"
+
+enum {
+	rw_buf_size = 1024
+};
+
+static ipc_test_t *test = NULL;
+static uint8_t rw_buf[rw_buf_size];
+
+static bool setup(bench_env_t *env, bench_run_t *run)
+{
+	errno_t rc;
+
+	rc = ipc_test_create(&test);
+	if (rc != EOK) {
+		return bench_run_fail(run,
+		    "failed contacting IPC test server (have you run /srv/test/ipc-test?): %s (%d)",
+		    str_error(rc), rc);
+	}
+
+	rc = ipc_test_set_rw_buf_size(test, rw_buf_size);
+	if (rc != EOK) {
+		return bench_run_fail(run,
+		    "failed setting read/write buffer size.");
+	}
+
+	return true;
+}
+
+static bool teardown(bench_env_t *env, bench_run_t *run)
+{
+	ipc_test_destroy(test);
+	return true;
+}
+
+static bool runner(bench_env_t *env, bench_run_t *run, uint64_t niter)
+{
+	errno_t rc;
+
+	bench_run_start(run);
+
+	for (uint64_t count = 0; count < niter; count++) {
+		rc = ipc_test_write(test, rw_buf, rw_buf_size);
+
+		if (rc != EOK) {
+			return bench_run_fail(run, "failed writing buffer: %s (%d)",
+			    str_error(rc), rc);
+		}
+	}
+
+	bench_run_stop(run);
+
+	return true;
+}
+
+benchmark_t benchmark_write1k = {
+	.name = "write1k",
+	.desc = "IPC write 1kB buffer benchmark",
+	.entry = &runner,
+	.setup = &setup,
+	.teardown = &teardown
+};
+
+/** @}
+ */
Index: uspace/app/hbench/meson.build
===================================================================
--- uspace/app/hbench/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/hbench/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 #
-# Copyright (c) 2018 Jiri Svoboda
+# Copyright (c) 2024 Jiri Svoboda
 # All rights reserved.
 #
@@ -27,5 +27,5 @@
 #
 
-deps = [ 'math' ]
+deps = [ 'block', 'math', 'ipctest' ]
 src = files(
 	'benchlist.c',
@@ -34,10 +34,15 @@
 	'main.c',
 	'utils.c',
+	'disk/randread.c',
+	'disk/seqread.c',
 	'fs/dirread.c',
 	'fs/fileread.c',
 	'ipc/ns_ping.c',
 	'ipc/ping_pong.c',
+	'ipc/read1k.c',
+	'ipc/write1k.c',
 	'malloc/malloc1.c',
 	'malloc/malloc2.c',
 	'synch/fibril_mutex.c',
+	'syscall/taskgetid.c'
 )
Index: uspace/app/hbench/syscall/taskgetid.c
===================================================================
--- uspace/app/hbench/syscall/taskgetid.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/hbench/syscall/taskgetid.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,74 @@
+/*
+ * 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 hbench
+ * @{
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <str_error.h>
+#include <task.h>
+#include "../hbench.h"
+
+static volatile task_id_t tid;
+
+static bool setup(bench_env_t *env, bench_run_t *run)
+{
+	return true;
+}
+
+static bool teardown(bench_env_t *env, bench_run_t *run)
+{
+	return true;
+}
+
+static bool runner(bench_env_t *env, bench_run_t *run, uint64_t niter)
+{
+	bench_run_start(run);
+
+	for (uint64_t count = 0; count < niter; count++) {
+		tid = task_get_id();
+		(void)tid;
+	}
+
+	bench_run_stop(run);
+
+	return true;
+}
+
+benchmark_t benchmark_taskgetid = {
+	.name = "taskgetid",
+	.desc = "task_get_id system call benchmark",
+	.entry = &runner,
+	.setup = &setup,
+	.teardown = &teardown
+};
+
+/** @}
+ */
Index: uspace/app/hello/hello.c
===================================================================
--- uspace/app/hello/hello.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/hello/hello.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -152,5 +152,5 @@
 int main(int argc, char *argv[])
 {
-	const char *display_spec = UI_DISPLAY_DEFAULT;
+	const char *display_spec = UI_ANY_DEFAULT;
 	errno_t rc;
 	int i;
Index: uspace/app/init/init.c
===================================================================
--- uspace/app/init/init.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/init/init.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -476,6 +476,5 @@
 		rc = display_server();
 		if (rc == EOK) {
-			app_start("/app/launcher", NULL);
-			app_start("/app/barber", NULL);
+			app_start("/app/taskbar", NULL);
 			app_start("/app/terminal", "-topleft");
 		}
Index: uspace/app/init/untar.c
===================================================================
--- uspace/app/init/untar.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/init/untar.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2018 Martin Decky
  * All rights reserved.
@@ -59,5 +60,5 @@
 		return ret;
 
-	ret = block_init(state->sid, 4096);
+	ret = block_init(state->sid);
 	if (ret != EOK)
 		return ret;
Index: pace/app/launcher/doc/doxygroups.h
===================================================================
--- uspace/app/launcher/doc/doxygroups.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(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 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,365 +1,0 @@
-/*
- * Copyright (c) 2020 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 char *display_spec = UI_DISPLAY_DEFAULT;
-
-static void wnd_close(ui_window_t *, void *);
-
-static ui_window_cb_t window_cb = {
-	.close = wnd_close
-};
-
-static void pb_clicked(ui_pbutton_t *, void *);
-
-static ui_pbutton_cb_t pbutton_cb = {
-	.clicked = pb_clicked
-};
-
-static int app_launch(const char *, 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);
-}
-
-/** 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_launch("/app/terminal", NULL);
-	} else if (pbutton == launcher->pb2) {
-		app_launch("/app/calculator", NULL);
-	} else if (pbutton == launcher->pb3) {
-		app_launch("/app/uidemo", NULL);
-	} else if (pbutton == launcher->pb4) {
-		app_launch("/app/gfxdemo", "ui");
-	}
-}
-
-static int app_launch(const char *app, const char *arg)
-{
-	errno_t rc;
-	task_id_t id;
-	task_wait_t wait;
-
-	if (display_spec != UI_DISPLAY_DEFAULT) {
-		printf("%s: Spawning %s -d %s\n", NAME, app, display_spec);
-		rc = task_spawnl(&id, &wait, app, app, "-d", display_spec,
-		    arg, NULL);
-	} else {
-		printf("%s: Spawning %s\n", NAME, app);
-		rc = task_spawnl(&id, &wait, app, app, arg, NULL);
-	}
-
-	if (rc != EOK) {
-		printf("%s: Error spawning %s %s (%s)\n", NAME, app,
-		    display_spec != DISPLAY_DEFAULT ? display_spec :
-		    "<default>", str_error(rc));
-		return -1;
-	}
-
-	task_exit_t texit;
-	int retval;
-	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;
-	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;
-			}
-
-			display_spec = argv[i++];
-		} else {
-			printf("Invalid option '%s'.\n", argv[i]);
-			print_syntax();
-			return 1;
-		}
-	}
-
-	rc = ui_create(display_spec, &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 = 300;
-
-	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;
-	}
-
-	rc = ui_pbutton_create(ui_res, "Terminal", &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 = 158;
-	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;
-	}
-
-	rc = ui_pbutton_create(ui_res, "Calculator", &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 = 170;
-	rect.p1.x = 190;
-	rect.p1.y = 198;
-	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;
-	}
-
-	rc = ui_pbutton_create(ui_res, "UI Demo", &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 = 210;
-	rect.p1.x = 190;
-	rect.p1.y = 238;
-	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;
-	}
-
-	rc = ui_pbutton_create(ui_res, "GFX Demo", &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 = 250;
-	rect.p1.x = 190;
-	rect.p1.y = 278;
-	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_window_add(window, ui_fixed_ctl(launcher.fixed));
-
-	(void) ui_res;
-	(void) app_launch;
-
-	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 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,63 +1,0 @@
-/*
- * Copyright (c) 2020 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 <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;
-} launcher_t;
-
-#endif
-
-/** @}
- */
Index: pace/app/launcher/meson.build
===================================================================
--- uspace/app/launcher/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(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 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -28,4 +28,5 @@
 
 apps = [
+	'aboutos',
 	'barber',
 	'bdsh',
@@ -39,4 +40,6 @@
 	'devctl',
 	'df',
+	'disp',
+	'display-cfg',
 	'dnscfg',
 	'dnsres',
@@ -55,5 +58,4 @@
 	'killall',
 	'kio',
-	'launcher',
 	'loc',
 	'logset',
@@ -66,7 +68,9 @@
 	'mkmfs',
 	'modplay',
+	'nav',
 	'netecho',
 	'nic',
 	'nterm',
+	'ofw',
 	'pci',
 	'ping',
@@ -78,4 +82,6 @@
 	'sysinfo',
 	'sysinst',
+	'taskbar',
+	'taskbar-cfg',
 	'taskdump',
 	'terminal',
Index: uspace/app/mkbd/meson.build
===================================================================
--- uspace/app/mkbd/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/mkbd/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -27,4 +27,4 @@
 #
 
-deps = [ 'usb', 'usbdev', 'usbhid', 'drv' ]
+deps = [ 'usb', 'usbdev', 'usbhid', 'drv', 'console' ]
 src = files('main.c')
Index: uspace/app/mkexfat/mkexfat.c
===================================================================
--- uspace/app/mkexfat/mkexfat.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/mkexfat/mkexfat.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2012 Maurizio Lombardi
  * All rights reserved.
@@ -858,5 +859,5 @@
 	}
 
-	rc = block_init(service_id, 2048);
+	rc = block_init(service_id);
 	if (rc != EOK) {
 		printf(NAME ": Error initializing libblock.\n");
Index: uspace/app/mkfat/mkfat.c
===================================================================
--- uspace/app/mkfat/mkfat.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/mkfat/mkfat.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2010 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -66,5 +66,6 @@
 	default_fat_count		= 2,
 	default_reserved_clusters	= 2,
-	default_media_descriptor	= 0xF8 /**< fixed disk */
+	default_media_descriptor	= 0xF8 /**< fixed disk */,
+	fat32_root_cluster		= 2
 };
 
@@ -198,5 +199,5 @@
 	}
 
-	rc = block_init(service_id, 2048);
+	rc = block_init(service_id);
 	if (rc != EOK)  {
 		printf(NAME ": Error initializing libblock.\n");
@@ -313,5 +314,5 @@
 	cfg->reserved_sectors = 1 + cfg->addt_res_sectors;
 
-	/* Only correct for FAT12/16 (FAT32 has root dir stored in clusters */
+	/* Only correct for FAT12/16 (FAT32 has root dir stored in clusters) */
 	rd_sectors = div_round_up(cfg->root_ent_max * DIRENT_SIZE,
 	    cfg->sector_size);
@@ -434,4 +435,9 @@
 	}
 
+	if (cfg->fat_type == FAT32) {
+		/* Root dir is stored in cluster fat32_root_cluster */
+		addr += fat32_root_cluster * cfg->sectors_per_cluster;
+	}
+
 	/* Root directory */
 	printf("Writing root directory.\n");
@@ -529,5 +535,5 @@
 		bs->fat32.ebs = 0x29;
 		bs->fat32.id = host2uint32_t_be(vsn);
-		bs->fat32.root_cluster = 2;
+		bs->fat32.root_cluster = fat32_root_cluster;
 
 		(void) fat_label_encode(&bs->fat32.label, bs_label);
Index: uspace/app/mkmfs/mkmfs.c
===================================================================
--- uspace/app/mkmfs/mkmfs.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/mkmfs/mkmfs.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2011 Maurizio Lombardi
  * All rights reserved.
@@ -211,5 +212,5 @@
 	}
 
-	rc = block_init(service_id, 2048);
+	rc = block_init(service_id);
 	if (rc != EOK)  {
 		printf(NAME ": Error initializing libblock.\n");
Index: uspace/app/modplay/meson.build
===================================================================
--- uspace/app/modplay/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/modplay/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -27,5 +27,5 @@
 #
 
-deps = [ 'trackmod', 'hound', 'pcm' ]
+deps = [ 'trackmod', 'hound', 'pcm', 'console' ]
 src = files('modplay.c')
 
Index: uspace/app/nav/doc/doxygroups.h
===================================================================
--- uspace/app/nav/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/nav/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,4 @@
+/** @addtogroup nav nav
+ * @brief Navigator
+ * @ingroup apps
+ */
Index: uspace/app/nav/main.c
===================================================================
--- uspace/app/nav/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/nav/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2021 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 nav
+ * @{
+ */
+/** @file Navigator main.
+ *
+ * HelenOS file manager.
+ */
+
+#include <stdio.h>
+#include <str.h>
+#include <ui/ui.h>
+#include "nav.h"
+
+static void print_syntax(void)
+{
+	printf("Syntax: nav [-d <display-spec>]\n");
+}
+
+int main(int argc, char *argv[])
+{
+	const char *display_spec = UI_CONSOLE_DEFAULT;
+	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 {
+			printf("Invalid option '%s'.\n", argv[i]);
+			print_syntax();
+			return 1;
+		}
+	}
+
+	if (i < argc) {
+		print_syntax();
+		return 1;
+	}
+
+	rc = navigator_run(display_spec);
+	if (rc != EOK)
+		return 1;
+
+	return 0;
+}
+
+/** @}
+ */
Index: uspace/app/nav/menu.c
===================================================================
--- uspace/app/nav/menu.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/nav/menu.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,170 @@
+/*
+ * 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 nav
+ * @{
+ */
+/** @file Navigator.
+ *
+ * HelenOS file manager.
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <ui/menu.h>
+#include <ui/menubar.h>
+#include <ui/menudd.h>
+#include <ui/menuentry.h>
+#include "menu.h"
+#include "nav.h"
+
+/** Create navigator menu.
+ *
+ * @param window Navigator window
+ * @param rmenu Place to store pointer to new menu
+ * @return EOK on success or an error code
+ */
+errno_t nav_menu_create(ui_window_t *window, nav_menu_t **rmenu)
+{
+	nav_menu_t *menu;
+	ui_menu_t *mfile;
+	ui_menu_entry_t *mopen;
+	ui_menu_entry_t *mfsep;
+	ui_menu_entry_t *mexit;
+	gfx_rect_t arect;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	menu = calloc(1, sizeof(nav_menu_t));
+	if (menu == NULL)
+		return ENOMEM;
+
+	menu->window = window;
+	menu->ui = ui_window_get_ui(window);
+
+	rc = ui_menu_bar_create(menu->ui, menu->window,
+	    &menu->menubar);
+	if (rc != EOK)
+		goto error;
+
+	rc = ui_menu_dd_create(menu->menubar, "~F~ile", NULL, &mfile);
+	if (rc != EOK)
+		goto error;
+
+	rc = ui_menu_entry_create(mfile, "~O~pen", "Enter", &mopen);
+	if (rc != EOK)
+		goto error;
+
+	ui_menu_entry_set_cb(mopen, nav_menu_file_open, (void *) menu);
+
+	rc = ui_menu_entry_sep_create(mfile, &mfsep);
+	if (rc != EOK)
+		goto error;
+
+	rc = ui_menu_entry_create(mfile, "E~x~it", "Ctrl-Q", &mexit);
+	if (rc != EOK)
+		goto error;
+
+	ui_menu_entry_set_cb(mexit, nav_menu_file_exit, (void *) menu);
+
+	ui_window_get_app_rect(menu->window, &arect);
+
+	rect.p0 = arect.p0;
+	rect.p1.x = arect.p1.x;
+	rect.p1.y = arect.p0.y + 1;
+	ui_menu_bar_set_rect(menu->menubar, &rect);
+
+	*rmenu = menu;
+	return EOK;
+error:
+	nav_menu_destroy(menu);
+	return rc;
+}
+
+/** Set navigator menu callbacks.
+ *
+ * @param menu Menu
+ * @param cb Callbacks
+ * @param arg Argument to callback functions
+ */
+void nav_menu_set_cb(nav_menu_t *menu, nav_menu_cb_t *cb, void *arg)
+{
+	menu->cb = cb;
+	menu->cb_arg = arg;
+}
+
+/** Destroy navigator menu.
+ *
+ * @param menu Menu
+ */
+void nav_menu_destroy(nav_menu_t *menu)
+{
+	if (menu->menubar != NULL)
+		ui_menu_bar_destroy(menu->menubar);
+
+	free(menu);
+}
+
+/** Return base UI control for the menu bar.
+ *
+ * @param menu Navigator menu
+ * @return UI control
+ */
+ui_control_t *nav_menu_ctl(nav_menu_t *menu)
+{
+	return ui_menu_bar_ctl(menu->menubar);
+}
+
+/** File / Open menu entry selected.
+ *
+ * @param mentry Menu entry
+ * @param arg Argument (navigator_t *)
+ */
+void nav_menu_file_open(ui_menu_entry_t *mentry, void *arg)
+{
+	nav_menu_t *menu = (nav_menu_t *)arg;
+
+	if (menu->cb != NULL && menu->cb->file_open != NULL)
+		menu->cb->file_open(menu->cb_arg);
+}
+
+/** File / Exit menu entry selected.
+ *
+ * @param mentry Menu entry
+ * @param arg Argument (navigator_t *)
+ */
+void nav_menu_file_exit(ui_menu_entry_t *mentry, void *arg)
+{
+	nav_menu_t *menu = (nav_menu_t *)arg;
+
+	if (menu->cb != NULL && menu->cb->file_exit != NULL)
+		menu->cb->file_exit(menu->cb_arg);
+}
+
+/** @}
+ */
Index: uspace/app/nav/menu.h
===================================================================
--- uspace/app/nav/menu.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/nav/menu.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2021 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 nav
+ * @{
+ */
+/**
+ * @file Navigator menu
+ */
+
+#ifndef MENU_H
+#define MENU_H
+
+#include <errno.h>
+#include <ui/menuentry.h>
+#include <ui/window.h>
+#include "types/menu.h"
+
+extern errno_t nav_menu_create(ui_window_t *, nav_menu_t **);
+extern void nav_menu_set_cb(nav_menu_t *, nav_menu_cb_t *, void *);
+extern void nav_menu_destroy(nav_menu_t *);
+extern ui_control_t *nav_menu_ctl(nav_menu_t *);
+extern void nav_menu_file_open(ui_menu_entry_t *, void *);
+extern void nav_menu_file_exit(ui_menu_entry_t *, void *);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/nav/meson.build
===================================================================
--- uspace/app/nav/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/nav/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,45 @@
+#
+# Copyright (c) 2021 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.
+#
+
+deps = [ 'ui' ]
+src = files(
+	'main.c',
+	'menu.c',
+	'nav.c',
+	'panel.c',
+)
+
+test_src = files(
+	'menu.c',
+	'nav.c',
+	'panel.c',
+	'test/main.c',
+	'test/menu.c',
+	'test/nav.c',
+	'test/panel.c',
+)
Index: uspace/app/nav/nav.c
===================================================================
--- uspace/app/nav/nav.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/nav/nav.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,342 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup nav
+ * @{
+ */
+/** @file Navigator.
+ *
+ * HelenOS file manager.
+ */
+
+#include <gfx/coord.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <str.h>
+#include <ui/fixed.h>
+#include <ui/filelist.h>
+#include <ui/resource.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include "menu.h"
+#include "nav.h"
+#include "panel.h"
+
+static void wnd_close(ui_window_t *, void *);
+static void wnd_kbd(ui_window_t *, void *, kbd_event_t *);
+
+static ui_window_cb_t window_cb = {
+	.close = wnd_close,
+	.kbd = wnd_kbd
+};
+
+static void navigator_file_open(void *);
+static void navigator_file_exit(void *);
+
+static nav_menu_cb_t navigator_menu_cb = {
+	.file_open = navigator_file_open,
+	.file_exit = navigator_file_exit
+};
+
+static void navigator_panel_activate_req(void *, panel_t *);
+
+static panel_cb_t navigator_panel_cb = {
+	.activate_req = navigator_panel_activate_req
+};
+
+/** Window close button was clicked.
+ *
+ * @param window Window
+ * @param arg Argument (navigator)
+ */
+static void wnd_close(ui_window_t *window, void *arg)
+{
+	navigator_t *navigator = (navigator_t *) arg;
+
+	ui_quit(navigator->ui);
+}
+
+/** Window keyboard event handler.
+ *
+ * @param window Window
+ * @param arg Argument (navigator)
+ * @param event Keyboard event
+ */
+static void wnd_kbd(ui_window_t *window, void *arg, kbd_event_t *event)
+{
+	navigator_t *navigator = (navigator_t *) arg;
+
+	if (event->type == KEY_PRESS &&
+	    ((event->mods & KM_ALT) == 0) &&
+	    ((event->mods & KM_SHIFT) == 0) &&
+	    (event->mods & KM_CTRL) != 0) {
+		switch (event->key) {
+		case KC_Q:
+			ui_quit(navigator->ui);
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (event->type == KEY_PRESS &&
+	    ((event->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0)) {
+		switch (event->key) {
+		case KC_TAB:
+			navigator_switch_panel(navigator);
+			break;
+		default:
+			break;
+		}
+	}
+
+	ui_window_def_kbd(window, event);
+}
+
+/** Create navigator.
+ *
+ * @param display_spec Display specification
+ * @param rnavigator Place to store pointer to new navigator
+ * @return EOK on success or ane error code
+ */
+errno_t navigator_create(const char *display_spec,
+    navigator_t **rnavigator)
+{
+	navigator_t *navigator;
+	ui_wnd_params_t params;
+	gfx_rect_t rect;
+	gfx_rect_t arect;
+	gfx_coord_t pw;
+	unsigned i;
+	errno_t rc;
+
+	navigator = calloc(1, sizeof(navigator_t));
+	if (navigator == NULL)
+		return ENOMEM;
+
+	rc = ui_create(display_spec, &navigator->ui);
+	if (rc != EOK) {
+		printf("Error creating UI on display %s.\n", display_spec);
+		goto error;
+	}
+
+	ui_wnd_params_init(&params);
+	params.caption = "Navigator";
+	params.style &= ~ui_wds_decorated;
+	params.placement = ui_wnd_place_full_screen;
+
+	rc = ui_window_create(navigator->ui, &params, &navigator->window);
+	if (rc != EOK) {
+		printf("Error creating window.\n");
+		goto error;
+	}
+
+	ui_window_set_cb(navigator->window, &window_cb, (void *) navigator);
+	ui_window_get_app_rect(navigator->window, &arect);
+
+	rc = ui_fixed_create(&navigator->fixed);
+	if (rc != EOK) {
+		printf("Error creating fixed layout.\n");
+		goto error;
+	}
+
+	ui_window_add(navigator->window, ui_fixed_ctl(navigator->fixed));
+
+	rc = nav_menu_create(navigator->window, &navigator->menu);
+	if (rc != EOK)
+		goto error;
+
+	nav_menu_set_cb(navigator->menu, &navigator_menu_cb,
+	    (void *)navigator);
+
+	rc = ui_fixed_add(navigator->fixed, nav_menu_ctl(navigator->menu));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		return rc;
+	}
+
+	/* Panel width */
+	pw = (arect.p1.x - arect.p0.x) / 2;
+
+	for (i = 0; i < 2; i++) {
+		rc = panel_create(navigator->window, i == 0,
+		    &navigator->panel[i]);
+		if (rc != EOK)
+			goto error;
+
+		rect.p0.x = arect.p0.x + pw * i;
+		rect.p0.y = arect.p0.y + 1;
+		rect.p1.x = arect.p0.x + pw * (i + 1);
+		rect.p1.y = arect.p1.y - 1;
+		panel_set_rect(navigator->panel[i], &rect);
+
+		panel_set_cb(navigator->panel[i], &navigator_panel_cb,
+		    navigator);
+
+		rc = ui_fixed_add(navigator->fixed,
+		    panel_ctl(navigator->panel[i]));
+		if (rc != EOK) {
+			printf("Error adding control to layout.\n");
+			goto error;
+		}
+
+		rc = panel_read_dir(navigator->panel[i], ".");
+		if (rc != EOK) {
+			printf("Error reading directory.\n");
+			goto error;
+		}
+	}
+
+	rc = ui_window_paint(navigator->window);
+	if (rc != EOK) {
+		printf("Error painting window.\n");
+		goto error;
+	}
+
+	*rnavigator = navigator;
+	return EOK;
+error:
+	navigator_destroy(navigator);
+	return rc;
+}
+
+void navigator_destroy(navigator_t *navigator)
+{
+	unsigned i;
+
+	for (i = 0; i < 2; i++) {
+		if (navigator->panel[i] != NULL) {
+			ui_fixed_remove(navigator->fixed,
+			    panel_ctl(navigator->panel[i]));
+			panel_destroy(navigator->panel[i]);
+		}
+	}
+
+	if (navigator->menu != NULL) {
+		ui_fixed_remove(navigator->fixed, nav_menu_ctl(navigator->menu));
+		nav_menu_destroy(navigator->menu);
+	}
+
+	if (navigator->window != NULL)
+		ui_window_destroy(navigator->window);
+	if (navigator->ui != NULL)
+		ui_destroy(navigator->ui);
+	free(navigator);
+}
+
+/** Run navigator on the specified display. */
+errno_t navigator_run(const char *display_spec)
+{
+	navigator_t *navigator;
+	errno_t rc;
+
+	rc = navigator_create(display_spec, &navigator);
+	if (rc != EOK)
+		return rc;
+
+	ui_run(navigator->ui);
+
+	navigator_destroy(navigator);
+	return EOK;
+}
+
+/** Get the currently active navigator panel.
+ *
+ * @param navigator Navigator
+ * @return Currently active panel
+ */
+panel_t *navigator_get_active_panel(navigator_t *navigator)
+{
+	int i;
+
+	for (i = 0; i < navigator_panels; i++) {
+		if (panel_is_active(navigator->panel[i]))
+			return navigator->panel[i];
+	}
+
+	/* This should not happen */
+	assert(false);
+	return NULL;
+}
+
+/** Switch to another navigator panel.
+ *
+ * Changes the currently active navigator panel to the next panel.
+ *
+ * @param navigator Navigator
+ */
+void navigator_switch_panel(navigator_t *navigator)
+{
+	errno_t rc;
+
+	if (panel_is_active(navigator->panel[0])) {
+		rc = panel_activate(navigator->panel[1]);
+		if (rc != EOK)
+			return;
+		panel_deactivate(navigator->panel[0]);
+	} else {
+		rc = panel_activate(navigator->panel[0]);
+		if (rc != EOK)
+			return;
+		panel_deactivate(navigator->panel[1]);
+	}
+}
+
+/** File / Open menu entry selected */
+static void navigator_file_open(void *arg)
+{
+	navigator_t *navigator = (navigator_t *)arg;
+	panel_t *panel;
+
+	panel = navigator_get_active_panel(navigator);
+	ui_file_list_open(panel->flist, ui_file_list_get_cursor(panel->flist));
+}
+
+/** File / Exit menu entry selected */
+static void navigator_file_exit(void *arg)
+{
+	navigator_t *navigator = (navigator_t *)arg;
+
+	ui_quit(navigator->ui);
+}
+
+/** Panel callback requesting panel activation.
+ *
+ * @param arg Argument (navigator_t *)
+ * @param panel Panel
+ */
+void navigator_panel_activate_req(void *arg, panel_t *panel)
+{
+	navigator_t *navigator = (navigator_t *)arg;
+
+	if (!panel_is_active(panel))
+		navigator_switch_panel(navigator);
+}
+
+/** @}
+ */
Index: uspace/app/nav/nav.h
===================================================================
--- uspace/app/nav/nav.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/nav/nav.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2021 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 nav
+ * @{
+ */
+/**
+ * @file Navigator
+ */
+
+#ifndef NAV_H
+#define NAV_H
+
+#include <errno.h>
+#include "types/nav.h"
+#include "types/panel.h"
+
+extern errno_t navigator_create(const char *, navigator_t **);
+extern void navigator_destroy(navigator_t *);
+extern errno_t navigator_run(const char *);
+extern panel_t *navigator_get_active_panel(navigator_t *);
+extern void navigator_switch_panel(navigator_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/nav/panel.c
===================================================================
--- uspace/app/nav/panel.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/nav/panel.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,452 @@
+/*
+ * 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 nav
+ * @{
+ */
+/** @file Navigator panel.
+ *
+ * Displays a file listing.
+ */
+
+#include <errno.h>
+#include <gfx/render.h>
+#include <gfx/text.h>
+#include <stdlib.h>
+#include <task.h>
+#include <ui/control.h>
+#include <ui/filelist.h>
+#include <ui/paint.h>
+#include <ui/resource.h>
+#include "panel.h"
+#include "nav.h"
+
+static void panel_ctl_destroy(void *);
+static errno_t panel_ctl_paint(void *);
+static ui_evclaim_t panel_ctl_kbd_event(void *, kbd_event_t *);
+static ui_evclaim_t panel_ctl_pos_event(void *, pos_event_t *);
+
+/** Panel control ops */
+static ui_control_ops_t panel_ctl_ops = {
+	.destroy = panel_ctl_destroy,
+	.paint = panel_ctl_paint,
+	.kbd_event = panel_ctl_kbd_event,
+	.pos_event = panel_ctl_pos_event
+};
+
+static void panel_flist_activate_req(ui_file_list_t *, void *);
+static void panel_flist_selected(ui_file_list_t *, void *, const char *);
+
+/** Panel file list callbacks */
+static ui_file_list_cb_t panel_flist_cb = {
+	.activate_req = panel_flist_activate_req,
+	.selected = panel_flist_selected,
+};
+
+/** Create panel.
+ *
+ * @param window Containing window
+ * @param active @c true iff panel should be active
+ * @param rpanel Place to store pointer to new panel
+ * @return EOK on success or an error code
+ */
+errno_t panel_create(ui_window_t *window, bool active, panel_t **rpanel)
+{
+	panel_t *panel;
+	errno_t rc;
+
+	panel = calloc(1, sizeof(panel_t));
+	if (panel == NULL)
+		return ENOMEM;
+
+	rc = ui_control_new(&panel_ctl_ops, (void *)panel,
+	    &panel->control);
+	if (rc != EOK) {
+		free(panel);
+		return rc;
+	}
+
+	rc = gfx_color_new_ega(0x07, &panel->color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_color_new_ega(0x0f, &panel->act_border_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = ui_file_list_create(window, active, &panel->flist);
+	if (rc != EOK)
+		goto error;
+
+	ui_file_list_set_cb(panel->flist, &panel_flist_cb, (void *)panel);
+
+	panel->window = window;
+	panel->active = active;
+	*rpanel = panel;
+	return EOK;
+error:
+	if (panel->color != NULL)
+		gfx_color_delete(panel->color);
+	if (panel->act_border_color != NULL)
+		gfx_color_delete(panel->act_border_color);
+	if (panel->flist != NULL)
+		ui_file_list_destroy(panel->flist);
+	ui_control_delete(panel->control);
+	free(panel);
+	return rc;
+}
+
+/** Destroy panel.
+ *
+ * @param panel Panel
+ */
+void panel_destroy(panel_t *panel)
+{
+	gfx_color_delete(panel->color);
+	gfx_color_delete(panel->act_border_color);
+	ui_control_delete(panel->control);
+	free(panel);
+}
+
+/** Set panel callbacks.
+ *
+ * @param panel Panel
+ * @param cb Callbacks
+ * @param arg Argument to callback functions
+ */
+void panel_set_cb(panel_t *panel, panel_cb_t *cb, void *arg)
+{
+	panel->cb = cb;
+	panel->cb_arg = arg;
+}
+
+/** Paint panel.
+ *
+ * @param panel Panel
+ */
+errno_t panel_paint(panel_t *panel)
+{
+	gfx_context_t *gc = ui_window_get_gc(panel->window);
+	ui_resource_t *res = ui_window_get_res(panel->window);
+	ui_box_style_t bstyle;
+	gfx_color_t *bcolor;
+	ui_control_t *ctl;
+	errno_t rc;
+
+	rc = gfx_set_color(gc, panel->color);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_fill_rect(gc, &panel->rect);
+	if (rc != EOK)
+		return rc;
+
+	if (panel->active) {
+		bstyle = ui_box_double;
+		bcolor = panel->act_border_color;
+	} else {
+		bstyle = ui_box_single;
+		bcolor = panel->color;
+	}
+
+	rc = ui_paint_text_box(res, &panel->rect, bstyle, bcolor);
+	if (rc != EOK)
+		return rc;
+
+	ctl = ui_file_list_ctl(panel->flist);
+	rc = ui_control_paint(ctl);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_update(gc);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Handle panel keyboard event.
+ *
+ * @param panel Panel
+ * @param event Keyboard event
+ * @return ui_claimed iff event was claimed
+ */
+ui_evclaim_t panel_kbd_event(panel_t *panel, kbd_event_t *event)
+{
+	ui_control_t *ctl;
+
+	if (!panel->active)
+		return ui_unclaimed;
+
+	ctl = ui_file_list_ctl(panel->flist);
+	return ui_control_kbd_event(ctl, event);
+}
+
+/** Handle panel position event.
+ *
+ * @param panel Panel
+ * @param event Position event
+ * @return ui_claimed iff event was claimed
+ */
+ui_evclaim_t panel_pos_event(panel_t *panel, pos_event_t *event)
+{
+	gfx_coord2_t pos;
+	ui_control_t *ctl;
+	ui_evclaim_t claim;
+
+	pos.x = event->hpos;
+	pos.y = event->vpos;
+	if (!gfx_pix_inside_rect(&pos, &panel->rect))
+		return ui_unclaimed;
+
+	ctl = ui_file_list_ctl(panel->flist);
+	claim = ui_control_pos_event(ctl, event);
+	if (claim == ui_claimed)
+		return ui_claimed;
+
+	if (!panel->active && event->type == POS_PRESS)
+		panel_activate_req(panel);
+
+	return ui_claimed;
+}
+
+/** Get base control for panel.
+ *
+ * @param panel Panel
+ * @return Base UI control
+ */
+ui_control_t *panel_ctl(panel_t *panel)
+{
+	return panel->control;
+}
+
+/** Set panel rectangle.
+ *
+ * @param panel Panel
+ * @param rect Rectangle
+ */
+void panel_set_rect(panel_t *panel, gfx_rect_t *rect)
+{
+	gfx_rect_t irect;
+
+	panel->rect = *rect;
+
+	irect.p0.x = panel->rect.p0.x + 1;
+	irect.p0.y = panel->rect.p0.y + 1;
+	irect.p1.x = panel->rect.p1.x;
+	irect.p1.y = panel->rect.p1.y - 1;
+
+	ui_file_list_set_rect(panel->flist, &irect);
+}
+
+/** Determine if panel is active.
+ *
+ * @param panel Panel
+ * @return @c true iff panel is active
+ */
+bool panel_is_active(panel_t *panel)
+{
+	return panel->active;
+}
+
+/** Activate panel.
+ *
+ * @param panel Panel
+ *
+ * @return EOK on success or an error code
+ */
+errno_t panel_activate(panel_t *panel)
+{
+	errno_t rc;
+
+	rc = ui_file_list_activate(panel->flist);
+	if (rc != EOK)
+		return rc;
+
+	panel->active = true;
+	(void) panel_paint(panel);
+	return EOK;
+}
+
+/** Deactivate panel.
+ *
+ * @param panel Panel
+ */
+void panel_deactivate(panel_t *panel)
+{
+	ui_file_list_deactivate(panel->flist);
+	panel->active = false;
+	(void) panel_paint(panel);
+}
+
+/** Destroy panel control.
+ *
+ * @param arg Argument (panel_t *)
+ */
+void panel_ctl_destroy(void *arg)
+{
+	panel_t *panel = (panel_t *) arg;
+
+	panel_destroy(panel);
+}
+
+/** Paint panel control.
+ *
+ * @param arg Argument (panel_t *)
+ * @return EOK on success or an error code
+ */
+errno_t panel_ctl_paint(void *arg)
+{
+	panel_t *panel = (panel_t *) arg;
+
+	return panel_paint(panel);
+}
+
+/** Handle panel control keyboard event.
+ *
+ * @param arg Argument (panel_t *)
+ * @param kbd_event Keyboard event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t panel_ctl_kbd_event(void *arg, kbd_event_t *event)
+{
+	panel_t *panel = (panel_t *) arg;
+
+	return panel_kbd_event(panel, event);
+}
+
+/** Handle panel control position event.
+ *
+ * @param arg Argument (panel_t *)
+ * @param pos_event Position event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t panel_ctl_pos_event(void *arg, pos_event_t *event)
+{
+	panel_t *panel = (panel_t *) arg;
+
+	return panel_pos_event(panel, event);
+}
+
+/** Read directory into panel entry list.
+ *
+ * @param panel Panel
+ * @param dirname Directory path
+ * @return EOK on success or an error code
+ */
+errno_t panel_read_dir(panel_t *panel, const char *dirname)
+{
+	return ui_file_list_read_dir(panel->flist, dirname);
+}
+
+/** Request panel activation.
+ *
+ * Call back to request panel activation.
+ *
+ * @param panel Panel
+ */
+void panel_activate_req(panel_t *panel)
+{
+	if (panel->cb != NULL && panel->cb->activate_req != NULL)
+		panel->cb->activate_req(panel->cb_arg, panel);
+}
+
+/** Open panel file entry.
+ *
+ * Perform Open action on a file entry (i.e. try running it).
+ *
+ * @param panel Panel
+ * @param fname File name
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t panel_open_file(panel_t *panel, const char *fname)
+{
+	task_id_t id;
+	task_wait_t wait;
+	task_exit_t texit;
+	int retval;
+	errno_t rc;
+	ui_t *ui;
+
+	ui = ui_window_get_ui(panel->window);
+
+	/* Free up and clean console for the child task. */
+	rc = ui_suspend(ui);
+	if (rc != EOK)
+		return rc;
+
+	rc = task_spawnl(&id, &wait, fname, fname, NULL);
+	if (rc != EOK)
+		goto error;
+
+	rc = task_wait(&wait, &texit, &retval);
+	if ((rc != EOK) || (texit != TASK_EXIT_NORMAL))
+		goto error;
+
+	/* Resume UI operation */
+	rc = ui_resume(ui);
+	if (rc != EOK)
+		return rc;
+
+	(void) ui_paint(ui_window_get_ui(panel->window));
+	return EOK;
+error:
+	(void) ui_resume(ui);
+	(void) ui_paint(ui_window_get_ui(panel->window));
+	return rc;
+}
+
+/** File list in panel requests activation.
+ *
+ * @param flist File list
+ * @param arg Argument (panel_t *)
+ */
+static void panel_flist_activate_req(ui_file_list_t *flist, void *arg)
+{
+	panel_t *panel = (panel_t *)arg;
+
+	panel_activate_req(panel);
+}
+
+/** File in panel file list was selected.
+ *
+ * @param flist File list
+ * @param arg Argument (panel_t *)
+ * @param fname File name
+ */
+static void panel_flist_selected(ui_file_list_t *flist, void *arg,
+    const char *fname)
+{
+	panel_t *panel = (panel_t *)arg;
+
+	(void) panel_open_file(panel, fname);
+}
+
+/** @}
+ */
Index: uspace/app/nav/panel.h
===================================================================
--- uspace/app/nav/panel.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/nav/panel.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup nav
+ * @{
+ */
+/**
+ * @file Navigator panel
+ */
+
+#ifndef PANEL_H
+#define PANEL_H
+
+#include <errno.h>
+#include <gfx/coord.h>
+#include <io/kbd_event.h>
+#include <io/pos_event.h>
+#include <ui/control.h>
+#include <ui/window.h>
+#include <stdbool.h>
+#include "types/panel.h"
+
+extern errno_t panel_create(ui_window_t *, bool, panel_t **);
+extern void panel_destroy(panel_t *);
+extern void panel_set_cb(panel_t *, panel_cb_t *, void *);
+extern errno_t panel_paint(panel_t *);
+extern ui_evclaim_t panel_kbd_event(panel_t *, kbd_event_t *);
+extern ui_evclaim_t panel_pos_event(panel_t *, pos_event_t *);
+extern ui_control_t *panel_ctl(panel_t *);
+extern void panel_set_rect(panel_t *, gfx_rect_t *);
+extern bool panel_is_active(panel_t *);
+extern errno_t panel_activate(panel_t *);
+extern void panel_deactivate(panel_t *);
+extern errno_t panel_read_dir(panel_t *, const char *);
+extern void panel_activate_req(panel_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/nav/test/main.c
===================================================================
--- uspace/app/nav/test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/nav/test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2021 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <pcut/pcut.h>
+
+PCUT_INIT;
+
+PCUT_IMPORT(menu);
+PCUT_IMPORT(nav);
+PCUT_IMPORT(panel);
+
+PCUT_MAIN();
Index: uspace/app/nav/test/menu.c
===================================================================
--- uspace/app/nav/test/menu.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/nav/test/menu.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2021 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <pcut/pcut.h>
+#include <stdbool.h>
+#include "../menu.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(menu);
+
+static void test_menu_file_open(void *);
+static void test_menu_file_exit(void *);
+
+static nav_menu_cb_t dummy_cb;
+static nav_menu_cb_t test_cb = {
+	.file_open = test_menu_file_open,
+	.file_exit = test_menu_file_exit
+};
+
+/** Test response */
+typedef struct {
+	bool file_open;
+	bool file_exit;
+} test_resp_t;
+
+/** Create and destroy menu. */
+PCUT_TEST(create_destroy)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	nav_menu_t *menu;
+	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 = nav_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	nav_menu_destroy(menu);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** nav_menu_set_cb() */
+PCUT_TEST(set_cb)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	nav_menu_t *menu;
+	int foo;
+	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 = nav_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	nav_menu_set_cb(menu, &test_cb, &foo);
+	PCUT_ASSERT_EQUALS(&test_cb, menu->cb);
+	PCUT_ASSERT_EQUALS(&foo, menu->cb_arg);
+
+	nav_menu_destroy(menu);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** File / Open callback */
+PCUT_TEST(file_open)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	nav_menu_t *menu;
+	test_resp_t resp;
+	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 = nav_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Call back with no callbacks set */
+	nav_menu_file_open(NULL, menu);
+
+	/* Call back with dummy callbacks set */
+	nav_menu_set_cb(menu, &dummy_cb, &resp);
+	nav_menu_file_open(NULL, menu);
+
+	/* Call back with test callbacks set */
+	resp.file_open = false;
+	nav_menu_set_cb(menu, &test_cb, &resp);
+	nav_menu_file_open(NULL, menu);
+	PCUT_ASSERT_TRUE(resp.file_open);
+
+	nav_menu_destroy(menu);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Testing File / Open callback */
+static void test_menu_file_open(void *arg)
+{
+	test_resp_t *resp = (test_resp_t *)arg;
+
+	resp->file_open = true;
+}
+
+/** Testing File / Exit callback */
+static void test_menu_file_exit(void *arg)
+{
+	test_resp_t *resp = (test_resp_t *)arg;
+
+	resp->file_exit = true;
+}
+
+PCUT_EXPORT(menu);
Index: uspace/app/nav/test/nav.c
===================================================================
--- uspace/app/nav/test/nav.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/nav/test/nav.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2021 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <pcut/pcut.h>
+#include "../nav.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(nav);
+
+/** Create and destroy navigator. */
+PCUT_TEST(create_destroy)
+{
+	navigator_t *nav;
+	errno_t rc;
+
+	rc = navigator_create(UI_DISPLAY_NULL, &nav);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	navigator_destroy(nav);
+}
+
+/** navigator_get_active_panel() returns the active panel */
+PCUT_TEST(get_active_panel)
+{
+	navigator_t *nav;
+	panel_t *panel;
+	errno_t rc;
+
+	rc = navigator_create(UI_DISPLAY_NULL, &nav);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* First panel should be active at the beginning */
+	panel = navigator_get_active_panel(nav);
+	PCUT_ASSERT_EQUALS(nav->panel[0], panel);
+
+	navigator_destroy(nav);
+}
+
+/** navigator_switch_panel() switches to a different panel */
+PCUT_TEST(switch_panel)
+{
+	navigator_t *nav;
+	panel_t *panel;
+	errno_t rc;
+
+	rc = navigator_create(UI_DISPLAY_NULL, &nav);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* First panel should be active at the beginning */
+	panel = navigator_get_active_panel(nav);
+	PCUT_ASSERT_EQUALS(nav->panel[0], panel);
+
+	navigator_switch_panel(nav);
+
+	/* Second panel should be active now */
+	panel = navigator_get_active_panel(nav);
+	PCUT_ASSERT_EQUALS(nav->panel[1], panel);
+
+	navigator_switch_panel(nav);
+
+	/* First panel should be active again */
+	panel = navigator_get_active_panel(nav);
+	PCUT_ASSERT_EQUALS(nav->panel[0], panel);
+
+	navigator_destroy(nav);
+}
+
+PCUT_EXPORT(nav);
Index: uspace/app/nav/test/panel.c
===================================================================
--- uspace/app/nav/test/panel.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/nav/test/panel.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,402 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <io/kbd_event.h>
+#include <io/pos_event.h>
+#include <pcut/pcut.h>
+#include <stdio.h>
+#include <ui/ui.h>
+#include <vfs/vfs.h>
+#include "../panel.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(panel);
+
+/** Test response */
+typedef struct {
+	bool activate_req;
+	panel_t *activate_req_panel;
+} test_resp_t;
+
+static void test_panel_activate_req(void *, panel_t *);
+
+static panel_cb_t test_cb = {
+	.activate_req = test_panel_activate_req
+};
+
+/** Create and destroy panel. */
+PCUT_TEST(create_destroy)
+{
+	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);
+}
+
+/** panel_set_cb() sets callback */
+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 = 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_set_cb(panel, &test_cb, &resp);
+	PCUT_ASSERT_EQUALS(&test_cb, panel->cb);
+	PCUT_ASSERT_EQUALS(&resp, panel->cb_arg);
+
+	panel_destroy(panel);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test panel_paint() */
+PCUT_TEST(paint)
+{
+	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);
+
+	rc = panel_paint(panel);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	panel_destroy(panel);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** panel_ctl() returns a valid UI control */
+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 = 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);
+
+	control = panel_ctl(panel);
+	PCUT_ASSERT_NOT_NULL(control);
+
+	panel_destroy(panel);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test panel_kbd_event() */
+PCUT_TEST(kbd_event)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	panel_t *panel;
+	ui_evclaim_t claimed;
+	kbd_event_t event;
+	errno_t rc;
+
+	/* Active panel should claim events */
+
+	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);
+
+	event.type = KEY_PRESS;
+	event.key = KC_ESCAPE;
+	event.mods = 0;
+	event.c = '\0';
+
+	claimed = panel_kbd_event(panel, &event);
+	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
+
+	panel_destroy(panel);
+
+	/* Inactive panel should not claim events */
+
+	rc = panel_create(window, false, &panel);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	event.type = KEY_PRESS;
+	event.key = KC_ESCAPE;
+	event.mods = 0;
+	event.c = '\0';
+
+	claimed = panel_kbd_event(panel, &event);
+	PCUT_ASSERT_EQUALS(ui_unclaimed, claimed);
+
+	panel_destroy(panel);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test panel_pos_event() */
+PCUT_TEST(pos_event)
+{
+}
+
+/** panel_set_rect() sets internal field */
+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 = 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);
+
+	rect.p0.x = 1;
+	rect.p0.y = 2;
+	rect.p1.x = 3;
+	rect.p1.y = 4;
+
+	panel_set_rect(panel, &rect);
+	PCUT_ASSERT_INT_EQUALS(rect.p0.x, panel->rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(rect.p0.y, panel->rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(rect.p1.x, panel->rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(rect.p1.y, panel->rect.p1.y);
+
+	panel_destroy(panel);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** panel_is_active() returns panel activity state */
+PCUT_TEST(is_active)
+{
+	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(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);
+}
+
+/** panel_activate() activates panel */
+PCUT_TEST(activate)
+{
+	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, false, &panel);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(panel_is_active(panel));
+	rc = panel_activate(panel);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_TRUE(panel_is_active(panel));
+
+	panel_destroy(panel);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** panel_deactivate() deactivates panel */
+PCUT_TEST(deactivate)
+{
+	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_deactivate(panel);
+	PCUT_ASSERT_FALSE(panel_is_active(panel));
+
+	panel_destroy(panel);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** panel_activate_req() sends activation request */
+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 = 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_set_cb(panel, &test_cb, &resp);
+
+	resp.activate_req = false;
+	resp.activate_req_panel = NULL;
+
+	panel_activate_req(panel);
+	PCUT_ASSERT_TRUE(resp.activate_req);
+	PCUT_ASSERT_EQUALS(panel, resp.activate_req_panel);
+
+	panel_destroy(panel);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+static void test_panel_activate_req(void *arg, panel_t *panel)
+{
+	test_resp_t *resp = (test_resp_t *)arg;
+
+	resp->activate_req = true;
+	resp->activate_req_panel = panel;
+}
+
+PCUT_EXPORT(panel);
Index: uspace/app/nav/types/menu.h
===================================================================
--- uspace/app/nav/types/menu.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/nav/types/menu.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2021 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 nav
+ * @{
+ */
+/**
+ * @file Navigator menu types
+ */
+
+#ifndef TYPES_MENU_H
+#define TYPES_MENU_H
+
+#include <ui/menubar.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+
+/** Navigator menu callbacks */
+typedef struct nav_menu_cb {
+	/** File / Open */
+	void (*file_open)(void *);
+	/** File / Exit */
+	void (*file_exit)(void *);
+} nav_menu_cb_t;
+
+/** Navigator menu */
+typedef struct nav_menu {
+	/** UI */
+	ui_t *ui;
+	/** Containing window */
+	ui_window_t *window;
+	/** Menu bar */
+	ui_menu_bar_t *menubar;
+	/** Callbacks */
+	nav_menu_cb_t *cb;
+	/** Callback argument */
+	void *cb_arg;
+} nav_menu_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/nav/types/nav.h
===================================================================
--- uspace/app/nav/types/nav.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/nav/types/nav.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2021 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 nav
+ * @{
+ */
+/**
+ * @file Navigator types
+ */
+
+#ifndef TYPES_NAV_H
+#define TYPES_NAV_H
+
+#include <ui/fixed.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+
+enum {
+	navigator_panels = 2
+};
+
+/** Navigator */
+typedef struct navigator {
+	/** User interface */
+	ui_t *ui;
+	/** Window */
+	ui_window_t *window;
+	/** Fixed layout */
+	ui_fixed_t *fixed;
+	/** Menu */
+	struct nav_menu *menu;
+	/** Panels */
+	struct panel *panel[navigator_panels];
+} navigator_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/nav/types/panel.h
===================================================================
--- uspace/app/nav/types/panel.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/nav/types/panel.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup nav
+ * @{
+ */
+/**
+ * @file Navigator panel types
+ */
+
+#ifndef TYPES_PANEL_H
+#define TYPES_PANEL_H
+
+#include <gfx/color.h>
+#include <gfx/coord.h>
+#include <ui/filelist.h>
+#include <ui/window.h>
+#include <stdint.h>
+
+/** Navigator panel
+ *
+ * This is a custom UI control.
+ */
+typedef struct panel {
+	/** Base control object */
+	struct ui_control *control;
+
+	/** Containing window */
+	ui_window_t *window;
+
+	/** Callbacks */
+	struct panel_cb *cb;
+
+	/** Callback argument */
+	void *cb_arg;
+
+	/** Panel rectangle */
+	gfx_rect_t rect;
+
+	/** Panel color */
+	gfx_color_t *color;
+
+	/** Active border color */
+	gfx_color_t *act_border_color;
+
+	/** @c true iff the panel is active */
+	bool active;
+
+	/** File list */
+	ui_file_list_t *flist;
+} panel_t;
+
+/** Panel callbacks */
+typedef struct panel_cb {
+	/** Request panel activation */
+	void (*activate_req)(void *, panel_t *);
+} panel_cb_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/netecho/comm.c
===================================================================
--- uspace/app/netecho/comm.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/netecho/comm.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -52,5 +52,4 @@
 static udp_t *udp;
 static udp_assoc_t *assoc;
-static inet_ep_t remote;
 
 #define RECV_BUF_SIZE 1024
@@ -185,5 +184,5 @@
 errno_t comm_send(void *data, size_t size)
 {
-	errno_t rc = udp_assoc_send_msg(assoc, &remote, data, size);
+	errno_t rc = udp_assoc_send_msg(assoc, NULL, data, size);
 	if (rc != EOK)
 		return EIO;
Index: uspace/app/netecho/meson.build
===================================================================
--- uspace/app/netecho/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/netecho/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -27,4 +27,4 @@
 #
 
-deps = [ 'inet' ]
+deps = [ 'inet', 'console' ]
 src = files('comm.c', 'netecho.c')
Index: uspace/app/nterm/meson.build
===================================================================
--- uspace/app/nterm/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/nterm/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -27,4 +27,4 @@
 #
 
-deps = [ 'inet' ]
+deps = [ 'inet', 'console' ]
 src = files('conn.c', 'nterm.c')
Index: uspace/app/ofw/doc/doxygroups.h
===================================================================
--- uspace/app/ofw/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/ofw/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,4 @@
+/** @addtogroup ofw ofw
+ * @brief Tool for printing the OpenFirmware device tree
+ * @ingroup apps
+ */
Index: uspace/app/ofw/meson.build
===================================================================
--- uspace/app/ofw/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/ofw/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,32 @@
+#
+# 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.
+#
+
+platform_specific = true
+
+deps = [ 'ofw' ]
+src = files('ofw.c')
Index: uspace/app/ofw/ofw.c
===================================================================
--- uspace/app/ofw/ofw.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/ofw/ofw.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,182 @@
+/*
+ * 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 ofw
+ * @{
+ */
+
+/**
+ * @file
+ * @brief Tool for printing the OpenFirmware device tree
+ */
+
+#include <errno.h>
+#include <ofw.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <str.h>
+
+#define NAME	"ofw"
+
+#define MAX_NAME_LENGTH 1024
+
+static void syntax_print(void);
+static errno_t ofw_print_subtree(const char *, bool);
+static errno_t ofw_print_properties(const char *);
+
+int main(int argc, char **argv)
+{
+	errno_t rc;
+	const char *path = "/";
+	bool verbose = false;
+
+	--argc;
+	++argv;
+
+	while (argc > 0 && argv[0][0] == '-') {
+		if (str_cmp(argv[0], "-p") == 0) {
+			--argc;
+			++argv;
+			if (argc < 1) {
+				printf("Option argument missing.\n");
+				return 1;
+			}
+
+			path = argv[0];
+			--argc;
+			++argv;
+		} else if (str_cmp(argv[0], "-v") == 0) {
+			--argc;
+			++argv;
+
+			verbose = true;
+		} else {
+			syntax_print();
+			return 1;
+		}
+	}
+
+	if (argc != 0) {
+		syntax_print();
+		return 1;
+	}
+
+	rc = ofw_print_subtree(path, verbose);
+	if (rc != EOK)
+		return 1;
+
+	return 0;
+}
+
+static void syntax_print(void)
+{
+	printf("syntax: %s [<options>]\n", NAME);
+	printf("options:\n"
+	    "\t-v        Verbose mode (print properties and their values)\n"
+	    "\t-p <path> Only print devices under <path>\n");
+}
+
+/** List OpenFirmware device nodes under a specific node.
+ *
+ * @param path Path of node where to start printing
+ * @param verbose If @c true, print properties
+ * @return EOK on success or an error code
+ */
+static errno_t ofw_print_subtree(const char *path, bool verbose)
+{
+	char *subpath;
+	ofw_child_it_t it;
+	errno_t rc;
+
+	printf("%s\n", path);
+
+	if (verbose) {
+		rc = ofw_print_properties(path);
+		if (rc != EOK)
+			return rc;
+	}
+
+	rc = ofw_child_it_first(&it, path);
+	while (!ofw_child_it_end(&it)) {
+		rc = ofw_child_it_get_path(&it, &subpath);
+		if (rc != EOK)
+			goto error;
+
+		rc = ofw_print_subtree(subpath, verbose);
+		if (rc != EOK)
+			goto error;
+
+		free(subpath);
+		subpath = NULL;
+
+		ofw_child_it_next(&it);
+	}
+
+	ofw_child_it_fini(&it);
+	return EOK;
+error:
+	if (subpath != NULL)
+		free(subpath);
+	ofw_child_it_fini(&it);
+	return rc;
+}
+
+static errno_t ofw_print_properties(const char *ofwpath)
+{
+	const char *propname;
+	const uint8_t *propval;
+	size_t val_sz;
+	ofw_prop_it_t it;
+	errno_t rc;
+	size_t i;
+
+	rc = ofw_prop_it_first(&it, ofwpath);
+	if (rc != EOK)
+		return rc;
+
+	while (!ofw_prop_it_end(&it)) {
+		propname = ofw_prop_it_get_name(&it);
+		printf("'%s' =", propname);
+
+		propval = ofw_prop_it_get_data(&it, &val_sz);
+
+		for (i = 0; i < val_sz; i++)
+			printf(" %02x", propval[i]);
+
+		printf(" ('%*s')\n", (int)val_sz - 1, (char *)propval);
+		ofw_prop_it_next(&it);
+	}
+
+	ofw_prop_it_fini(&it);
+	return EOK;
+}
+
+/**
+ * @}
+ */
Index: uspace/app/ping/meson.build
===================================================================
--- uspace/app/ping/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/ping/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -27,4 +27,4 @@
 #
 
-deps = [ 'inet' ]
+deps = [ 'inet', 'console' ]
 src = files('ping.c')
Index: uspace/app/sysinst/sysinst.c
===================================================================
--- uspace/app/sysinst/sysinst.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/sysinst/sysinst.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2018 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -62,5 +62,6 @@
  * in Grub notation).
  */
-#define DEFAULT_DEV "devices/\\hw\\sys\\00:01.0\\ata-c1\\d0"
+#define DEFAULT_DEV_0 "devices/\\hw\\sys\\00:01.1\\c0d0"
+#define DEFAULT_DEV_1 "devices/\\hw\\sys\\00:01.0\\ata1\\c0d0"
 //#define DEFAULT_DEV "devices/\\hw\\pci0\\00:01.2\\uhci_rh\\usb01_a1\\mass-storage0\\l0"
 /** Volume label for the new file system */
@@ -79,8 +80,34 @@
 #define BOOT_BLOCK_IDX 0 /* MBR */
 
+static const char *default_devs[] = {
+	DEFAULT_DEV_0,
+	DEFAULT_DEV_1,
+	NULL
+};
+
 static const char *sys_dirs[] = {
 	"/cfg",
-	"/data"
+	"/data",
+	NULL
 };
+
+/** Check the if the destination device exists.
+ *
+ * @param dev Disk device
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t sysinst_check_dev(const char *dev)
+{
+	service_id_t sid;
+	errno_t rc;
+
+	rc = loc_service_get_id(dev, &sid, 0);
+	if (rc != EOK)
+		return rc;
+
+	(void)sid;
+	return EOK;
+}
 
 /** Label the destination device.
@@ -361,5 +388,5 @@
 
 	printf("sysinst_copy_boot_blocks: block_init.\n");
-	rc = block_init(sid, 512);
+	rc = block_init(sid);
 	if (rc != EOK)
 		return rc;
@@ -490,6 +517,20 @@
 int main(int argc, char *argv[])
 {
-	const char *dev = DEFAULT_DEV;
-	return sysinst_install(dev);
+	unsigned i;
+	errno_t rc;
+
+	i = 0;
+	while (default_devs[i] != NULL) {
+		rc = sysinst_check_dev(default_devs[i]);
+		if (rc == EOK)
+			break;
+	}
+
+	if (default_devs[i] == NULL) {
+		printf("Cannot determine installation device.\n");
+		return 1;
+	}
+
+	return sysinst_install(default_devs[i]);
 }
 
Index: uspace/app/taskbar-cfg/doc/doxygroups.h
===================================================================
--- uspace/app/taskbar-cfg/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar-cfg/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,4 @@
+/** @addtogroup taskbar-cfg taskbar-cfg
+ * @brief Taskbar configuration utility (UI)
+ * @ingroup apps
+ */
Index: uspace/app/taskbar-cfg/main.c
===================================================================
--- uspace/app/taskbar-cfg/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar-cfg/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,94 @@
+/*
+ * 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 taskbar-cfg
+ * @{
+ */
+/** @file Taskbar configuration utility (UI) main
+ */
+
+#include <stdio.h>
+#include <str.h>
+#include <ui/ui.h>
+#include "taskbar-cfg.h"
+
+static void print_syntax(void)
+{
+	printf("Syntax: taskbarcfg [-d <display-spec>]\n");
+}
+
+int main(int argc, char *argv[])
+{
+	const char *display_spec = UI_ANY_DEFAULT;
+	taskbar_cfg_t *tbcfg;
+	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 {
+			printf("Invalid option '%s'.\n", argv[i]);
+			print_syntax();
+			return 1;
+		}
+	}
+
+	if (i < argc) {
+		print_syntax();
+		return 1;
+	}
+
+	rc = taskbar_cfg_create(display_spec, &tbcfg);
+	if (rc != EOK)
+		return 1;
+
+	rc = taskbar_cfg_open(tbcfg, "/cfg/taskbar.sif");
+	if (rc != EOK)
+		return 1;
+
+	rc = taskbar_cfg_populate(tbcfg);
+	if (rc != EOK)
+		return 1;
+
+	ui_run(tbcfg->ui);
+	taskbar_cfg_destroy(tbcfg);
+
+	return 0;
+}
+
+/** @}
+ */
Index: uspace/app/taskbar-cfg/meson.build
===================================================================
--- uspace/app/taskbar-cfg/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar-cfg/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,43 @@
+#
+# 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.
+#
+
+deps = [ 'tbarcfg', 'ui' ]
+src = files(
+	'taskbar-cfg.c',
+	'main.c',
+	'smeedit.c',
+	'startmenu.c'
+)
+
+#test_src = files(
+#	'taskbar-cfg.c',
+#	'startmenu.c',
+#	'test/taskbar-cfg.c',
+#	'test/main.c',
+#	'test/startmenu.c'
+#)
Index: uspace/app/taskbar-cfg/smeedit.c
===================================================================
--- uspace/app/taskbar-cfg/smeedit.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar-cfg/smeedit.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,443 @@
+/*
+ * 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 taskbar-cfg
+ * @{
+ */
+/** @file Start menu entry edit dialog
+ */
+
+#include <gfx/coord.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ui/checkbox.h>
+#include <ui/fixed.h>
+#include <ui/resource.h>
+#include <ui/window.h>
+#include "taskbar-cfg.h"
+#include "smeedit.h"
+#include "startmenu.h"
+
+static void wnd_close(ui_window_t *, void *);
+
+static ui_window_cb_t window_cb = {
+	.close = wnd_close
+};
+
+static void smeedit_ok_clicked(ui_pbutton_t *, void *);
+static void smeedit_cancel_clicked(ui_pbutton_t *, void *);
+
+/** OK button callbacks */
+ui_pbutton_cb_t smeedit_ok_button_cb = {
+	.clicked = smeedit_ok_clicked
+};
+
+/** Cancel button callbacks */
+ui_pbutton_cb_t smeedit_cancel_button_cb = {
+	.clicked = smeedit_cancel_clicked
+};
+
+/** Window close button was clicked.
+ *
+ * @param window Window
+ * @param arg Argument (tbcfg)
+ */
+static void wnd_close(ui_window_t *window, void *arg)
+{
+	smeedit_t *smee = (smeedit_t *)arg;
+
+	(void)window;
+	smeedit_destroy(smee);
+}
+
+/** Create start menu entry edit dialog.
+ *
+ * @param smenu Start menu
+ * @param smentry Start menu entry to edit or @c NULL if creating
+ *                a new entry
+ * @param rsmee Place to store pointer to new start menu entry edit window
+ * @return EOK on success or an error code
+ */
+errno_t smeedit_create(startmenu_t *smenu, startmenu_entry_t *smentry,
+    smeedit_t **rsmee)
+{
+	ui_wnd_params_t params;
+	ui_t *ui;
+	ui_window_t *window = NULL;
+	smeedit_t *smee = NULL;
+	gfx_rect_t rect;
+	ui_resource_t *res;
+	const char *cmd;
+	const char *caption;
+	bool terminal;
+	errno_t rc;
+
+	ui = smenu->tbarcfg->ui;
+
+	if (smentry != NULL) {
+		cmd = smenu_entry_get_cmd(smentry->entry);
+		caption = smenu_entry_get_caption(smentry->entry);
+		terminal = smenu_entry_get_terminal(smentry->entry);
+	} else {
+		cmd = "";
+		caption = "";
+		terminal = false;
+	}
+
+	smee = calloc(1, sizeof(smeedit_t));
+	if (smee == NULL) {
+		printf("Out of memory.\n");
+		return ENOMEM;
+	}
+
+	smee->startmenu = smenu;
+	smee->smentry = smentry;
+
+	ui_wnd_params_init(&params);
+	params.caption = smentry != NULL ? "Edit Start Menu Entry" :
+	    "Create Start Menu Entry";
+	if (ui_is_textmode(ui)) {
+		params.rect.p0.x = 0;
+		params.rect.p0.y = 0;
+		params.rect.p1.x = 50;
+		params.rect.p1.y = 13;
+	} else {
+		params.rect.p0.x = 0;
+		params.rect.p0.y = 0;
+		params.rect.p1.x = 370;
+		params.rect.p1.y = 230;
+	}
+
+	rc = ui_window_create(ui, &params, &window);
+	if (rc != EOK) {
+		printf("Error creating window.\n");
+		goto error;
+	}
+
+	ui_window_set_cb(window, &window_cb, (void *)smee);
+	smee->window = window;
+
+	res = ui_window_get_res(window);
+
+	rc = ui_fixed_create(&smee->fixed);
+	if (rc != EOK) {
+		printf("Error creating fixed layout.\n");
+		goto error;
+	}
+
+	/* Command to run label */
+
+	rc = ui_label_create(res, "Command to run:", &smee->lcmd);
+	if (rc != EOK)
+		goto error;
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 3;
+		rect.p0.y = 2;
+		rect.p1.x = 48;
+		rect.p1.y = 3;
+	} else {
+		rect.p0.x = 10;
+		rect.p0.y = 35;
+		rect.p1.x = 190;
+		rect.p1.y = 50;
+	}
+
+	ui_label_set_rect(smee->lcmd, &rect);
+
+	rc = ui_fixed_add(smee->fixed, ui_label_ctl(smee->lcmd));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	/* Command entry */
+
+	rc = ui_entry_create(window, cmd, &smee->ecmd);
+	if (rc != EOK)
+		goto error;
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 3;
+		rect.p0.y = 3;
+		rect.p1.x = 48;
+		rect.p1.y = 4;
+	} else {
+		rect.p0.x = 10;
+		rect.p0.y = 50;
+		rect.p1.x = 360;
+		rect.p1.y = 75;
+	}
+
+	ui_entry_set_rect(smee->ecmd, &rect);
+
+	rc = ui_fixed_add(smee->fixed, ui_entry_ctl(smee->ecmd));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	/* Caption label */
+
+	rc = ui_label_create(res, "Caption:", &smee->lcaption);
+	if (rc != EOK)
+		goto error;
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 3;
+		rect.p0.y = 5;
+		rect.p1.x = 20;
+		rect.p1.y = 6;
+	} else {
+		rect.p0.x = 10;
+		rect.p0.y = 95;
+		rect.p1.x = 190;
+		rect.p1.y = 110;
+	}
+
+	ui_label_set_rect(smee->lcaption, &rect);
+
+	rc = ui_fixed_add(smee->fixed, ui_label_ctl(smee->lcaption));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	/* Caption entry */
+
+	rc = ui_entry_create(window, caption, &smee->ecaption);
+	if (rc != EOK)
+		goto error;
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 3;
+		rect.p0.y = 6;
+		rect.p1.x = 48;
+		rect.p1.y = 7;
+	} else {
+		rect.p0.x = 10;
+		rect.p0.y = 110;
+		rect.p1.x = 360;
+		rect.p1.y = 135;
+	}
+
+	ui_entry_set_rect(smee->ecaption, &rect);
+
+	rc = ui_fixed_add(smee->fixed, ui_entry_ctl(smee->ecaption));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	/* Start in terminal checkbox */
+
+	rc = ui_checkbox_create(res, "Start in terminal", &smee->cbterminal);
+	if (rc != EOK)
+		goto error;
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 3;
+		rect.p0.y = 8;
+		rect.p1.x = 6;
+		rect.p1.y = 9;
+	} else {
+		rect.p0.x = 10;
+		rect.p0.y = 155;
+		rect.p1.x = 360;
+		rect.p1.y = 170;
+	}
+
+	ui_checkbox_set_rect(smee->cbterminal, &rect);
+	ui_checkbox_set_checked(smee->cbterminal, terminal);
+
+	rc = ui_fixed_add(smee->fixed, ui_checkbox_ctl(smee->cbterminal));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	/* OK button */
+
+	rc = ui_pbutton_create(res, "OK", &smee->bok);
+	if (rc != EOK)
+		goto error;
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 23;
+		rect.p0.y = 10;
+		rect.p1.x = 35;
+		rect.p1.y = 11;
+	} else {
+		rect.p0.x = 190;
+		rect.p0.y = 190;
+		rect.p1.x = 270;
+		rect.p1.y = 215;
+	}
+
+	ui_pbutton_set_cb(smee->bok, &smeedit_ok_button_cb, (void *)smee);
+	ui_pbutton_set_rect(smee->bok, &rect);
+	ui_pbutton_set_default(smee->bok, true);
+
+	rc = ui_fixed_add(smee->fixed, ui_pbutton_ctl(smee->bok));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	/* Cancel button */
+
+	rc = ui_pbutton_create(res, "Cancel", &smee->bcancel);
+	if (rc != EOK)
+		goto error;
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 36;
+		rect.p0.y = 10;
+		rect.p1.x = 48;
+		rect.p1.y = 11;
+	} else {
+		rect.p0.x = 280;
+		rect.p0.y = 190;
+		rect.p1.x = 360;
+		rect.p1.y = 215;
+	}
+
+	ui_pbutton_set_cb(smee->bcancel, &smeedit_cancel_button_cb,
+	    (void *)smee);
+	ui_pbutton_set_rect(smee->bcancel, &rect);
+
+	rc = ui_fixed_add(smee->fixed, ui_pbutton_ctl(smee->bcancel));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	ui_window_add(window, ui_fixed_ctl(smee->fixed));
+
+	rc = ui_window_paint(window);
+	if (rc != EOK)
+		goto error;
+
+	*rsmee = smee;
+	return EOK;
+error:
+	if (smee->window != NULL)
+		ui_window_destroy(smee->window);
+	free(smee);
+	return rc;
+}
+
+/** Destroy Taskbar configuration window.
+ *
+ * @param smee Start menu entry edit dialog
+ */
+void smeedit_destroy(smeedit_t *smee)
+{
+	ui_window_destroy(smee->window);
+}
+
+/** OK button clicked.
+ *
+ * @params bok OK button
+ * @params arg Argument (smeedit_t *)
+ */
+static void smeedit_ok_clicked(ui_pbutton_t *bok, void *arg)
+{
+	smeedit_t *smee;
+	smenu_entry_t *entry;
+	startmenu_entry_t *smentry;
+	const char *cmd;
+	const char *caption;
+	bool terminal;
+	errno_t rc;
+
+	(void)bok;
+	smee = (smeedit_t *)arg;
+
+	cmd = ui_entry_get_text(smee->ecmd);
+	caption = ui_entry_get_text(smee->ecaption);
+	terminal = ui_checkbox_get_checked(smee->cbterminal);
+
+	if (smee->smentry == NULL) {
+		/* Create new entry */
+		rc = smenu_entry_create(smee->startmenu->tbarcfg->tbarcfg,
+		    caption, cmd, terminal, &entry);
+		if (rc != EOK)
+			return;
+
+		rc = startmenu_insert(smee->startmenu, entry, &smentry);
+		if (rc != EOK)
+			return;
+
+		startmenu_repaint(smee->startmenu);
+		(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
+	} else {
+		/* Edit existing entry */
+		rc = smenu_entry_set_cmd(smee->smentry->entry, cmd);
+		if (rc != EOK)
+			return;
+
+		smenu_entry_set_caption(smee->smentry->entry, caption);
+		if (rc != EOK)
+			return;
+
+		smenu_entry_set_terminal(smee->smentry->entry, terminal);
+		if (rc != EOK)
+			return;
+
+		(void)smenu_entry_save(smee->smentry->entry);
+		startmenu_entry_update(smee->smentry);
+		(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
+	}
+
+	smeedit_destroy(smee);
+}
+
+/** Cancel button clicked.
+ *
+ * @params bok OK button
+ * @params arg Argument (smeedit_t *)
+ */
+static void smeedit_cancel_clicked(ui_pbutton_t *bcancel, void *arg)
+{
+	smeedit_t *smee;
+
+	(void)bcancel;
+	smee = (smeedit_t *)arg;
+	smeedit_destroy(smee);
+}
+
+/** @}
+ */
Index: uspace/app/taskbar-cfg/smeedit.h
===================================================================
--- uspace/app/taskbar-cfg/smeedit.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar-cfg/smeedit.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,48 @@
+/*
+ * 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 taskbar-cfg
+ * @{
+ */
+/**
+ * @file Start menu entry edit dialog
+ */
+
+#ifndef SMEEDIT_H
+#define SMEEDIT_H
+
+#include "types/smeedit.h"
+#include "types/startmenu.h"
+
+extern errno_t smeedit_create(startmenu_t *, startmenu_entry_t *, smeedit_t **);
+extern void smeedit_destroy(smeedit_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/taskbar-cfg/startmenu.c
===================================================================
--- uspace/app/taskbar-cfg/startmenu.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar-cfg/startmenu.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,721 @@
+/*
+ * 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 taskbar-cfg
+ * @{
+ */
+/** @file Start menu configuration tab
+ */
+
+#include <gfx/coord.h>
+#include <loc.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <str.h>
+#include <tbarcfg/tbarcfg.h>
+#include <ui/control.h>
+#include <ui/label.h>
+#include <ui/list.h>
+#include <ui/pbutton.h>
+#include <ui/resource.h>
+#include <ui/tab.h>
+#include <ui/window.h>
+#include "smeedit.h"
+#include "startmenu.h"
+#include "taskbar-cfg.h"
+
+static void startmenu_entry_selected(ui_list_entry_t *, void *);
+static void startmenu_new_entry_clicked(ui_pbutton_t *, void *);
+static void startmenu_delete_entry_clicked(ui_pbutton_t *, void *);
+static void startmenu_edit_entry_clicked(ui_pbutton_t *, void *);
+static void startmenu_sep_entry_clicked(ui_pbutton_t *, void *);
+static void startmenu_up_entry_clicked(ui_pbutton_t *, void *);
+static void startmenu_down_entry_clicked(ui_pbutton_t *, void *);
+
+/** Entry list callbacks */
+ui_list_cb_t startmenu_entry_list_cb = {
+	.selected = startmenu_entry_selected
+};
+
+/** New entry button callbacks */
+ui_pbutton_cb_t startmenu_new_entry_button_cb = {
+	.clicked = startmenu_new_entry_clicked
+};
+
+/** Delete entry button callbacks */
+ui_pbutton_cb_t startmenu_delete_entry_button_cb = {
+	.clicked = startmenu_delete_entry_clicked
+};
+
+/** Edit entry button callbacks */
+ui_pbutton_cb_t startmenu_edit_entry_button_cb = {
+	.clicked = startmenu_edit_entry_clicked
+};
+
+/** Separator entry button callbacks */
+ui_pbutton_cb_t startmenu_sep_entry_button_cb = {
+	.clicked = startmenu_sep_entry_clicked
+};
+
+/** Move entry up button callbacks */
+ui_pbutton_cb_t startmenu_up_entry_button_cb = {
+	.clicked = startmenu_up_entry_clicked
+};
+
+/** Move entry down button callbacks */
+ui_pbutton_cb_t startmenu_down_entry_button_cb = {
+	.clicked = startmenu_down_entry_clicked
+};
+
+/** Create start menu configuration tab
+ *
+ * @param tbcfg Taskbar configuration dialog
+ * @param rsmenu Place to store pointer to new start menu configuration tab
+ * @return EOK on success or an error code
+ */
+errno_t startmenu_create(taskbar_cfg_t *tbcfg, startmenu_t **rsmenu)
+{
+	ui_resource_t *ui_res;
+	startmenu_t *smenu;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	ui_res = ui_window_get_res(tbcfg->window);
+
+	smenu = calloc(1, sizeof(startmenu_t));
+	if (smenu == NULL) {
+		printf("Out of memory.\n");
+		return ENOMEM;
+	}
+
+	smenu->tbarcfg = tbcfg;
+
+	/* 'Start Menu' tab */
+
+	rc = ui_tab_create(tbcfg->tabset, "Start Menu", &smenu->tab);
+	if (rc != EOK)
+		goto error;
+
+	rc = ui_fixed_create(&smenu->fixed);
+	if (rc != EOK) {
+		printf("Error creating fixed layout.\n");
+		goto error;
+	}
+
+	/* 'Start menu entries:' label */
+
+	rc = ui_label_create(ui_res, "Start menu entries:",
+	    &smenu->entries_label);
+	if (rc != EOK) {
+		printf("Error creating label.\n");
+		goto error;
+	}
+
+	if (ui_resource_is_textmode(ui_res)) {
+		rect.p0.x = 4;
+		rect.p0.y = 4;
+		rect.p1.x = 36;
+		rect.p1.y = 5;
+	} else {
+		rect.p0.x = 20;
+		rect.p0.y = 60;
+		rect.p1.x = 360;
+		rect.p1.y = 80;
+	}
+
+	ui_label_set_rect(smenu->entries_label, &rect);
+
+	rc = ui_fixed_add(smenu->fixed, ui_label_ctl(smenu->entries_label));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	/* List of entries */
+
+	rc = ui_list_create(tbcfg->window, false, &smenu->entries_list);
+	if (rc != EOK) {
+		printf("Error creating list.\n");
+		goto error;
+	}
+
+	if (ui_resource_is_textmode(ui_res)) {
+		rect.p0.x = 4;
+		rect.p0.y = 5;
+		rect.p1.x = 56;
+		rect.p1.y = 20;
+	} else {
+		rect.p0.x = 20;
+		rect.p0.y = 80;
+		rect.p1.x = 360;
+		rect.p1.y = 330;
+	}
+
+	ui_list_set_rect(smenu->entries_list, &rect);
+
+	rc = ui_fixed_add(smenu->fixed, ui_list_ctl(smenu->entries_list));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	ui_list_set_cb(smenu->entries_list, &startmenu_entry_list_cb,
+	    (void *)smenu);
+
+	/* New entry button */
+
+	rc = ui_pbutton_create(ui_res, "New...", &smenu->new_entry);
+	if (rc != EOK) {
+		printf("Error creating button.\n");
+		goto error;
+	}
+
+	if (ui_resource_is_textmode(ui_res)) {
+		rect.p0.x = 58;
+		rect.p0.y = 5;
+		rect.p1.x = 68;
+		rect.p1.y = 6;
+	} else {
+		rect.p0.x = 370;
+		rect.p0.y = 80;
+		rect.p1.x = 450;
+		rect.p1.y = 105;
+	}
+
+	ui_pbutton_set_rect(smenu->new_entry, &rect);
+
+	rc = ui_fixed_add(smenu->fixed, ui_pbutton_ctl(smenu->new_entry));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	ui_pbutton_set_cb(smenu->new_entry, &startmenu_new_entry_button_cb,
+	    (void *)smenu);
+
+	/* Delete entry button */
+
+	rc = ui_pbutton_create(ui_res, "Delete", &smenu->delete_entry);
+	if (rc != EOK) {
+		printf("Error creating button.\n");
+		goto error;
+	}
+
+	if (ui_resource_is_textmode(ui_res)) {
+		rect.p0.x = 58;
+		rect.p0.y = 7;
+		rect.p1.x = 68;
+		rect.p1.y = 8;
+	} else {
+		rect.p0.x = 370;
+		rect.p0.y = 110;
+		rect.p1.x = 450;
+		rect.p1.y = 135;
+	}
+
+	ui_pbutton_set_rect(smenu->delete_entry, &rect);
+
+	rc = ui_fixed_add(smenu->fixed, ui_pbutton_ctl(smenu->delete_entry));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	ui_pbutton_set_cb(smenu->delete_entry,
+	    &startmenu_delete_entry_button_cb, (void *)smenu);
+
+	/* Edit entry button */
+
+	rc = ui_pbutton_create(ui_res, "Edit...", &smenu->edit_entry);
+	if (rc != EOK) {
+		printf("Error creating button.\n");
+		goto error;
+	}
+
+	if (ui_resource_is_textmode(ui_res)) {
+		rect.p0.x = 58;
+		rect.p0.y = 9;
+		rect.p1.x = 68;
+		rect.p1.y = 10;
+	} else {
+		rect.p0.x = 370;
+		rect.p0.y = 140;
+		rect.p1.x = 450;
+		rect.p1.y = 165;
+	}
+
+	ui_pbutton_set_rect(smenu->edit_entry, &rect);
+
+	rc = ui_fixed_add(smenu->fixed, ui_pbutton_ctl(smenu->edit_entry));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	ui_pbutton_set_cb(smenu->edit_entry,
+	    &startmenu_edit_entry_button_cb, (void *)smenu);
+
+	/* Separator entry button */
+
+	rc = ui_pbutton_create(ui_res, "Separator", &smenu->sep_entry);
+	if (rc != EOK) {
+		printf("Error creating button.\n");
+		goto error;
+	}
+
+	if (ui_resource_is_textmode(ui_res)) {
+		rect.p0.x = 58;
+		rect.p0.y = 11;
+		rect.p1.x = 68;
+		rect.p1.y = 12;
+	} else {
+		rect.p0.x = 370;
+		rect.p0.y = 170;
+		rect.p1.x = 450;
+		rect.p1.y = 195;
+	}
+
+	ui_pbutton_set_rect(smenu->sep_entry, &rect);
+
+	rc = ui_fixed_add(smenu->fixed, ui_pbutton_ctl(smenu->sep_entry));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	ui_pbutton_set_cb(smenu->sep_entry,
+	    &startmenu_sep_entry_button_cb, (void *)smenu);
+
+	/* Move entry up button */
+
+	rc = ui_pbutton_create(ui_res, "Up", &smenu->up_entry);
+	if (rc != EOK) {
+		printf("Error creating button.\n");
+		goto error;
+	}
+
+	if (ui_resource_is_textmode(ui_res)) {
+		rect.p0.x = 58;
+		rect.p0.y = 13;
+		rect.p1.x = 68;
+		rect.p1.y = 14;
+	} else {
+		rect.p0.x = 370;
+		rect.p0.y = 220;
+		rect.p1.x = 450;
+		rect.p1.y = 245;
+	}
+
+	ui_pbutton_set_rect(smenu->up_entry, &rect);
+
+	rc = ui_fixed_add(smenu->fixed, ui_pbutton_ctl(smenu->up_entry));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	ui_pbutton_set_cb(smenu->up_entry,
+	    &startmenu_up_entry_button_cb, (void *)smenu);
+
+	/* Move entry down button */
+
+	rc = ui_pbutton_create(ui_res, "Down", &smenu->down_entry);
+	if (rc != EOK) {
+		printf("Error creating button.\n");
+		goto error;
+	}
+
+	if (ui_resource_is_textmode(ui_res)) {
+		rect.p0.x = 58;
+		rect.p0.y = 15;
+		rect.p1.x = 68;
+		rect.p1.y = 16;
+	} else {
+		rect.p0.x = 370;
+		rect.p0.y = 250;
+		rect.p1.x = 450;
+		rect.p1.y = 275;
+	}
+
+	ui_pbutton_set_rect(smenu->down_entry, &rect);
+
+	rc = ui_fixed_add(smenu->fixed, ui_pbutton_ctl(smenu->down_entry));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		goto error;
+	}
+
+	ui_pbutton_set_cb(smenu->down_entry,
+	    &startmenu_down_entry_button_cb, (void *)smenu);
+
+	ui_tab_add(smenu->tab, ui_fixed_ctl(smenu->fixed));
+
+	*rsmenu = smenu;
+	return EOK;
+error:
+	if (smenu->down_entry != NULL)
+		ui_pbutton_destroy(smenu->down_entry);
+	if (smenu->up_entry != NULL)
+		ui_pbutton_destroy(smenu->up_entry);
+	if (smenu->delete_entry != NULL)
+		ui_pbutton_destroy(smenu->delete_entry);
+	if (smenu->new_entry != NULL)
+		ui_pbutton_destroy(smenu->new_entry);
+	if (smenu->entries_label != NULL)
+		ui_label_destroy(smenu->entries_label);
+	if (smenu->entries_list != NULL)
+		ui_list_destroy(smenu->entries_list);
+	if (smenu->fixed != NULL)
+		ui_fixed_destroy(smenu->fixed);
+	free(smenu);
+	return rc;
+}
+
+/** Populate start menu tab with start menu configuration data
+ *
+ * @param smenu Start menu configuration tab
+ * @param tbarcfg Taskbar configuration
+ * @return EOK on success or an error code
+ */
+errno_t startmenu_populate(startmenu_t *smenu, tbarcfg_t *tbarcfg)
+{
+	smenu_entry_t *entry;
+	startmenu_entry_t *smentry;
+	errno_t rc;
+
+	entry = tbarcfg_smenu_first(tbarcfg);
+	while (entry != NULL) {
+		rc = startmenu_insert(smenu, entry, &smentry);
+		if (rc != EOK)
+			return rc;
+
+		entry = tbarcfg_smenu_next(entry);
+	}
+
+	return EOK;
+}
+
+/** Destroy start menu configuration tab.
+ *
+ * @param smenu Start menu configuration tab
+ */
+void startmenu_destroy(startmenu_t *smenu)
+{
+	ui_list_entry_t *lentry;
+	startmenu_entry_t *entry;
+
+	lentry = ui_list_first(smenu->entries_list);
+	while (lentry != NULL) {
+		entry = (startmenu_entry_t *)ui_list_entry_get_arg(lentry);
+		free(entry);
+		ui_list_entry_delete(lentry);
+		lentry = ui_list_first(smenu->entries_list);
+	}
+
+	/* This will automatically destroy all controls in the tab */
+	ui_tab_destroy(smenu->tab);
+	free(smenu);
+}
+
+/** Insert new entry into entries list.
+ *
+ * @param smenu Start menu configuration tab
+ * @param entry Backing entry
+ * @param rsmentry Place to store pointer to new entry or NULL
+ * @return EOK on success or an error code
+ */
+errno_t startmenu_insert(startmenu_t *smenu, smenu_entry_t *entry,
+    startmenu_entry_t **rsmentry)
+{
+	startmenu_entry_t *smentry;
+	ui_list_entry_attr_t attr;
+	bool separator;
+	errno_t rc;
+
+	smentry = calloc(1, sizeof(startmenu_entry_t));
+	if (smentry == NULL)
+		return ENOMEM;
+
+	smentry->startmenu = smenu;
+	smentry->entry = entry;
+
+	ui_list_entry_attr_init(&attr);
+	separator = smenu_entry_get_separator(entry);
+	if (separator)
+		attr.caption = "-- Separator --";
+	else
+		attr.caption = smenu_entry_get_caption(entry);
+
+	attr.arg = (void *)smentry;
+
+	rc = ui_list_entry_append(smenu->entries_list, &attr, &smentry->lentry);
+	if (rc != EOK) {
+		free(smentry);
+		return rc;
+	}
+
+	if (rsmentry != NULL)
+		*rsmentry = smentry;
+	return EOK;
+}
+
+/** Get selected start menu entry.
+ *
+ * @param smenu Start menu
+ * @return Selected entry or @c NULL if no entry is selected
+ */
+startmenu_entry_t *startmenu_get_selected(startmenu_t *smenu)
+{
+	ui_list_entry_t *entry;
+
+	entry = ui_list_get_cursor(smenu->entries_list);
+	if (entry == NULL)
+		return NULL;
+
+	return (startmenu_entry_t *)ui_list_entry_get_arg(entry);
+}
+
+/** Create new menu entry.
+ *
+ * @param smenu Start menu
+ */
+void startmenu_new_entry(startmenu_t *smenu)
+{
+	smeedit_t *smee;
+	errno_t rc;
+
+	rc = smeedit_create(smenu, NULL, &smee);
+	if (rc != EOK)
+		return;
+
+	(void)smee;
+	(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
+}
+
+/** Create new separator menu entry.
+ *
+ * @param smenu Start menu
+ */
+void startmenu_sep_entry(startmenu_t *smenu)
+{
+	startmenu_entry_t *smentry = NULL;
+	smenu_entry_t *entry;
+	errno_t rc;
+
+	rc = smenu_entry_sep_create(smenu->tbarcfg->tbarcfg, &entry);
+	if (rc != EOK)
+		return;
+
+	(void)startmenu_insert(smenu, entry, &smentry);
+	(void)ui_control_paint(ui_list_ctl(smenu->entries_list));
+	(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
+}
+
+/** Edit selected menu entry.
+ *
+ * @param smenu Start menu
+ */
+void startmenu_edit(startmenu_t *smenu)
+{
+	smeedit_t *smee;
+	startmenu_entry_t *smentry;
+	errno_t rc;
+
+	smentry = startmenu_get_selected(smenu);
+	if (smentry == NULL)
+		return;
+
+	/* Do not edit separator entries */
+	if (smenu_entry_get_separator(smentry->entry))
+		return;
+
+	rc = smeedit_create(smenu, smentry, &smee);
+	if (rc != EOK)
+		return;
+
+	(void)smee;
+}
+
+/** Update start menu entry caption.
+ *
+ * When editing an entry the entry's label might change. We need
+ * to update the list entry caption to reflect that.
+ *
+ * @param entry Start menu entry
+ */
+errno_t startmenu_entry_update(startmenu_entry_t *entry)
+{
+	return ui_list_entry_set_caption(entry->lentry,
+	    smenu_entry_get_caption(entry->entry));
+}
+
+/** Repaint start menu entry list.
+ *
+ * When editing an entry the entry's label might change. We need
+ * to update the list entry caption to reflect that.
+ *
+ * @param smenu Start menu
+ */
+void startmenu_repaint(startmenu_t *smenu)
+{
+	(void) ui_control_paint(ui_list_ctl(smenu->entries_list));
+}
+
+/** Entry in entry list is selected.
+ *
+ * @param lentry UI list entry
+ * @param arg Argument (dcfg_seats_entry_t *)
+ */
+static void startmenu_entry_selected(ui_list_entry_t *lentry, void *arg)
+{
+	(void)lentry;
+	(void)arg;
+}
+
+/** New entry button clicked.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (startmenu_t *)
+ */
+static void startmenu_new_entry_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	startmenu_t *smenu = (startmenu_t *)arg;
+
+	(void)pbutton;
+	startmenu_new_entry(smenu);
+}
+
+/** Delete entry button clicked.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (startmenu_t *)
+ */
+static void startmenu_delete_entry_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	startmenu_t *smenu = (startmenu_t *)arg;
+	startmenu_entry_t *smentry;
+	errno_t rc;
+
+	(void)pbutton;
+
+	smentry = startmenu_get_selected(smenu);
+	if (smentry == NULL)
+		return;
+
+	rc = smenu_entry_destroy(smentry->entry);
+	if (rc != EOK)
+		return;
+
+	ui_list_entry_delete(smentry->lentry);
+	free(smentry);
+	(void)ui_control_paint(ui_list_ctl(smenu->entries_list));
+	(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
+}
+
+/** Edit entry button clicked.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (startmenu_t *)
+ */
+static void startmenu_edit_entry_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	startmenu_t *smenu = (startmenu_t *)arg;
+
+	(void)pbutton;
+	startmenu_edit(smenu);
+}
+
+/** Separator entry button clicked.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (startmenu_t *)
+ */
+static void startmenu_sep_entry_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	startmenu_t *smenu = (startmenu_t *)arg;
+
+	(void)pbutton;
+	startmenu_sep_entry(smenu);
+}
+
+/** Up entry button clicked.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (startmenu_t *)
+ */
+static void startmenu_up_entry_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	startmenu_t *smenu = (startmenu_t *)arg;
+	startmenu_entry_t *smentry;
+	errno_t rc;
+
+	(void)pbutton;
+
+	smentry = startmenu_get_selected(smenu);
+	if (smentry == NULL)
+		return;
+
+	rc = smenu_entry_move_up(smentry->entry);
+	if (rc != EOK)
+		return;
+
+	ui_list_entry_move_up(smentry->lentry);
+
+	(void)ui_control_paint(ui_list_ctl(smenu->entries_list));
+	(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
+}
+
+/** Down entry button clicked.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (startmenu_t *)
+ */
+static void startmenu_down_entry_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	startmenu_t *smenu = (startmenu_t *)arg;
+	startmenu_entry_t *smentry;
+	errno_t rc;
+
+	(void)pbutton;
+
+	smentry = startmenu_get_selected(smenu);
+	if (smentry == NULL)
+		return;
+
+	rc = smenu_entry_move_down(smentry->entry);
+	if (rc != EOK)
+		return;
+
+	ui_list_entry_move_down(smentry->lentry);
+
+	(void)ui_control_paint(ui_list_ctl(smenu->entries_list));
+	(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
+}
+
+/** @}
+ */
Index: uspace/app/taskbar-cfg/startmenu.h
===================================================================
--- uspace/app/taskbar-cfg/startmenu.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar-cfg/startmenu.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,58 @@
+/*
+ * 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 taskbar-cfg
+ * @{
+ */
+/**
+ * @file Start menu configuration tab
+ */
+
+#ifndef STARTMENU_H
+#define STARTMENU_H
+
+#include <tbarcfg/tbarcfg.h>
+#include "types/startmenu.h"
+#include "types/taskbar-cfg.h"
+
+extern errno_t startmenu_create(taskbar_cfg_t *, startmenu_t **);
+extern errno_t startmenu_populate(startmenu_t *, tbarcfg_t *);
+extern void startmenu_destroy(startmenu_t *);
+extern errno_t startmenu_insert(startmenu_t *, smenu_entry_t *,
+    startmenu_entry_t **);
+extern startmenu_entry_t *startmenu_get_selected(startmenu_t *);
+extern void startmenu_new_entry(startmenu_t *);
+extern void startmenu_sep_entry(startmenu_t *);
+extern void startmenu_edit(startmenu_t *);
+extern errno_t startmenu_entry_update(startmenu_entry_t *);
+extern void startmenu_repaint(startmenu_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/taskbar-cfg/taskbar-cfg.c
===================================================================
--- uspace/app/taskbar-cfg/taskbar-cfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar-cfg/taskbar-cfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,220 @@
+/*
+ * 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 taskbar-cfg
+ * @{
+ */
+/** @file Taskbar configuration utility (UI)
+ */
+
+#include <gfx/coord.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ui/fixed.h>
+#include <ui/resource.h>
+#include <ui/tabset.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include "taskbar-cfg.h"
+#include "startmenu.h"
+
+static void wnd_close(ui_window_t *, void *);
+
+static ui_window_cb_t window_cb = {
+	.close = wnd_close
+};
+
+/** Window close button was clicked.
+ *
+ * @param window Window
+ * @param arg Argument (tbcfg)
+ */
+static void wnd_close(ui_window_t *window, void *arg)
+{
+	taskbar_cfg_t *tbcfg = (taskbar_cfg_t *) arg;
+
+	ui_quit(tbcfg->ui);
+}
+
+/** Create Taskbar configuration window.
+ *
+ * @param display_spec Display specification
+ * @param rdcfg Place to store pointer to new taskbar configuration
+ * @return EOK on success or an error code
+ */
+errno_t taskbar_cfg_create(const char *display_spec, taskbar_cfg_t **rdcfg)
+{
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	taskbar_cfg_t *tbcfg = NULL;
+	gfx_rect_t rect;
+	ui_resource_t *ui_res;
+	errno_t rc;
+
+	tbcfg = calloc(1, sizeof(taskbar_cfg_t));
+	if (tbcfg == NULL) {
+		printf("Out of memory.\n");
+		return ENOMEM;
+	}
+
+	rc = ui_create(display_spec, &ui);
+	if (rc != EOK) {
+		printf("Error creating UI on display %s.\n", display_spec);
+		goto error;
+	}
+
+	ui_wnd_params_init(&params);
+	params.caption = "Taskbar Configuration";
+	if (ui_is_textmode(ui)) {
+		params.rect.p0.x = 0;
+		params.rect.p0.y = 0;
+		params.rect.p1.x = 70;
+		params.rect.p1.y = 23;
+	} else {
+		params.rect.p0.x = 0;
+		params.rect.p0.y = 0;
+		params.rect.p1.x = 470;
+		params.rect.p1.y = 350;
+	}
+
+	tbcfg->ui = ui;
+
+	rc = ui_window_create(ui, &params, &window);
+	if (rc != EOK) {
+		printf("Error creating window.\n");
+		goto error;
+	}
+
+	ui_window_set_cb(window, &window_cb, (void *)tbcfg);
+	tbcfg->window = window;
+
+	ui_res = ui_window_get_res(window);
+
+	rc = ui_fixed_create(&tbcfg->fixed);
+	if (rc != EOK) {
+		printf("Error creating fixed layout.\n");
+		return rc;
+	}
+
+	rc = ui_tab_set_create(ui_res, &tbcfg->tabset);
+	if (rc != EOK) {
+		printf("Error creating tab set.\n");
+		return rc;
+	}
+
+	ui_window_get_app_rect(window, &rect);
+	ui_tab_set_set_rect(tbcfg->tabset, &rect);
+
+	rc = ui_fixed_add(tbcfg->fixed, ui_tab_set_ctl(tbcfg->tabset));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		return rc;
+	}
+
+	rc = startmenu_create(tbcfg, &tbcfg->startmenu);
+	if (rc != EOK)
+		goto error;
+
+	ui_window_add(window, ui_fixed_ctl(tbcfg->fixed));
+
+	*rdcfg = tbcfg;
+	return EOK;
+error:
+	if (tbcfg->startmenu != NULL)
+		startmenu_destroy(tbcfg->startmenu);
+	if (tbcfg->tabset != NULL)
+		ui_tab_set_destroy(tbcfg->tabset);
+	if (tbcfg->fixed != NULL)
+		ui_fixed_destroy(tbcfg->fixed);
+	if (tbcfg->ui != NULL)
+		ui_destroy(ui);
+	free(tbcfg);
+	return rc;
+}
+
+/** Open Taskbar configuration.
+ *
+ * @param tbcfg Taskbar configuration dialog
+ * @param cfg_repo Path to configuration repository
+ * @return EOK on success or an error code
+ */
+errno_t taskbar_cfg_open(taskbar_cfg_t *tbcfg, const char *cfg_repo)
+{
+	errno_t rc;
+
+	rc = tbarcfg_open(cfg_repo, &tbcfg->tbarcfg);
+	if (rc != EOK) {
+		rc = tbarcfg_create(cfg_repo, &tbcfg->tbarcfg);
+		if (rc != EOK) {
+			printf("Error opening Taskbar configuration.\n");
+			goto error;
+		}
+	}
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Populate task configuration from configuration repository.
+ *
+ * @param tbcfg Taskbar configuration dialog
+ * @return EOK on success or an error code
+ */
+errno_t taskbar_cfg_populate(taskbar_cfg_t *tbcfg)
+{
+	errno_t rc;
+
+	rc = startmenu_populate(tbcfg->startmenu, tbcfg->tbarcfg);
+	if (rc != EOK)
+		return rc;
+
+	rc = ui_window_paint(tbcfg->window);
+	if (rc != EOK) {
+		printf("Error painting window.\n");
+		return rc;
+	}
+
+	return EOK;
+}
+
+/** Destroy Taskbar configuration window.
+ *
+ * @param tbcfg Taskbar configuration window
+ */
+void taskbar_cfg_destroy(taskbar_cfg_t *tbcfg)
+{
+	if (tbcfg->tbarcfg != NULL)
+		tbarcfg_close(tbcfg->tbarcfg);
+	ui_window_destroy(tbcfg->window);
+	ui_destroy(tbcfg->ui);
+}
+
+/** @}
+ */
Index: uspace/app/taskbar-cfg/taskbar-cfg.h
===================================================================
--- uspace/app/taskbar-cfg/taskbar-cfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar-cfg/taskbar-cfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,49 @@
+/*
+ * 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 taskbar-cfg
+ * @{
+ */
+/**
+ * @file Taskbar configuration utility (in UI)
+ */
+
+#ifndef TASKBAR_CFG_H
+#define TASKBAR_CFG_H
+
+#include "types/taskbar-cfg.h"
+
+extern errno_t taskbar_cfg_create(const char *, taskbar_cfg_t **);
+extern errno_t taskbar_cfg_open(taskbar_cfg_t *, const char *);
+extern errno_t taskbar_cfg_populate(taskbar_cfg_t *);
+extern void taskbar_cfg_destroy(taskbar_cfg_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/taskbar-cfg/test/main.c
===================================================================
--- uspace/app/taskbar-cfg/test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar-cfg/test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+#include <pcut/pcut.h>
+
+PCUT_INIT;
+
+PCUT_IMPORT(taskbar_cfg);
+PCUT_IMPORT(startmenu);
+
+PCUT_MAIN();
Index: uspace/app/taskbar-cfg/test/startmenu.c
===================================================================
--- uspace/app/taskbar-cfg/test/startmenu.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar-cfg/test/startmenu.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,274 @@
+/*
+ * 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.
+ */
+
+#include <async.h>
+#include <dispcfg.h>
+#include <dispcfg_srv.h>
+#include <errno.h>
+#include <pcut/pcut.h>
+#include <str.h>
+#include <testdc.h>
+#include "../display-cfg.h"
+#include "../seats.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(seats);
+
+static const char *test_dispcfg_server = "test-dispcfg";
+static const char *test_dispcfg_svc = "test/dispcfg";
+
+/** Test dcfg_seats_create() and dcfg_seats_destroy() */
+PCUT_TEST(create_destroy)
+{
+	display_cfg_t *dcfg;
+	dcfg_seats_t *seats;
+	errno_t rc;
+
+	rc = display_cfg_create(UI_DISPLAY_NULL, &dcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dcfg_seats_create(dcfg, &seats);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	dcfg_seats_destroy(seats);
+	display_cfg_destroy(dcfg);
+}
+
+/** dcfg_seats_insert() inserts an entry into the seat list */
+PCUT_TEST(seats_insert)
+{
+	display_cfg_t *dcfg;
+	dcfg_seats_t *seats;
+	dcfg_seats_entry_t *entry = NULL;
+	errno_t rc;
+
+	rc = display_cfg_create(UI_DISPLAY_NULL, &dcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dcfg_seats_create(dcfg, &seats);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dcfg_seats_insert(seats, "Alice", 42, &entry);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	PCUT_ASSERT_STR_EQUALS("Alice", entry->name);
+	PCUT_ASSERT_INT_EQUALS(42, entry->seat_id);
+
+	dcfg_seats_destroy(seats);
+	display_cfg_destroy(dcfg);
+}
+
+/** dcfg_seats_list_populate() populates seat list */
+PCUT_TEST(seats_list_populate)
+{
+	display_cfg_t *dcfg;
+	dcfg_seats_t *seats;
+	errno_t rc;
+	service_id_t sid;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = display_cfg_create(UI_DISPLAY_NULL, &dcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = display_cfg_open(dcfg, test_dispcfg_svc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dcfg_seats_create(dcfg, &seats);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/*
+	 * dcfg_seat_list_populate() calls dispcfg_get_seat_list()
+	 * and dispcfg_get_seat_info()
+	 */
+	resp.rc = EOK;
+	resp.get_seat_list_rlist = calloc(1, sizeof(dispcfg_seat_list_t));
+	PCUT_ASSERT_NOT_NULL(resp.get_seat_list_rlist);
+	resp.get_seat_list_rlist->nseats = 1;
+	resp.get_seat_list_rlist->seats = calloc(1, sizeof(sysarg_t));
+	PCUT_ASSERT_NOT_NULL(resp.get_seat_list_rlist->seats);
+	resp.get_seat_list_rlist->seats[0] = 42;
+
+	resp.get_seat_info_rinfo = calloc(1, sizeof(dispcfg_seat_info_t));
+	PCUT_ASSERT_NOT_NULL(resp.get_seat_info_rinfo);
+	resp.get_seat_info_rinfo->name = str_dup("Alice");
+	PCUT_ASSERT_NOT_NULL(resp.get_seat_info_rinfo->name);
+
+	rc = dcfg_seats_list_populate(seats);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	dcfg_seats_destroy(seats);
+	display_cfg_destroy(dcfg);
+
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** dcfg_devices_insert() inserts an entry into the device list */
+PCUT_TEST(devices_insert)
+{
+	display_cfg_t *dcfg;
+	dcfg_seats_t *seats;
+	ui_list_entry_t *lentry;
+	dcfg_devices_entry_t *entry;
+	errno_t rc;
+
+	rc = display_cfg_create(UI_DISPLAY_NULL, &dcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dcfg_seats_create(dcfg, &seats);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dcfg_devices_insert(seats, "mydevice", 42);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	lentry = ui_list_first(seats->device_list);
+	PCUT_ASSERT_NOT_NULL(lentry);
+	entry = (dcfg_devices_entry_t *)ui_list_entry_get_arg(lentry);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	PCUT_ASSERT_STR_EQUALS("mydevice", entry->name);
+	PCUT_ASSERT_INT_EQUALS(42, entry->svc_id);
+
+	dcfg_seats_destroy(seats);
+	display_cfg_destroy(dcfg);
+}
+
+/** dcfg_avail_devices_insert() inserts entry into available devices list */
+PCUT_TEST(avail_devices_insert)
+{
+	display_cfg_t *dcfg;
+	dcfg_seats_t *seats;
+	ui_list_entry_t *lentry;
+	dcfg_devices_entry_t *entry;
+	ui_select_dialog_t *dialog;
+	ui_select_dialog_params_t sdparams;
+	errno_t rc;
+
+	rc = display_cfg_create(UI_DISPLAY_NULL, &dcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dcfg_seats_create(dcfg, &seats);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_select_dialog_params_init(&sdparams);
+	sdparams.caption = "Dialog";
+	sdparams.prompt = "Select";
+
+	rc = ui_select_dialog_create(seats->dcfg->ui, &sdparams, &dialog);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dcfg_avail_devices_insert(seats, dialog, "mydevice", 42);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	lentry = ui_list_first(ui_select_dialog_list(dialog));
+	PCUT_ASSERT_NOT_NULL(lentry);
+	entry = (dcfg_devices_entry_t *)ui_list_entry_get_arg(lentry);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	PCUT_ASSERT_STR_EQUALS("mydevice", entry->name);
+	PCUT_ASSERT_INT_EQUALS(42, entry->svc_id);
+
+	ui_select_dialog_destroy(dialog);
+	dcfg_seats_destroy(seats);
+	display_cfg_destroy(dcfg);
+}
+
+PCUT_TEST(asgn_dev_list_populate)
+{
+}
+
+PCUT_TEST(avail_dev_list_populate)
+{
+}
+
+PCUT_TEST(seats_get_selected)
+{
+}
+
+PCUT_TEST(devices_get_selected)
+{
+}
+
+PCUT_TEST(seats_list_selected)
+{
+}
+
+PCUT_TEST(add_seat_clicked)
+{
+}
+
+PCUT_TEST(remove_seat_clicked)
+{
+}
+
+PCUT_TEST(add_seat_dialog_bok)
+{
+}
+
+PCUT_TEST(add_seat_dialog_bcancel)
+{
+}
+
+PCUT_TEST(add_seat_dialog_close)
+{
+}
+
+PCUT_TEST(add_device_clicked)
+{
+}
+
+PCUT_TEST(remove_device_clicked)
+{
+}
+
+PCUT_TEST(add_device_dialog_bok)
+{
+}
+
+PCUT_TEST(add_device_dialog_bcancel)
+{
+}
+
+PCUT_TEST(add_device_dialog_close)
+{
+}
+
+PCUT_EXPORT(seats);
Index: uspace/app/taskbar-cfg/test/taskbar-cfg.c
===================================================================
--- uspace/app/taskbar-cfg/test/taskbar-cfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar-cfg/test/taskbar-cfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <pcut/pcut.h>
+#include <ui/ui.h>
+#include "../display-cfg.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(display_cfg);
+
+/** Test display_cfg_create() and display_cfg_destroy() */
+PCUT_TEST(create_destroy)
+{
+	display_cfg_t *dcfg;
+	errno_t rc;
+
+	rc = display_cfg_create(UI_DISPLAY_NULL, &dcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_cfg_destroy(dcfg);
+}
+
+PCUT_EXPORT(display_cfg);
Index: uspace/app/taskbar-cfg/types/smeedit.h
===================================================================
--- uspace/app/taskbar-cfg/types/smeedit.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar-cfg/types/smeedit.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,75 @@
+/*
+ * 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 taskbar-cfg
+ * @{
+ */
+/**
+ * @file Start menu entry edit dialog
+ */
+
+#ifndef TYPES_SMEEDIT_H
+#define TYPES_SMEEDIT_H
+
+#include <ui/checkbox.h>
+#include <ui/fixed.h>
+#include <ui/label.h>
+#include <ui/entry.h>
+#include <ui/pbutton.h>
+#include <ui/window.h>
+
+/** Start menu entry edit dialog */
+typedef struct smeedit {
+	/** Containing start menu configuration tab */
+	struct startmenu *startmenu;
+	/** Start menu entry or @c NULL if creating a new entry */
+	struct startmenu_entry *smentry;
+	/** Window */
+	ui_window_t *window;
+	/** Fixed layout */
+	ui_fixed_t *fixed;
+	/** 'Caption' label */
+	ui_label_t *lcaption;
+	/** Caption entry */
+	ui_entry_t *ecaption;
+	/** 'Command' label */
+	ui_label_t *lcmd;
+	/** Command entry */
+	ui_entry_t *ecmd;
+	/** Start in terminal checkbox */
+	ui_checkbox_t *cbterminal;
+	/** OK button */
+	ui_pbutton_t *bok;
+	/** Cancel button */
+	ui_pbutton_t *bcancel;
+} smeedit_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/taskbar-cfg/types/startmenu.h
===================================================================
--- uspace/app/taskbar-cfg/types/startmenu.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar-cfg/types/startmenu.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,85 @@
+/*
+ * 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 taskbar-cfg
+ * @{
+ */
+/**
+ * @file Start menu configuration tab
+ */
+
+#ifndef TYPES_STARTMENU_H
+#define TYPES_STARTMENU_H
+
+#include <tbarcfg/tbarcfg.h>
+#include <ui/fixed.h>
+#include <ui/label.h>
+#include <ui/list.h>
+#include <ui/pbutton.h>
+#include <ui/tab.h>
+
+/** Start menu configuration tab */
+typedef struct startmenu {
+	/** Containing taskbar configuration */
+	struct taskbar_cfg *tbarcfg;
+	/** UI tab */
+	ui_tab_t *tab;
+	/** Fixed layout */
+	ui_fixed_t *fixed;
+	/** 'Start menu entries' label */
+	ui_label_t *entries_label;
+	/** List of start menu entries */
+	ui_list_t *entries_list;
+	/** New entry button */
+	ui_pbutton_t *new_entry;
+	/** Delete entry button */
+	ui_pbutton_t *delete_entry;
+	/** Edit entry button */
+	ui_pbutton_t *edit_entry;
+	/** Separator entry button */
+	ui_pbutton_t *sep_entry;
+	/** Move entry up button */
+	ui_pbutton_t *up_entry;
+	/** Move entry down button */
+	ui_pbutton_t *down_entry;
+} startmenu_t;
+
+/** Start menu entry */
+typedef struct startmenu_entry {
+	/** Containing start menu configuration tab */
+	struct startmenu *startmenu;
+	/** Backing entry */
+	smenu_entry_t *entry;
+	/** List entry */
+	ui_list_entry_t *lentry;
+} startmenu_entry_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/taskbar-cfg/types/taskbar-cfg.h
===================================================================
--- uspace/app/taskbar-cfg/types/taskbar-cfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar-cfg/types/taskbar-cfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,65 @@
+/*
+ * 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 taskbar-cfg
+ * @{
+ */
+/**
+ * @file Taskbar configuration utility (UI) types
+ */
+
+#ifndef TYPES_TASKBAR_CFG_H
+#define TYPES_TASKBAR_CFG_H
+
+#include <tbarcfg/tbarcfg.h>
+#include <ui/fixed.h>
+#include <ui/label.h>
+#include <ui/tabset.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+
+/** Taskbar configuration utility (UI) */
+typedef struct taskbar_cfg {
+	/** Taskbar configuration */
+	tbarcfg_t *tbarcfg;
+	/** UI */
+	ui_t *ui;
+	/** Containing window */
+	ui_window_t *window;
+	/** Fixed layout */
+	ui_fixed_t *fixed;
+	/** Tab set */
+	ui_tab_set_t *tabset;
+	/** Start menu configuration tab */
+	struct startmenu *startmenu;
+} taskbar_cfg_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/taskbar/clock.c
===================================================================
--- uspace/app/taskbar/clock.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/clock.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,347 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup taskbar
+ * @{
+ */
+/** @file Taskbar clock.
+ *
+ * Displays the current time in an inset frame.
+ */
+
+#include <errno.h>
+#include <gfx/render.h>
+#include <gfx/text.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <task.h>
+#include <ui/control.h>
+#include <ui/paint.h>
+#include <ui/resource.h>
+#include <ui/ui.h>
+#include "clock.h"
+
+static void taskbar_clock_ctl_destroy(void *);
+static errno_t taskbar_clock_ctl_paint(void *);
+static ui_evclaim_t taskbar_clock_ctl_kbd_event(void *, kbd_event_t *);
+static ui_evclaim_t taskbar_clock_ctl_pos_event(void *, pos_event_t *);
+static void taskbar_clock_timer(void *);
+
+/** Taskbar clock control ops */
+static ui_control_ops_t taskbar_clock_ctl_ops = {
+	.destroy = taskbar_clock_ctl_destroy,
+	.paint = taskbar_clock_ctl_paint,
+	.kbd_event = taskbar_clock_ctl_kbd_event,
+	.pos_event = taskbar_clock_ctl_pos_event
+};
+
+/** Create taskbar clock.
+ *
+ * @param window Containing window
+ * @param rclock Place to store pointer to new clock
+ * @return EOK on success or an error code
+ */
+errno_t taskbar_clock_create(ui_window_t *window, taskbar_clock_t **rclock)
+{
+	taskbar_clock_t *clock;
+	errno_t rc;
+
+	clock = calloc(1, sizeof(taskbar_clock_t));
+	if (clock == NULL)
+		return ENOMEM;
+
+	rc = ui_control_new(&taskbar_clock_ctl_ops, (void *)clock,
+	    &clock->control);
+	if (rc != EOK) {
+		free(clock);
+		return rc;
+	}
+
+	clock->timer = fibril_timer_create(NULL);
+	if (clock->timer == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	fibril_mutex_initialize(&clock->lock);
+	fibril_condvar_initialize(&clock->timer_done_cv);
+	fibril_timer_set(clock->timer, 1000000, taskbar_clock_timer, clock);
+
+	clock->window = window;
+	*rclock = clock;
+	return EOK;
+error:
+	ui_control_delete(clock->control);
+	free(clock);
+	return rc;
+}
+
+/** Destroy taskbar clock.
+ *
+ * @param clock Taskbar clock
+ */
+void taskbar_clock_destroy(taskbar_clock_t *clock)
+{
+	/*
+	 * Signal to the timer that we are cleaning up. If the timer handler
+	 * misses it and sets the timer again, we will clear that active
+	 * timer and be done (and if we were even slower and the timer
+	 * fired again, it's the same situation as before.
+	 */
+	fibril_mutex_lock(&clock->lock);
+	clock->timer_cleanup = true;
+	fibril_mutex_unlock(&clock->lock);
+
+	/* If we catch the timer while it's active, there's nothing to do. */
+	if (fibril_timer_clear(clock->timer) != fts_active) {
+		/* Need to wait for timer handler to finish */
+		fibril_mutex_lock(&clock->lock);
+		while (clock->timer_done == false)
+			fibril_condvar_wait(&clock->timer_done_cv, &clock->lock);
+		fibril_mutex_unlock(&clock->lock);
+	}
+
+	fibril_timer_destroy(clock->timer);
+	ui_control_delete(clock->control);
+	free(clock);
+}
+
+static errno_t taskbar_clock_get_text(taskbar_clock_t *clock, char *buf,
+    size_t bsize)
+{
+	struct timespec ts;
+	struct tm tm;
+	errno_t rc;
+
+	getrealtime(&ts);
+	rc = time_utc2tm(ts.tv_sec, &tm);
+	if (rc != EOK)
+		return rc;
+
+	snprintf(buf, bsize, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min,
+	    tm.tm_sec);
+	return EOK;
+}
+
+/** Paint taskbar clock.
+ *
+ * @param clock Taskbar clock
+ */
+errno_t taskbar_clock_paint(taskbar_clock_t *clock)
+{
+	gfx_context_t *gc = ui_window_get_gc(clock->window);
+	ui_resource_t *res = ui_window_get_res(clock->window);
+	ui_t *ui = ui_window_get_ui(clock->window);
+	char buf[10];
+	gfx_text_fmt_t fmt;
+	gfx_coord2_t pos;
+	gfx_rect_t irect;
+	errno_t rc;
+
+	if (!ui_is_textmode(ui)) {
+		/* Paint frame */
+		rc = ui_paint_inset_frame(res, &clock->rect, &irect);
+		if (rc != EOK)
+			goto error;
+	} else {
+		irect = clock->rect;
+	}
+
+	rc = gfx_set_color(gc, ui_resource_get_wnd_face_color(res));
+	if (rc != EOK)
+		goto error;
+
+	/* Fill background */
+	rc = gfx_fill_rect(gc, &irect);
+	if (rc != EOK)
+		goto error;
+
+	pos.x = (irect.p0.x + irect.p1.x) / 2;
+	pos.y = (irect.p0.y + irect.p1.y) / 2;
+
+	gfx_text_fmt_init(&fmt);
+	fmt.font = ui_resource_get_font(res);
+	fmt.color = ui_resource_get_wnd_text_color(res);
+	fmt.halign = gfx_halign_center;
+	fmt.valign = gfx_valign_center;
+
+	rc = taskbar_clock_get_text(clock, buf, sizeof(buf));
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_puttext(&pos, &fmt, buf);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_update(gc);
+	if (rc != EOK)
+		goto error;
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Handle taskbar clock keyboard event.
+ *
+ * @param clock Taskbar clock
+ * @param event Keyboard event
+ * @return ui_claimed iff event was claimed
+ */
+ui_evclaim_t taskbar_clock_kbd_event(taskbar_clock_t *clock, kbd_event_t *event)
+{
+	return ui_unclaimed;
+}
+
+/** Handle taskbar clock position event.
+ *
+ * @param clock Taskbar clock
+ * @param event Position event
+ * @return ui_claimed iff event was claimed
+ */
+ui_evclaim_t taskbar_clock_pos_event(taskbar_clock_t *clock, pos_event_t *event)
+{
+	gfx_coord2_t pos;
+
+	pos.x = event->hpos;
+	pos.y = event->vpos;
+	if (!gfx_pix_inside_rect(&pos, &clock->rect))
+		return ui_unclaimed;
+
+	return ui_claimed;
+}
+
+/** Get base control for taskbar clock.
+ *
+ * @param clock Taskbar clock
+ * @return Base UI control
+ */
+ui_control_t *taskbar_clock_ctl(taskbar_clock_t *clock)
+{
+	return clock->control;
+}
+
+/** Set taskbar clock rectangle.
+ *
+ * @param clock Taskbar clock
+ * @param rect Rectangle
+ */
+void taskbar_clock_set_rect(taskbar_clock_t *clock, gfx_rect_t *rect)
+{
+	gfx_rect_t irect;
+
+	clock->rect = *rect;
+
+	irect.p0.x = clock->rect.p0.x + 1;
+	irect.p0.y = clock->rect.p0.y + 1;
+	irect.p1.x = clock->rect.p1.x;
+	irect.p1.y = clock->rect.p1.y - 1;
+
+	(void)irect;
+}
+
+/** Destroy clock control.
+ *
+ * @param arg Argument (taskbar_clock_t *)
+ */
+void taskbar_clock_ctl_destroy(void *arg)
+{
+	taskbar_clock_t *clock = (taskbar_clock_t *) arg;
+
+	taskbar_clock_destroy(clock);
+}
+
+/** Paint taskbar clock control.
+ *
+ * @param arg Argument (taskbar_clock_t *)
+ * @return EOK on success or an error code
+ */
+errno_t taskbar_clock_ctl_paint(void *arg)
+{
+	taskbar_clock_t *clock = (taskbar_clock_t *) arg;
+
+	return taskbar_clock_paint(clock);
+}
+
+/** Handle taskbar clock control keyboard event.
+ *
+ * @param arg Argument (taskbar_clock_t *)
+ * @param kbd_event Keyboard event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t taskbar_clock_ctl_kbd_event(void *arg, kbd_event_t *event)
+{
+	taskbar_clock_t *clock = (taskbar_clock_t *) arg;
+
+	return taskbar_clock_kbd_event(clock, event);
+}
+
+/** Handle taskbar clock control position event.
+ *
+ * @param arg Argument (taskbar_clock_t *)
+ * @param pos_event Position event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t taskbar_clock_ctl_pos_event(void *arg, pos_event_t *event)
+{
+	taskbar_clock_t *clock = (taskbar_clock_t *) arg;
+
+	return taskbar_clock_pos_event(clock, event);
+}
+
+/** Taskbar clock timer handler.
+ *
+ * @param arg Argument (taskbar_clock_t *)
+ */
+static void taskbar_clock_timer(void *arg)
+{
+	taskbar_clock_t *clock = (taskbar_clock_t *) arg;
+	ui_t *ui;
+
+	ui = ui_window_get_ui(clock->window);
+	ui_lock(ui);
+
+	fibril_mutex_lock(&clock->lock);
+	if (!ui_is_suspended(ui_window_get_ui(clock->window)))
+		(void) taskbar_clock_paint(clock);
+
+	if (!clock->timer_cleanup) {
+		fibril_timer_set(clock->timer, 1000000, taskbar_clock_timer,
+		    clock);
+	} else {
+		/* Acknowledge timer cleanup */
+		clock->timer_done = true;
+		fibril_condvar_signal(&clock->timer_done_cv);
+	}
+
+	fibril_mutex_unlock(&clock->lock);
+	ui_unlock(ui);
+}
+
+/** @}
+ */
Index: uspace/app/taskbar/clock.h
===================================================================
--- uspace/app/taskbar/clock.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/clock.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup taskbar
+ * @{
+ */
+/**
+ * @file Taskbar clock
+ */
+
+#ifndef CLOCK_H
+#define CLOCK_H
+
+#include <errno.h>
+#include <gfx/coord.h>
+#include <io/kbd_event.h>
+#include <io/pos_event.h>
+#include <ui/control.h>
+#include <ui/window.h>
+#include <stdbool.h>
+#include "types/clock.h"
+
+extern errno_t taskbar_clock_create(ui_window_t *, taskbar_clock_t **);
+extern void taskbar_clock_destroy(taskbar_clock_t *);
+extern errno_t taskbar_clock_paint(taskbar_clock_t *);
+extern ui_evclaim_t taskbar_clock_kbd_event(taskbar_clock_t *, kbd_event_t *);
+extern ui_evclaim_t taskbar_clock_pos_event(taskbar_clock_t *, pos_event_t *);
+extern ui_control_t *taskbar_clock_ctl(taskbar_clock_t *);
+extern void taskbar_clock_set_rect(taskbar_clock_t *, gfx_rect_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/taskbar/doc/doxygroups.h
===================================================================
--- uspace/app/taskbar/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,4 @@
+/** @addtogroup taskbar taskbar
+ * @brief Taskbar
+ * @ingroup apps
+ */
Index: uspace/app/taskbar/main.c
===================================================================
--- uspace/app/taskbar/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup taskbar
+ * @{
+ */
+/** @file Taskbar
+ */
+
+#include <stdio.h>
+#include <str.h>
+#include <ui/ui.h>
+#include "taskbar.h"
+
+static void print_syntax(void)
+{
+	printf("Syntax: taskbar [-d <display-spec>]\n");
+}
+
+int main(int argc, char *argv[])
+{
+	const char *display_spec = UI_ANY_DEFAULT;
+	const char *wndmgt_svc = WNDMGT_DEFAULT;
+	taskbar_t *taskbar;
+	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], "-w") == 0) {
+			++i;
+			if (i >= argc) {
+				printf("Argument missing.\n");
+				print_syntax();
+				return 1;
+			}
+
+			wndmgt_svc = argv[i++];
+		} else {
+			printf("Invalid option '%s'.\n", argv[i]);
+			print_syntax();
+			return 1;
+		}
+	}
+
+	if (i < argc) {
+		print_syntax();
+		return 1;
+	}
+
+	rc = taskbar_create(display_spec, wndmgt_svc, &taskbar);
+	if (rc != EOK)
+		return 1;
+
+	ui_run(taskbar->ui);
+	taskbar_destroy(taskbar);
+
+	return 0;
+}
+
+/** @}
+ */
Index: uspace/app/taskbar/meson.build
===================================================================
--- uspace/app/taskbar/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,50 @@
+#
+# 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.
+#
+
+deps = [ 'tbarcfg', 'ui', 'wndmgt' ]
+src = files(
+	'clock.c',
+	'main.c',
+	'taskbar.c',
+	'tbsmenu.c',
+	'wndlist.c',
+)
+
+test_src = files(
+	'clock.c',
+	'taskbar.c',
+	'tbsmenu.c',
+	'wndlist.c',
+	'test/clock.c',
+	'test/main.c',
+	'test/taskbar.c',
+	'test/tbsmenu.c',
+	'test/wndlist.c',
+)
+
+installed_data += { 'name': 'taskbar.sif', 'dir': '/cfg' }
Index: uspace/app/taskbar/taskbar.c
===================================================================
--- uspace/app/taskbar/taskbar.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/taskbar.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,363 @@
+/*
+ * 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 taskbar
+ * @{
+ */
+/** @file Taskbar
+ */
+
+#include <gfx/coord.h>
+#include <io/pos_event.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <str.h>
+#include <ui/fixed.h>
+#include <ui/resource.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include <wndmgt.h>
+#include "clock.h"
+#include "taskbar.h"
+#include "tbsmenu.h"
+#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 = {
+	.close = taskbar_wnd_close,
+	.kbd = taskbar_wnd_kbd,
+	.pos = taskbar_wnd_pos
+};
+
+/** Window close button was clicked.
+ *
+ * @param window Window
+ * @param arg Argument (taskbar)
+ */
+static void taskbar_wnd_close(ui_window_t *window, void *arg)
+{
+	taskbar_t *taskbar = (taskbar_t *) arg;
+
+	ui_quit(taskbar->ui);
+}
+
+/** Window received keyboard event.
+ *
+ * @param window Window
+ * @param arg Argument (taskbar)
+ * @param event Keyboard event
+ */
+static void taskbar_wnd_kbd(ui_window_t *window, void *arg, kbd_event_t *event)
+{
+	taskbar_t *taskbar = (taskbar_t *) arg;
+	ui_evclaim_t claim;
+
+	/* Remember ID of device that sent the last event */
+	taskbar->wndlist->ev_idev_id = event->kbd_id;
+	taskbar->tbsmenu->ev_idev_id = event->kbd_id;
+
+	claim = ui_window_def_kbd(window, event);
+	if (claim == ui_claimed)
+		return;
+
+	if (event->type == KEY_PRESS && (event->mods & KM_CTRL) == 0 &&
+	    (event->mods & KM_ALT) == 0 && (event->mods & KM_SHIFT) == 0 &&
+	    event->key == KC_ENTER) {
+		if (!tbsmenu_is_open(taskbar->tbsmenu))
+			tbsmenu_open(taskbar->tbsmenu);
+	}
+}
+
+/** Window received position event.
+ *
+ * @param window Window
+ * @param arg Argument (taskbar)
+ * @param event Position event
+ */
+static void taskbar_wnd_pos(ui_window_t *window, void *arg, pos_event_t *event)
+{
+	taskbar_t *taskbar = (taskbar_t *) arg;
+
+	/* Remember ID of device that sent the last event */
+	taskbar->wndlist->ev_idev_id = event->pos_id;
+	taskbar->tbsmenu->ev_idev_id = event->pos_id;
+
+	ui_window_def_pos(window, event);
+}
+
+/** Create taskbar.
+ *
+ * @param display_spec Display specification
+ * @param wndmgt_svc Window management service (or WNDMGT_DEFAULT)
+ * @param rtaskbar Place to store pointer to new taskbar
+ * @return @c EOK on success or an error coe
+ */
+errno_t taskbar_create(const char *display_spec, const char *wndmgt_svc,
+    taskbar_t **rtaskbar)
+{
+	ui_wnd_params_t params;
+	taskbar_t *taskbar = NULL;
+	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);
+	if (rc != EOK) {
+		printf("Error creating UI on display %s.\n", display_spec);
+		goto error;
+	}
+
+	rc = ui_get_rect(taskbar->ui, &scr_rect);
+	if (rc != EOK) {
+		if (str_cmp(display_spec, UI_DISPLAY_NULL) != 0) {
+			printf("Error getting screen dimensions.\n");
+			goto error;
+		}
+
+		/* For the sake of unit tests */
+		scr_rect.p0.x = 0;
+		scr_rect.p0.y = 0;
+		scr_rect.p1.x = 100;
+		scr_rect.p1.y = 100;
+	}
+
+	ui_wnd_params_init(&params);
+	params.caption = "Taskbar";
+	params.placement = ui_wnd_place_bottom_left;
+
+	/* Window has no titlebar */
+	params.style &= ~ui_wds_titlebar;
+
+	/* Window is not obscured by other windows */
+	params.flags |= ui_wndf_topmost;
+
+	/* Prevent taskbar window from being listed in taskbar */
+	params.flags |= ui_wndf_system;
+
+	/* Make maximized windows avoid taskbar */
+	params.flags |= ui_wndf_avoid;
+
+	params.rect.p0.x = 0;
+	params.rect.p0.y = 0;
+	params.rect.p1.x = scr_rect.p1.x - scr_rect.p0.x;
+
+	if (ui_is_textmode(taskbar->ui)) {
+		params.rect.p1.y = 1;
+		params.style &= ~ui_wds_frame;
+	} else {
+		params.rect.p1.y = 32;
+	}
+
+	rc = ui_window_create(taskbar->ui, &params, &taskbar->window);
+	if (rc != EOK) {
+		printf("Error creating window.\n");
+		goto error;
+	}
+
+	rc = ui_fixed_create(&taskbar->fixed);
+	if (rc != EOK) {
+		printf("Error creating fixed layout.\n");
+		goto error;
+	}
+
+	rc = tbsmenu_create(taskbar->window, taskbar->fixed, dspec,
+	    &taskbar->tbsmenu);
+	if (rc != EOK) {
+		printf("Error creating start menu.\n");
+		goto error;
+	}
+
+	rc = tbsmenu_load(taskbar->tbsmenu, TASKBAR_CONFIG_FILE);
+	if (rc != EOK) {
+		printf("Error loading start menu from '%s'.\n",
+		    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");
+	}
+
+	if (ui_is_textmode(taskbar->ui)) {
+		rect.p0.x = params.rect.p0.x + 1;
+		rect.p0.y = 0;
+		rect.p1.x = params.rect.p0.x + 9;
+		rect.p1.y = 1;
+	} else {
+		rect.p0.x = params.rect.p0.x + 5;
+		rect.p0.y = 4;
+		rect.p1.x = params.rect.p0.x + 84;
+		rect.p1.y = 32 - 4;
+	}
+
+	tbsmenu_set_rect(taskbar->tbsmenu, &rect);
+
+	rc = wndlist_create(taskbar->window, taskbar->fixed, &taskbar->wndlist);
+	if (rc != EOK) {
+		printf("Error creating window list.\n");
+		goto error;
+	}
+
+	if (ui_is_textmode(taskbar->ui)) {
+		rect.p0.x = params.rect.p0.x + 10;
+		rect.p0.y = 0;
+		rect.p1.x = params.rect.p1.x - 10;
+		rect.p1.y = 1;
+	} else {
+		rect.p0.x = params.rect.p0.x + 90;
+		rect.p0.y = 4;
+		rect.p1.x = params.rect.p1.x - 84;
+		rect.p1.y = 32 - 4;
+	}
+	wndlist_set_rect(taskbar->wndlist, &rect);
+
+	/*
+	 * We may not be able to open WM service if display server is not
+	 * running. That's okay, there simply are no windows to manage.
+	 */
+	rc = wndlist_open_wm(taskbar->wndlist, wndmgt_svc);
+	if (rc != EOK && rc != ENOENT) {
+		printf("Error attaching window management service.\n");
+		goto error;
+	}
+
+	rc = taskbar_clock_create(taskbar->window, &taskbar->clock);
+	if (rc != EOK)
+		goto error;
+
+	if (ui_is_textmode(taskbar->ui)) {
+		rect.p0.x = params.rect.p1.x - 10;
+		rect.p0.y = 0;
+		rect.p1.x = params.rect.p1.x;
+		rect.p1.y = 1;
+	} else {
+		rect.p0.x = params.rect.p1.x - 80;
+		rect.p0.y = 4;
+		rect.p1.x = params.rect.p1.x - 4;
+		rect.p1.y = 32 - 4;
+	}
+
+	taskbar_clock_set_rect(taskbar->clock, &rect);
+
+	rc = ui_fixed_add(taskbar->fixed, taskbar_clock_ctl(taskbar->clock));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		taskbar_clock_destroy(taskbar->clock);
+		goto error;
+	}
+
+	ui_window_add(taskbar->window, ui_fixed_ctl(taskbar->fixed));
+	ui_window_set_cb(taskbar->window, &window_cb, (void *)taskbar);
+
+	rc = ui_window_paint(taskbar->window);
+	if (rc != EOK) {
+		printf("Error painting window.\n");
+		goto error;
+	}
+
+	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);
+	if (taskbar->wndlist != NULL)
+		wndlist_destroy(taskbar->wndlist);
+	if (taskbar->tbsmenu != NULL)
+		tbsmenu_destroy(taskbar->tbsmenu);
+	if (taskbar->window != NULL)
+		ui_window_destroy(taskbar->window);
+	if (taskbar->ui != NULL)
+		ui_destroy(taskbar->ui);
+	free(taskbar);
+	return rc;
+
+}
+
+/** Destroy taskbar. */
+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);
+	wndlist_destroy(taskbar->wndlist);
+	tbsmenu_destroy(taskbar->tbsmenu);
+	ui_window_destroy(taskbar->window);
+	ui_destroy(taskbar->ui);
+}
+
+/** 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.h
===================================================================
--- uspace/app/taskbar/taskbar.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/taskbar.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup taskbar
+ * @{
+ */
+/**
+ * @file Taskbar
+ */
+
+#ifndef TASKBAR_H
+#define TASKBAR_H
+
+#include <errno.h>
+#include <wndmgt.h>
+#include "types/taskbar.h"
+
+extern errno_t taskbar_create(const char *, const char *, taskbar_t **);
+extern void taskbar_destroy(taskbar_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/taskbar/taskbar.sif
===================================================================
--- uspace/app/taskbar/taskbar.sif	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/taskbar.sif	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +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 -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 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/tbsmenu.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,701 @@
+/*
+ * 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 taskbar
+ * @{
+ */
+/** @file Taskbar start menu
+ */
+
+#include <gfx/coord.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <str.h>
+#include <task.h>
+#include <tbarcfg/tbarcfg.h>
+#include <ui/fixed.h>
+#include <ui/menu.h>
+#include <ui/menuentry.h>
+#include <ui/resource.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include "tbsmenu.h"
+
+static void tbsmenu_smenu_close_req(ui_menu_t *, void *);
+
+/** Start menu callbacks */
+static ui_menu_cb_t tbsmenu_smenu_cb = {
+	.close_req = tbsmenu_smenu_close_req,
+};
+
+static void tbsmenu_button_down(ui_pbutton_t *, void *);
+
+/** Start button callbacks */
+static ui_pbutton_cb_t tbsmenu_button_cb = {
+	.down = tbsmenu_button_down
+};
+
+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.
+ *
+ * @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,
+    const char *dspec, tbsmenu_t **rtbsmenu)
+{
+	ui_resource_t *res = ui_window_get_res(window);
+	tbsmenu_t *tbsmenu = NULL;
+	errno_t rc;
+
+	tbsmenu = calloc(1, sizeof(tbsmenu_t));
+	if (tbsmenu == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	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)
+		goto error;
+
+	ui_pbutton_set_cb(tbsmenu->sbutton, &tbsmenu_button_cb,
+	    (void *)tbsmenu);
+
+	ui_pbutton_set_default(tbsmenu->sbutton, true);
+
+	rc = ui_fixed_add(fixed, ui_pbutton_ctl(tbsmenu->sbutton));
+	if (rc != EOK)
+		goto error;
+
+	rc = ui_menu_create(window, &tbsmenu->smenu);
+	if (rc != EOK)
+		goto error;
+
+	ui_menu_set_cb(tbsmenu->smenu, &tbsmenu_smenu_cb, (void *)tbsmenu);
+
+	tbsmenu->window = window;
+	tbsmenu->fixed = fixed;
+	list_initialize(&tbsmenu->entries);
+
+	*rtbsmenu = tbsmenu;
+	return EOK;
+error:
+	if (tbsmenu != NULL && tbsmenu->display_spec != NULL)
+		free(tbsmenu->display_spec);
+	if (tbsmenu != NULL)
+		ui_pbutton_destroy(tbsmenu->sbutton);
+	if (tbsmenu != NULL)
+		free(tbsmenu);
+	return rc;
+}
+
+/** Load start menu from repository.
+ *
+ * @param tbsmenu Start menu
+ * @param Repository path
+ * @return EOK on success or an error code
+ */
+errno_t tbsmenu_load(tbsmenu_t *tbsmenu, const char *repopath)
+{
+	tbsmenu_entry_t *tentry;
+	tbarcfg_t *tbcfg = NULL;
+	smenu_entry_t *sme;
+	bool separator;
+	const char *caption;
+	const char *cmd;
+	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);
+	if (rc != EOK)
+		goto error;
+
+	sme = tbarcfg_smenu_first(tbcfg);
+	while (sme != NULL) {
+		separator = smenu_entry_get_separator(sme);
+		if (separator == false) {
+			caption = smenu_entry_get_caption(sme);
+			cmd = smenu_entry_get_cmd(sme);
+			terminal = smenu_entry_get_terminal(sme);
+
+			rc = tbsmenu_add(tbsmenu, caption, cmd, terminal,
+			    &tentry);
+			if (rc != EOK)
+				goto error;
+		} else {
+			rc = tbsmenu_add_sep(tbsmenu, &tentry);
+			if (rc != EOK)
+				goto error;
+		}
+
+		(void)tentry;
+
+		sme = tbarcfg_smenu_next(sme);
+	}
+
+	tbarcfg_close(tbcfg);
+	return EOK;
+error:
+	if (tbcfg != NULL)
+		tbarcfg_close(tbcfg);
+	return rc;
+}
+
+/** 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.
+ *
+ * @param tbsmenu Start menu
+ * @param rect Rectangle
+ */
+void tbsmenu_set_rect(tbsmenu_t *tbsmenu, gfx_rect_t *rect)
+{
+	tbsmenu->rect = *rect;
+	ui_pbutton_set_rect(tbsmenu->sbutton, rect);
+}
+
+/** Open taskbar start menu.
+ *
+ * @param tbsmenu Start menu
+ */
+void tbsmenu_open(tbsmenu_t *tbsmenu)
+{
+	(void) ui_menu_open(tbsmenu->smenu, &tbsmenu->rect,
+	    tbsmenu->ev_idev_id);
+}
+
+/** Close taskbar start menu.
+ *
+ * @param tbsmenu Start menu
+ */
+void tbsmenu_close(tbsmenu_t *tbsmenu)
+{
+	ui_menu_close(tbsmenu->smenu);
+
+	if (tbsmenu->needs_reload)
+		(void) tbsmenu_load(tbsmenu, tbsmenu->repopath);
+}
+
+/** Determine if taskbar start menu is open.
+ *
+ * @param tbsmenu Start menu
+ * @return @c true iff start menu is open
+ */
+bool tbsmenu_is_open(tbsmenu_t *tbsmenu)
+{
+	return ui_menu_is_open(tbsmenu->smenu);
+}
+
+/** Destroy taskbar start menu.
+ *
+ * @param tbsmenu Start menu
+ */
+void tbsmenu_destroy(tbsmenu_t *tbsmenu)
+{
+	tbsmenu_entry_t *entry;
+
+	/* Destroy entries */
+	entry = tbsmenu_first(tbsmenu);
+	while (entry != NULL) {
+		tbsmenu_remove(tbsmenu, entry, false);
+		entry = tbsmenu_first(tbsmenu);
+	}
+
+	ui_fixed_remove(tbsmenu->fixed, ui_pbutton_ctl(tbsmenu->sbutton));
+	ui_pbutton_destroy(tbsmenu->sbutton);
+	ui_menu_destroy(tbsmenu->smenu);
+
+	free(tbsmenu);
+}
+
+/** Add entry to start menu.
+ *
+ * @param tbsmenu Start menu
+ * @param caption Caption
+ * @param cmd Command to run
+ * @param terminal Start in terminal
+ * @param entry Start menu entry
+ * @return @c EOK on success or an error code
+ */
+errno_t tbsmenu_add(tbsmenu_t *tbsmenu, const char *caption,
+    const char *cmd, bool terminal, tbsmenu_entry_t **rentry)
+{
+	errno_t rc;
+	tbsmenu_entry_t *entry;
+
+	entry = calloc(1, sizeof(tbsmenu_entry_t));
+	if (entry == NULL)
+		return ENOMEM;
+
+	entry->caption = str_dup(caption);
+	if (entry->caption == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	entry->cmd = str_dup(cmd);
+	if (entry->cmd == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	entry->terminal = terminal;
+
+	rc = ui_menu_entry_create(tbsmenu->smenu, caption, "", &entry->mentry);
+	if (rc != EOK)
+		goto error;
+
+	ui_menu_entry_set_cb(entry->mentry, tbsmenu_smenu_entry_cb,
+	    (void *)entry);
+
+	entry->tbsmenu = tbsmenu;
+	list_append(&entry->lentries, &tbsmenu->entries);
+	*rentry = entry;
+	return EOK;
+error:
+	if (entry->caption != NULL)
+		free(entry->caption);
+	if (entry->cmd != NULL)
+		free(entry->cmd);
+	free(entry);
+	return rc;
+}
+
+/** Add separator entry to start menu.
+ *
+ * @param tbsmenu Start menu
+ * @param entry Start menu entry
+ * @return @c EOK on success or an error code
+ */
+errno_t tbsmenu_add_sep(tbsmenu_t *tbsmenu, tbsmenu_entry_t **rentry)
+{
+	errno_t rc;
+	tbsmenu_entry_t *entry;
+
+	entry = calloc(1, sizeof(tbsmenu_entry_t));
+	if (entry == NULL)
+		return ENOMEM;
+
+	rc = ui_menu_entry_sep_create(tbsmenu->smenu, &entry->mentry);
+	if (rc != EOK)
+		goto error;
+
+	ui_menu_entry_set_cb(entry->mentry, tbsmenu_smenu_entry_cb,
+	    (void *)entry);
+
+	entry->tbsmenu = tbsmenu;
+	list_append(&entry->lentries, &tbsmenu->entries);
+	*rentry = entry;
+	return EOK;
+error:
+	free(entry);
+	return rc;
+}
+
+/** Remove entry from start menu.
+ *
+ * @param tbsmenu Start menu
+ * @param entry Start menu entry
+ * @param paint @c true to repaint start menu
+ */
+void tbsmenu_remove(tbsmenu_t *tbsmenu, tbsmenu_entry_t *entry,
+    bool paint)
+{
+	assert(entry->tbsmenu == tbsmenu);
+
+	list_remove(&entry->lentries);
+
+	ui_menu_entry_destroy(entry->mentry);
+	free(entry->caption);
+	free(entry->cmd);
+	free(entry);
+}
+
+/** Handle start menu close request.
+ *
+ * @param menu Menu
+ * @param arg Argument (tbsmenu_t *)
+ * @param wnd_id Window ID
+ */
+static void tbsmenu_smenu_close_req(ui_menu_t *menu, void *arg)
+{
+	tbsmenu_t *tbsmenu = (tbsmenu_t *)arg;
+
+	(void)tbsmenu;
+	ui_menu_close(menu);
+}
+
+/** Start menu entry was activated.
+ *
+ * @param smentry Start menu entry
+ * @param arg Argument (tbsmenu_entry_t *)
+ */
+static void tbsmenu_smenu_entry_cb(ui_menu_entry_t *smentry, void *arg)
+{
+	tbsmenu_entry_t *entry = (tbsmenu_entry_t *)arg;
+
+	(void)tbsmenu_entry_start(entry);
+}
+
+/** Get first start menu entry.
+ *
+ * @param tbsmenu Start menu
+ * @return First entry or @c NULL if the menu is empty
+ */
+tbsmenu_entry_t *tbsmenu_first(tbsmenu_t *tbsmenu)
+{
+	link_t *link;
+
+	link = list_first(&tbsmenu->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, tbsmenu_entry_t, lentries);
+}
+
+/** Get last start menu entry.
+ *
+ * @param tbsmenu Start menu
+ * @return Last entry or @c NULL if the menu is empty
+ */
+tbsmenu_entry_t *tbsmenu_last(tbsmenu_t *tbsmenu)
+{
+	link_t *link;
+
+	link = list_last(&tbsmenu->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, tbsmenu_entry_t, lentries);
+}
+
+/** Get next start menu entry.
+ *
+ * @param cur Current entry
+ * @return Next entry or @c NULL if @a cur is the last entry
+ */
+tbsmenu_entry_t *tbsmenu_next(tbsmenu_entry_t *cur)
+{
+	link_t *link;
+
+	link = list_next(&cur->lentries, &cur->tbsmenu->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, tbsmenu_entry_t, lentries);
+}
+
+/** Get number of start menu entries.
+ *
+ * @param tbsmenu Start menu
+ * @return Number of entries
+ */
+size_t tbsmenu_count(tbsmenu_t *tbsmenu)
+{
+	return list_count(&tbsmenu->entries);
+}
+
+/** Start button was depressed.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (tbsmenu_entry_t *)
+ */
+static void tbsmenu_button_down(ui_pbutton_t *pbutton, void *arg)
+{
+	tbsmenu_t *tbsmenu = (tbsmenu_t *)arg;
+
+	if (!tbsmenu_is_open(tbsmenu)) {
+		tbsmenu_open(tbsmenu);
+	} else {
+		/* menu is open */
+		tbsmenu_close(tbsmenu);
+	}
+}
+
+/** Split command string into individual parts.
+ *
+ * Command arguments are separated by spaces. There is no way
+ * to provide an argument containing spaces.
+ *
+ * @param str Command with arguments separated by spaces
+ * @param cmd Command structure to fill in
+ * @return EOK on success or an error code
+ */
+static errno_t tbsmenu_cmd_split(const char *str, tbsmenu_cmd_t *cmd)
+{
+	char *buf;
+	char *arg;
+	char *next;
+	size_t cnt;
+
+	buf = str_dup(str);
+	if (buf == NULL)
+		return ENOMEM;
+
+	/* Count the entries */
+	cnt = 0;
+	arg = str_tok(buf, " ", &next);
+	while (arg != NULL) {
+		++cnt;
+		arg = str_tok(next, " ", &next);
+	}
+
+	/* Need to copy again as buf was mangled */
+	free(buf);
+	buf = str_dup(str);
+	if (buf == NULL)
+		return ENOMEM;
+
+	cmd->argv = calloc(cnt + 1, sizeof(char *));
+	if (cmd->argv == NULL) {
+		free(buf);
+		return ENOMEM;
+	}
+
+	/* Copy individual arguments */
+	cnt = 0;
+	arg = str_tok(buf, " ", &next);
+	while (arg != NULL) {
+		cmd->argv[cnt] = str_dup(arg);
+		if (cmd->argv[cnt] == NULL) {
+			tbsmenu_cmd_fini(cmd);
+			return ENOMEM;
+		}
+		++cnt;
+
+		arg = str_tok(next, " ", &next);
+	}
+
+	cmd->argv[cnt] = NULL;
+
+	return EOK;
+}
+
+/** Free command structure.
+ *
+ * @param cmd Command
+ */
+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 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;
+}
+
+/** Execute start menu entry.
+ *
+ * @param entry Start menu entry
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t tbsmenu_entry_start(tbsmenu_entry_t *entry)
+{
+	task_id_t id;
+	task_wait_t wait;
+	task_exit_t texit;
+	tbsmenu_cmd_t cmd;
+	int retval;
+	bool suspended;
+	int i;
+	int cnt;
+	char **cp;
+	const char **targv = NULL;
+	char *dspec = NULL;
+	sysarg_t idev_id;
+	int rv;
+	errno_t rc;
+	ui_t *ui;
+
+	ui = ui_window_get_ui(entry->tbsmenu->window);
+	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) {
+		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. */
+	rc = ui_suspend(ui);
+	if (rc != EOK)
+		goto error;
+
+	suspended = true;
+
+	/* Don't start in terminal if not running in a window */
+	if (entry->terminal && !ui_is_fullscreen(ui)) {
+		cnt = 0;
+		cp = cmd.argv;
+		while (*cp != NULL) {
+			++cnt;
+			++cp;
+		}
+
+		targv = calloc(cnt + 5, sizeof(char **));
+		if (targv == NULL)
+			goto error;
+
+		targv[0] = "/app/terminal";
+		targv[1] = "-d";
+		targv[2] = dspec;
+		targv[3] = "-c";
+
+		for (i = 0; i <= cnt; i++) {
+			targv[4 + i] = cmd.argv[i];
+		}
+
+		rc = task_spawnv(&id, &wait, targv[0], targv);
+		if (rc != EOK)
+			goto error;
+
+		free(targv);
+		targv = NULL;
+	} else {
+		rc = task_spawnv(&id, &wait, cmd.argv[0], (const char *const *)
+		    cmd.argv);
+		if (rc != EOK)
+			goto error;
+	}
+
+	rc = task_wait(&wait, &texit, &retval);
+	if ((rc != EOK) || (texit != TASK_EXIT_NORMAL))
+		goto error;
+
+	/* Resume UI operation */
+	rc = ui_resume(ui);
+	if (rc != EOK)
+		goto error;
+
+	(void) ui_paint(ui);
+	return EOK;
+error:
+	free(dspec);
+	if (targv != NULL)
+		free(targv);
+	tbsmenu_cmd_fini(&cmd);
+	if (suspended) {
+		rc = ui_resume(ui);
+		if (rc != EOK) {
+			printf("Failed to resume UI.\n");
+			exit(1);
+		}
+	}
+	(void) ui_paint(ui);
+	return rc;
+}
+
+/** @}
+ */
Index: uspace/app/taskbar/tbsmenu.h
===================================================================
--- uspace/app/taskbar/tbsmenu.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/tbsmenu.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,69 @@
+/*
+ * 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 taskbar
+ * @{
+ */
+/**
+ * @file Taskbar start menu
+ */
+
+#ifndef TBSMENU_H
+#define TBSMENU_H
+
+#include <errno.h>
+#include <gfx/coord.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <ui/fixed.h>
+#include <ui/window.h>
+#include <wndmgt.h>
+#include "types/tbsmenu.h"
+
+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 *);
+extern void tbsmenu_close(tbsmenu_t *);
+extern bool tbsmenu_is_open(tbsmenu_t *);
+extern void tbsmenu_destroy(tbsmenu_t *);
+extern errno_t tbsmenu_add(tbsmenu_t *, const char *, const char *, bool,
+    tbsmenu_entry_t **);
+extern errno_t tbsmenu_add_sep(tbsmenu_t *, tbsmenu_entry_t **);
+extern void tbsmenu_remove(tbsmenu_t *, tbsmenu_entry_t *, bool);
+extern tbsmenu_entry_t *tbsmenu_first(tbsmenu_t *);
+extern tbsmenu_entry_t *tbsmenu_last(tbsmenu_t *);
+extern tbsmenu_entry_t *tbsmenu_next(tbsmenu_entry_t *);
+extern size_t tbsmenu_count(tbsmenu_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/taskbar/test/clock.c
===================================================================
--- uspace/app/taskbar/test/clock.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/test/clock.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <gfx/coord.h>
+#include <pcut/pcut.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include "../clock.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(clock);
+
+/** Creating and destroying taskbar clock */
+PCUT_TEST(create_destroy)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	taskbar_clock_t *clock;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = taskbar_clock_create(window, &clock);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	taskbar_clock_destroy(clock);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Painting taskbar clock */
+PCUT_TEST(paint)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	taskbar_clock_t *clock;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = taskbar_clock_create(window, &clock);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = taskbar_clock_paint(clock);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	taskbar_clock_destroy(clock);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Keyboard event */
+PCUT_TEST(kbd_event)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	taskbar_clock_t *clock;
+	kbd_event_t event;
+	ui_evclaim_t claim;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = taskbar_clock_create(window, &clock);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	event.type = KEY_PRESS;
+	event.key = KC_ENTER;
+	event.mods = 0;
+	claim = taskbar_clock_kbd_event(clock, &event);
+	PCUT_ASSERT_EQUALS(ui_unclaimed, claim);
+
+	taskbar_clock_destroy(clock);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Position event */
+PCUT_TEST(pos_event)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	taskbar_clock_t *clock;
+	pos_event_t event;
+	ui_evclaim_t claim;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = taskbar_clock_create(window, &clock);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	event.type = POS_PRESS;
+	event.hpos = 0;
+	event.vpos = 0;
+	claim = taskbar_clock_pos_event(clock, &event);
+	PCUT_ASSERT_EQUALS(ui_unclaimed, claim);
+
+	taskbar_clock_destroy(clock);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** taskbar_clock_ctl returns a usable UI control */
+PCUT_TEST(ctl)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	taskbar_clock_t *clock;
+	ui_control_t *ctl;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = taskbar_clock_create(window, &clock);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ctl = taskbar_clock_ctl(clock);
+	PCUT_ASSERT_NOT_NULL(ctl);
+
+	rc = ui_control_paint(ctl);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	taskbar_clock_destroy(clock);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** taskbar_clock_set_rect() sets internal field */
+PCUT_TEST(set_rect)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	taskbar_clock_t *clock;
+	gfx_rect_t rect;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = taskbar_clock_create(window, &clock);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 1;
+	rect.p0.y = 2;
+	rect.p1.x = 3;
+	rect.p1.y = 4;
+	taskbar_clock_set_rect(clock, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(rect.p0.x, clock->rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(rect.p0.y, clock->rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(rect.p1.x, clock->rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(rect.p1.y, clock->rect.p1.y);
+
+	taskbar_clock_destroy(clock);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+PCUT_EXPORT(clock);
Index: uspace/app/taskbar/test/main.c
===================================================================
--- uspace/app/taskbar/test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#include <pcut/pcut.h>
+
+PCUT_INIT;
+
+PCUT_IMPORT(clock);
+PCUT_IMPORT(taskbar);
+PCUT_IMPORT(tbsmenu);
+PCUT_IMPORT(wndlist);
+
+PCUT_MAIN();
Index: uspace/app/taskbar/test/taskbar.c
===================================================================
--- uspace/app/taskbar/test/taskbar.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/test/taskbar.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <pcut/pcut.h>
+#include "../taskbar.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(taskbar);
+
+/* Test creating and destroying taskbar */
+PCUT_TEST(create_destroy)
+{
+	errno_t rc;
+	taskbar_t *taskbar;
+
+	rc = taskbar_create(UI_DISPLAY_NULL, NULL, &taskbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	taskbar_destroy(taskbar);
+}
+
+PCUT_EXPORT(taskbar);
Index: uspace/app/taskbar/test/tbsmenu.c
===================================================================
--- uspace/app/taskbar/test/tbsmenu.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/test/tbsmenu.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <pcut/pcut.h>
+#include <ui/fixed.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include "../tbsmenu.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(tbsmenu);
+
+/* Test creating and destroying taskbar start menu */
+PCUT_TEST(create_destroy)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	ui_fixed_t *fixed = NULL;
+	tbsmenu_t *tbsmenu = NULL;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_fixed_create(&fixed);
+	ui_window_add(window, ui_fixed_ctl(fixed));
+
+	rc = tbsmenu_create(window, fixed, UI_DISPLAY_DEFAULT, &tbsmenu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	tbsmenu_destroy(tbsmenu);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test tbsmenu_open/close/is_open() */
+PCUT_TEST(open_close_is_open)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	ui_fixed_t *fixed = NULL;
+	tbsmenu_t *tbsmenu = NULL;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_fixed_create(&fixed);
+	ui_window_add(window, ui_fixed_ctl(fixed));
+
+	rc = tbsmenu_create(window, fixed, UI_DISPLAY_DEFAULT, &tbsmenu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(tbsmenu_is_open(tbsmenu));
+	tbsmenu_open(tbsmenu);
+	PCUT_ASSERT_TRUE(tbsmenu_is_open(tbsmenu));
+	tbsmenu_close(tbsmenu);
+	PCUT_ASSERT_FALSE(tbsmenu_is_open(tbsmenu));
+
+	tbsmenu_destroy(tbsmenu);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+PCUT_EXPORT(tbsmenu);
Index: uspace/app/taskbar/test/wndlist.c
===================================================================
--- uspace/app/taskbar/test/wndlist.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/test/wndlist.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,649 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <loc.h>
+#include <pcut/pcut.h>
+#include <str.h>
+#include <ui/fixed.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include <wndmgt_srv.h>
+#include "../wndlist.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(taskbar);
+
+static void test_wndmgt_conn(ipc_call_t *, void *);
+
+static errno_t test_get_window_list(void *, wndmgt_window_list_t **);
+static errno_t test_get_window_info(void *, sysarg_t, wndmgt_window_info_t **);
+
+static wndmgt_ops_t test_wndmgt_srv_ops = {
+	.get_window_list = test_get_window_list,
+	.get_window_info = test_get_window_info
+};
+
+static const char *test_wndmgt_server = "test-wndlist-wm";
+static const char *test_wndmgt_svc = "test/wndlist-wm";
+
+/* Test creating and destroying window list */
+PCUT_TEST(create_destroy)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	ui_fixed_t *fixed = NULL;
+	wndlist_t *wndlist;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_fixed_create(&fixed);
+	ui_window_add(window, ui_fixed_ctl(fixed));
+
+	rc = wndlist_create(window, fixed, &wndlist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	wndlist_destroy(wndlist);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/* Test setting window list rectangle */
+PCUT_TEST(set_rect)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	ui_fixed_t *fixed = NULL;
+	gfx_rect_t rect;
+	wndlist_t *wndlist;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_fixed_create(&fixed);
+	ui_window_add(window, ui_fixed_ctl(fixed));
+
+	rc = wndlist_create(window, fixed, &wndlist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 1;
+	rect.p0.y = 2;
+	rect.p1.x = 3;
+	rect.p1.y = 4;
+	wndlist_set_rect(wndlist, &rect);
+	PCUT_ASSERT_INT_EQUALS(1, wndlist->rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(2, wndlist->rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(3, wndlist->rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(4, wndlist->rect.p1.y);
+
+	wndlist_destroy(wndlist);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test opening WM service */
+PCUT_TEST(open_wm)
+{
+	errno_t rc;
+	service_id_t sid;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	ui_fixed_t *fixed = NULL;
+	wndlist_t *wndlist;
+	loc_srv_t *srv;
+
+	/* Set up a test WM service */
+
+	async_set_fallback_port_handler(test_wndmgt_conn, NULL);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_wndmgt_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_wndmgt_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Create window list and connect it to our test service */
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_fixed_create(&fixed);
+	ui_window_add(window, ui_fixed_ctl(fixed));
+
+	rc = wndlist_create(window, fixed, &wndlist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndlist_open_wm(wndlist, test_wndmgt_svc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	wndlist_destroy(wndlist);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** Test appending new entry */
+PCUT_TEST(append)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	ui_fixed_t *fixed = NULL;
+	wndlist_t *wndlist;
+	wndlist_entry_t *entry;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_fixed_create(&fixed);
+	ui_window_add(window, ui_fixed_ctl(fixed));
+
+	rc = wndlist_create(window, fixed, &wndlist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndlist_append(wndlist, 123, "Foo", true, true);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	entry = wndlist_first(wndlist);
+	PCUT_ASSERT_INT_EQUALS(123, entry->wnd_id);
+	PCUT_ASSERT_NOT_NULL(entry->button);
+	PCUT_ASSERT_TRUE(ui_pbutton_get_light(entry->button));
+
+	wndlist_destroy(wndlist);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test removing entry */
+PCUT_TEST(remove)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	ui_fixed_t *fixed = NULL;
+	wndlist_t *wndlist;
+	wndlist_entry_t *entry;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_fixed_create(&fixed);
+	ui_window_add(window, ui_fixed_ctl(fixed));
+
+	rc = wndlist_create(window, fixed, &wndlist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndlist_append(wndlist, 1, "Foo", true, true);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndlist_append(wndlist, 2, "Bar", false, true);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	entry = wndlist_first(wndlist);
+	PCUT_ASSERT_INT_EQUALS(1, entry->wnd_id);
+
+	rc = wndlist_remove(wndlist, entry, true);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	entry = wndlist_first(wndlist);
+	PCUT_ASSERT_INT_EQUALS(2, entry->wnd_id);
+
+	wndlist_destroy(wndlist);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test updating entry */
+PCUT_TEST(update)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	ui_fixed_t *fixed = NULL;
+	wndlist_t *wndlist;
+	wndlist_entry_t *entry;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_fixed_create(&fixed);
+	ui_window_add(window, ui_fixed_ctl(fixed));
+
+	rc = wndlist_create(window, fixed, &wndlist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndlist_append(wndlist, 1, "Foo", true, true);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	entry = wndlist_first(wndlist);
+	PCUT_ASSERT_INT_EQUALS(1, entry->wnd_id);
+	PCUT_ASSERT_NOT_NULL(entry->button);
+	PCUT_ASSERT_TRUE(ui_pbutton_get_light(entry->button));
+
+	rc = wndlist_update(wndlist, entry, "Bar", false);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(1, entry->wnd_id);
+	PCUT_ASSERT_NOT_NULL(entry->button);
+	PCUT_ASSERT_FALSE(ui_pbutton_get_light(entry->button));
+
+	wndlist_destroy(wndlist);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test setting entry rectangle */
+PCUT_TEST(set_entry_rect)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	ui_fixed_t *fixed = NULL;
+	wndlist_t *wndlist;
+	wndlist_entry_t *entry;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_fixed_create(&fixed);
+	ui_window_add(window, ui_fixed_ctl(fixed));
+
+	rc = wndlist_create(window, fixed, &wndlist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndlist_append(wndlist, 123, "Foo", true, true);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	entry = wndlist_first(wndlist);
+
+	/* Set entry rectangle */
+	wndlist_set_entry_rect(wndlist, entry);
+
+	wndlist_destroy(wndlist);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test finding entry by window ID */
+PCUT_TEST(entry_by_id)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	ui_fixed_t *fixed = NULL;
+	wndlist_t *wndlist;
+	wndlist_entry_t *entry;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_fixed_create(&fixed);
+	ui_window_add(window, ui_fixed_ctl(fixed));
+
+	rc = wndlist_create(window, fixed, &wndlist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndlist_append(wndlist, 1, "Foo", true, true);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndlist_append(wndlist, 2, "Bar", false, true);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	entry = wndlist_entry_by_id(wndlist, 1);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_INT_EQUALS(1, entry->wnd_id);
+
+	entry = wndlist_entry_by_id(wndlist, 2);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_INT_EQUALS(2, entry->wnd_id);
+
+	wndlist_destroy(wndlist);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test wndlist_first() / wndlist_next() */
+PCUT_TEST(first_next)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	ui_fixed_t *fixed = NULL;
+	wndlist_t *wndlist;
+	wndlist_entry_t *entry;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_fixed_create(&fixed);
+	ui_window_add(window, ui_fixed_ctl(fixed));
+
+	rc = wndlist_create(window, fixed, &wndlist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndlist_append(wndlist, 1, "Foo", true, true);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndlist_append(wndlist, 2, "Bar", false, true);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	entry = wndlist_first(wndlist);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_INT_EQUALS(1, entry->wnd_id);
+
+	entry = wndlist_next(entry);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_INT_EQUALS(2, entry->wnd_id);
+
+	entry = wndlist_next(entry);
+	PCUT_ASSERT_NULL(entry);
+
+	wndlist_destroy(wndlist);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test wndlist_last() */
+PCUT_TEST(last)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	ui_fixed_t *fixed = NULL;
+	wndlist_t *wndlist;
+	wndlist_entry_t *entry;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_fixed_create(&fixed);
+	ui_window_add(window, ui_fixed_ctl(fixed));
+
+	rc = wndlist_create(window, fixed, &wndlist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndlist_append(wndlist, 1, "Foo", true, true);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndlist_append(wndlist, 2, "Bar", false, true);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	entry = wndlist_last(wndlist);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_INT_EQUALS(2, entry->wnd_id);
+
+	wndlist_destroy(wndlist);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test wndlist_count() */
+PCUT_TEST(count)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	ui_fixed_t *fixed = NULL;
+	wndlist_t *wndlist;
+	size_t count;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_fixed_create(&fixed);
+	ui_window_add(window, ui_fixed_ctl(fixed));
+
+	rc = wndlist_create(window, fixed, &wndlist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	count = wndlist_count(wndlist);
+	PCUT_ASSERT_INT_EQUALS(0, count);
+
+	rc = wndlist_append(wndlist, 1, "Foo", true, true);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	count = wndlist_count(wndlist);
+	PCUT_ASSERT_INT_EQUALS(1, count);
+
+	rc = wndlist_append(wndlist, 2, "Bar", false, true);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	count = wndlist_count(wndlist);
+	PCUT_ASSERT_INT_EQUALS(2, count);
+
+	wndlist_destroy(wndlist);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test repainting window list */
+PCUT_TEST(repaint)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	ui_fixed_t *fixed = NULL;
+	wndlist_t *wndlist;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_fixed_create(&fixed);
+	ui_window_add(window, ui_fixed_ctl(fixed));
+
+	rc = wndlist_create(window, fixed, &wndlist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndlist_append(wndlist, 1, "Foo", true, true);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndlist_append(wndlist, 2, "Bar", false, true);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndlist_repaint(wndlist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	wndlist_destroy(wndlist);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test window management service connection. */
+static void test_wndmgt_conn(ipc_call_t *icall, void *arg)
+{
+	wndmgt_srv_t srv;
+
+	/* Set up protocol structure */
+	wndmgt_srv_initialize(&srv);
+	srv.ops = &test_wndmgt_srv_ops;
+	srv.arg = arg;
+
+	/* Handle connection */
+	wndmgt_conn(icall, &srv);
+}
+
+static errno_t test_get_window_list(void *arg, wndmgt_window_list_t **rlist)
+{
+	wndmgt_window_list_t *wlist;
+
+	(void)arg;
+
+	wlist = calloc(1, sizeof(wndmgt_window_list_t));
+	if (wlist == NULL)
+		return ENOMEM;
+
+	wlist->nwindows = 1;
+	wlist->windows = calloc(1, sizeof(sysarg_t));
+	if (wlist->windows == NULL) {
+		free(wlist);
+		return ENOMEM;
+	}
+
+	wlist->windows[0] = 42;
+	*rlist = wlist;
+	return EOK;
+}
+
+static errno_t test_get_window_info(void *arg, sysarg_t wnd_id,
+    wndmgt_window_info_t **rinfo)
+{
+	wndmgt_window_info_t *winfo;
+
+	(void)arg;
+
+	winfo = calloc(1, sizeof(wndmgt_window_info_t));
+	if (winfo == NULL)
+		return ENOMEM;
+
+	winfo->caption = str_dup("Hello");
+	if (winfo->caption == NULL) {
+		free(winfo);
+		return ENOMEM;
+	}
+
+	*rinfo = winfo;
+	return EOK;
+}
+
+PCUT_EXPORT(wndlist);
Index: uspace/app/taskbar/types/clock.h
===================================================================
--- uspace/app/taskbar/types/clock.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/types/clock.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup taskbar
+ * @{
+ */
+/**
+ * @file Taskbar clock
+ */
+
+#ifndef TYPES_CLOCK_H
+#define TYPES_CLOCK_H
+
+#include <fibril_synch.h>
+#include <gfx/coord.h>
+#include <ui/window.h>
+
+/** Taskbar clock
+ *
+ * This is a custom UI control.
+ */
+typedef struct taskbar_clock {
+	/** Base control object */
+	struct ui_control *control;
+
+	/** Containing window */
+	ui_window_t *window;
+
+	/** Clock rectangle */
+	gfx_rect_t rect;
+
+	/** Clock lock */
+	fibril_mutex_t lock;
+
+	/** Clock update timer */
+	fibril_timer_t *timer;
+
+	/** Signal to timer that we are cleaning up */
+	bool timer_cleanup;
+
+	/** Signal to maih thread that timer is done */
+	bool timer_done;
+
+	/** Timer done condition variable */
+	fibril_condvar_t timer_done_cv;
+} taskbar_clock_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/taskbar/types/taskbar.h
===================================================================
--- uspace/app/taskbar/types/taskbar.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/types/taskbar.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,69 @@
+/*
+ * 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 taskbar
+ * @{
+ */
+/**
+ * @file Taskbar types
+ */
+
+#ifndef TYPES_TASKBAR_H
+#define TYPES_TASKBAR_H
+
+#include <types/common.h>
+#include <ui/fixed.h>
+#include <ui/label.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include "clock.h"
+#include "tbsmenu.h"
+#include "wndlist.h"
+
+/** Taskbar */
+typedef struct taskbar {
+	/** User interface */
+	ui_t *ui;
+	/** Taskbar window */
+	ui_window_t *window;
+	/** Fixed layout */
+	ui_fixed_t *fixed;
+	/** Start menu */
+	tbsmenu_t *tbsmenu;
+	/** Window list */
+	wndlist_t *wndlist;
+	/** Clock */
+	taskbar_clock_t *clock;
+	/** Configuration change listener */
+	tbarcfg_listener_t *lst;
+} taskbar_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/taskbar/types/tbsmenu.h
===================================================================
--- uspace/app/taskbar/types/tbsmenu.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/types/tbsmenu.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,107 @@
+/*
+ * 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 taskbar
+ * @{
+ */
+/**
+ * @file Taskbar start menu
+ */
+
+#ifndef TYPES_TBSMENU_H
+#define TYPES_TBSMENU_H
+
+#include <adt/list.h>
+#include <gfx/coord.h>
+#include <stdbool.h>
+#include <tbarcfg/tbarcfg.h>
+#include <ui/pbutton.h>
+#include <ui/fixed.h>
+#include <ui/menu.h>
+#include <ui/menuentry.h>
+#include <ui/window.h>
+
+/** Taskbar start menu entry */
+typedef struct {
+	/** Containing start menu */
+	struct tbsmenu *tbsmenu;
+	/** Link to tbsmenu->entries */
+	link_t lentries;
+	/** Menu entry */
+	ui_menu_entry_t *mentry;
+	/** Caption */
+	char *caption;
+	/** Command to run */
+	char *cmd;
+	/** Start in terminal */
+	bool terminal;
+} tbsmenu_entry_t;
+
+/** Taskbar start menu */
+typedef struct tbsmenu {
+	/** Containing window */
+	ui_window_t *window;
+
+	/** Layout to which we add start button */
+	ui_fixed_t *fixed;
+
+	/** Start button rectangle */
+	gfx_rect_t rect;
+
+	/** Start button */
+	ui_pbutton_t *sbutton;
+
+	/** Start menu */
+	ui_menu_t *smenu;
+
+	/** Start menu entries (of tbsmenu_entry_t) */
+	list_t entries;
+
+	/** 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 {
+	/** NULL-terminated array of string pointers */
+	char **argv;
+} tbsmenu_cmd_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/taskbar/types/wndlist.h
===================================================================
--- uspace/app/taskbar/types/wndlist.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/types/wndlist.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,93 @@
+/*
+ * 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 taskbar
+ * @{
+ */
+/**
+ * @file Taskbar window list
+ */
+
+#ifndef TYPES_WNDLIST_H
+#define TYPES_WNDLIST_H
+
+#include <adt/list.h>
+#include <gfx/coord.h>
+#include <stdbool.h>
+#include <ui/pbutton.h>
+#include <ui/fixed.h>
+#include <ui/window.h>
+#include <wndmgt.h>
+
+/** Taskbar window list entry */
+typedef struct {
+	/** Containing window list */
+	struct wndlist *wndlist;
+	/** Link to wndlist->entries */
+	link_t lentries;
+	/** Window ID */
+	sysarg_t wnd_id;
+	/** Entry is visible */
+	bool visible;
+	/** Window button */
+	ui_pbutton_t *button;
+	/** Window button rectangle */
+	gfx_rect_t rect;
+} wndlist_entry_t;
+
+/** Taskbar window list */
+typedef struct wndlist {
+	/** Base control object */
+	struct ui_control *control;
+
+	/** Containing window */
+	ui_window_t *window;
+
+	/** Layout to which we add window buttons */
+	ui_fixed_t *fixed;
+
+	/** Window list rectangle */
+	gfx_rect_t rect;
+
+	/** Window list entries (of wndlist_entry_t) */
+	list_t entries;
+
+	/** Current button pitch */
+	gfx_coord_t pitch;
+
+	/** Window management service */
+	wndmgt_t *wndmgt;
+
+	/** Device ID of last input event */
+	sysarg_t ev_idev_id;
+} wndlist_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/taskbar/wndlist.c
===================================================================
--- uspace/app/taskbar/wndlist.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/wndlist.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,729 @@
+/*
+ * 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 taskbar
+ * @{
+ */
+/** @file Taskbar window list
+ */
+
+#include <gfx/coord.h>
+#include <gfx/render.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <str.h>
+#include <ui/fixed.h>
+#include <ui/label.h>
+#include <ui/resource.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include "wndlist.h"
+
+static void wndlist_wm_window_added(void *, sysarg_t);
+static void wndlist_wm_window_removed(void *, sysarg_t);
+static void wndlist_wm_window_changed(void *, sysarg_t);
+
+/** Window list WM callbacks */
+static wndmgt_cb_t wndlist_wndmgt_cb = {
+	.window_added = wndlist_wm_window_added,
+	.window_removed = wndlist_wm_window_removed,
+	.window_changed = wndlist_wm_window_changed
+};
+
+static void wndlist_button_clicked(ui_pbutton_t *, void *);
+
+/** Window list button callbacks */
+static ui_pbutton_cb_t wndlist_button_cb = {
+	.clicked = wndlist_button_clicked
+};
+
+enum {
+	/** Min. X distance between left edges of two consecutive buttons */
+	wndlist_button_pitch_min = 85,
+	/** Max. X distance between left edges of two consecutive buttons (text) */
+	wndlist_button_pitch_min_text = 10,
+	/** Min. X distance between left edges of two consecutive buttons */
+	wndlist_button_pitch_max = 165,
+	/** Max. X distance between left edges of two consecutive buttons (text) */
+	wndlist_button_pitch_max_text = 17,
+	/** Padding between buttons */
+	wndlist_button_pad = 5,
+	/** Padding between buttons (text) */
+	wndlist_button_pad_text = 1
+};
+
+/** Create taskbar window list.
+ *
+ * @param window Containing window
+ * @param fixed Fixed layout to which buttons will be added
+ * @param rwndlist Place to store pointer to new window list
+ * @return @c EOK on success or an error code
+ */
+errno_t wndlist_create(ui_window_t *window, ui_fixed_t *fixed,
+    wndlist_t **rwndlist)
+{
+	ui_resource_t *res = ui_window_get_res(window);
+	wndlist_t *wndlist = NULL;
+	errno_t rc;
+
+	wndlist = calloc(1, sizeof(wndlist_t));
+	if (wndlist == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	wndlist->window = window;
+	wndlist->fixed = fixed;
+	list_initialize(&wndlist->entries);
+
+	if (ui_resource_is_textmode(res))
+		wndlist->pitch = wndlist_button_pitch_max_text;
+	else
+		wndlist->pitch = wndlist_button_pitch_max;
+
+	*rwndlist = wndlist;
+	return EOK;
+error:
+	return rc;
+}
+
+/** Set window list rectangle.
+ *
+ * @param wndlist Window list
+ * @param rect Rectangle
+ */
+void wndlist_set_rect(wndlist_t *wndlist, gfx_rect_t *rect)
+{
+	wndlist->rect = *rect;
+}
+
+/** Attach window management service to window list.
+ *
+ * @param wndlist Window list
+ * @param wndmgt_svc Window management service name
+ * @return @c EOK on success or an error code
+ */
+errno_t wndlist_open_wm(wndlist_t *wndlist, const char *wndmgt_svc)
+{
+	errno_t rc;
+	wndmgt_window_list_t *wlist = NULL;
+	wndmgt_window_info_t *winfo = NULL;
+	sysarg_t i;
+
+	rc = wndmgt_open(wndmgt_svc, &wndlist_wndmgt_cb, (void *)wndlist,
+	    &wndlist->wndmgt);
+	if (rc != EOK)
+		goto error;
+
+	rc = wndmgt_get_window_list(wndlist->wndmgt, &wlist);
+	if (rc != EOK)
+		goto error;
+
+	for (i = 0; i < wlist->nwindows; i++) {
+		rc = wndmgt_get_window_info(wndlist->wndmgt, wlist->windows[i],
+		    &winfo);
+		if (rc != EOK)
+			goto error;
+
+		if ((winfo->flags & (wndf_popup | wndf_system)) == 0) {
+			rc = wndlist_append(wndlist, wlist->windows[i],
+			    winfo->caption, winfo->nfocus != 0, false);
+			if (rc != EOK) {
+				wndmgt_free_window_info(winfo);
+				goto error;
+			}
+		}
+
+		wndmgt_free_window_info(winfo);
+	}
+
+	return EOK;
+error:
+	if (wlist != NULL)
+		wndmgt_free_window_list(wlist);
+	if (wndlist->wndmgt != NULL) {
+		wndmgt_close(wndlist->wndmgt);
+		wndlist->wndmgt = NULL;
+	}
+	return rc;
+}
+
+/** Destroy taskbar window list.
+ *
+ * @param wndlist Window list
+ */
+void wndlist_destroy(wndlist_t *wndlist)
+{
+	wndlist_entry_t *entry;
+
+	/* Close window management service */
+	if (wndlist->wndmgt)
+		wndmgt_close(wndlist->wndmgt);
+
+	/* Destroy entries */
+	entry = wndlist_first(wndlist);
+	while (entry != NULL) {
+		(void)wndlist_remove(wndlist, entry, false);
+		entry = wndlist_first(wndlist);
+	}
+
+	free(wndlist);
+}
+
+/** Append new entry to window list.
+ *
+ * @param wndlist Window list
+ * @param wnd_id Window ID
+ * @param caption Entry caption
+ * @param active @c true iff window is active
+ * @param paint @c true to paint immediately
+ * @return @c EOK on success or an error code
+ */
+errno_t wndlist_append(wndlist_t *wndlist, sysarg_t wnd_id,
+    const char *caption, bool active, bool paint)
+{
+	wndlist_entry_t *entry = NULL;
+	ui_resource_t *res;
+	wndlist_entry_t *e;
+	errno_t rc;
+
+	entry = calloc(1, sizeof(wndlist_entry_t));
+	if (entry == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	entry->wnd_id = wnd_id;
+	res = ui_window_get_res(wndlist->window);
+
+	rc = ui_pbutton_create(res, caption, &entry->button);
+	if (rc != EOK)
+		goto error;
+
+	entry->wndlist = wndlist;
+	list_append(&entry->lentries, &wndlist->entries);
+
+	entry->visible = false;
+
+	ui_pbutton_set_light(entry->button, active);
+
+	/* Set button callbacks */
+	ui_pbutton_set_cb(entry->button, &wndlist_button_cb, (void *)entry);
+
+	if (wndlist_update_pitch(wndlist)) {
+		/*
+		 * Update rectangles for all entries, including @a entry, adding
+		 * it to the layout, if applicable.
+		 */
+		e = wndlist_first(wndlist);
+		while (e != NULL) {
+			wndlist_set_entry_rect(wndlist, e);
+			e = wndlist_next(e);
+		}
+
+		if (paint)
+			return wndlist_repaint(wndlist);
+	} else {
+		wndlist_set_entry_rect(wndlist, entry);
+		if (paint)
+			return wndlist_paint_entry(entry);
+	}
+
+	return EOK;
+error:
+	if (entry != NULL && entry->button != NULL)
+		ui_pbutton_destroy(entry->button);
+	if (entry != NULL)
+		free(entry);
+	return rc;
+
+}
+
+/** Remove entry from window list.
+ *
+ * @param wndlist Window list
+ * @param entry Window list entry
+ * @param paint @c true to repaint window list
+ * @return @c EOK on success or an error code
+ */
+errno_t wndlist_remove(wndlist_t *wndlist, wndlist_entry_t *entry,
+    bool paint)
+{
+	wndlist_entry_t *e;
+	wndlist_entry_t *next;
+	wndlist_entry_t *last;
+	errno_t rc = EOK;
+
+	assert(entry->wndlist == wndlist);
+	next = wndlist_next(entry);
+
+	/* Remember last entry */
+	last = wndlist_last(wndlist);
+
+	if (entry->visible)
+		ui_fixed_remove(wndlist->fixed, ui_pbutton_ctl(entry->button));
+
+	list_remove(&entry->lentries);
+
+	if (wndlist_update_pitch(wndlist)) {
+		/*
+		 * Update rectangles for all entries.
+		 */
+		e = wndlist_first(wndlist);
+		while (e != NULL) {
+			wndlist_set_entry_rect(wndlist, e);
+			e = wndlist_next(e);
+		}
+
+		if (paint)
+			rc = wndlist_repaint(wndlist);
+	} else {
+		/* Unpaint the last entry */
+		if (paint)
+			rc = wndlist_unpaint_entry(last);
+
+		/*
+		 * Update rectangles for entries to the right
+		 */
+
+		e = NULL;
+		while (next != NULL) {
+			e = next;
+
+			wndlist_set_entry_rect(wndlist, e);
+			if (paint) {
+				rc = wndlist_paint_entry(e);
+				if (rc != EOK)
+					return rc;
+			}
+
+			next = wndlist_next(e);
+		}
+	}
+
+	ui_pbutton_destroy(entry->button);
+	free(entry);
+	return rc;
+}
+
+/** Update button pitch.
+ *
+ * Recalculatebutton pitch @c wndlist->pitch based on current number
+ * of buttons.
+ *
+ * @param wndlist Window list
+ * @return @c true iff pitch changed
+ */
+bool wndlist_update_pitch(wndlist_t *wndlist)
+{
+	ui_resource_t *res;
+	size_t nbuttons;
+	gfx_coord_t pitch;
+	gfx_coord_t pitch_max;
+	gfx_coord_t pitch_min;
+	gfx_coord_t pad;
+
+	res = ui_window_get_res(wndlist->window);
+
+	if (ui_resource_is_textmode(res)) {
+		pitch_max = wndlist_button_pitch_max_text;
+		pitch_min = wndlist_button_pitch_min_text;
+		pad = wndlist_button_pad_text;
+	} else {
+		pitch_max = wndlist_button_pitch_max;
+		pitch_min = wndlist_button_pitch_min;
+		pad = wndlist_button_pad;
+	}
+
+	/* Compute pitch that fits all buttons perfectly */
+	nbuttons = wndlist_count(wndlist);
+	if (nbuttons > 0)
+		pitch = (wndlist->rect.p1.x - wndlist->rect.p0.x + pad) / nbuttons;
+	else
+		pitch = pitch_min;
+
+	if (pitch < pitch_min)
+		pitch = pitch_min;
+	if (pitch > pitch_max)
+		pitch = pitch_max;
+
+	/* Did the pitch change? */
+	if (pitch == wndlist->pitch)
+		return false;
+
+	wndlist->pitch = pitch;
+	return true;
+}
+
+/** Update window list entry.
+ *
+ * @param wndlist Window list
+ * @param entry Window list entry
+ * @param active @c true iff the window is active
+ * @return @c EOK on success or an error code
+ */
+errno_t wndlist_update(wndlist_t *wndlist, wndlist_entry_t *entry,
+    const char *caption, bool active)
+{
+	errno_t rc;
+	assert(entry->wndlist == wndlist);
+
+	rc = ui_pbutton_set_caption(entry->button, caption);
+	if (rc != EOK)
+		return rc;
+
+	ui_pbutton_set_light(entry->button, active);
+
+	rc = wndlist_paint_entry(entry);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Compute and set window list entry rectangle.
+ *
+ * Compute rectangle for window list entry and set it.
+ *
+ * @param wndlist Window list
+ * @param entry Window list entry
+ */
+void wndlist_set_entry_rect(wndlist_t *wndlist, wndlist_entry_t *entry)
+{
+	wndlist_entry_t *e;
+	gfx_rect_t rect;
+	ui_resource_t *res;
+	gfx_coord_t pitch;
+	gfx_coord_t pad;
+	size_t idx;
+
+	/* Determine entry index */
+	idx = 0;
+	e = wndlist_first(wndlist);
+	while (e != entry) {
+		assert(e != NULL);
+		e = wndlist_next(e);
+		++idx;
+	}
+
+	res = ui_window_get_res(wndlist->window);
+
+	if (ui_resource_is_textmode(res)) {
+		pad = wndlist_button_pad_text;
+	} else {
+		pad = wndlist_button_pad;
+	}
+
+	pitch = wndlist->pitch;
+
+	rect.p0.x = wndlist->rect.p0.x + pitch * idx;
+	rect.p0.y = wndlist->rect.p0.y;
+	rect.p1.x = wndlist->rect.p0.x + pitch * (idx + 1) - pad;
+	rect.p1.y = wndlist->rect.p1.y;
+
+	/* Entry does not fit? */
+	if (rect.p1.x > wndlist->rect.p1.x) {
+		/* Make entry invisible */
+		if (entry->visible) {
+			ui_fixed_remove(wndlist->fixed,
+			    ui_pbutton_ctl(entry->button));
+			entry->visible = false;
+		}
+	} else {
+		/* Make entry visible */
+		if (!entry->visible) {
+			ui_fixed_add(wndlist->fixed,
+			    ui_pbutton_ctl(entry->button));
+			entry->visible = true;
+		}
+	}
+
+	ui_pbutton_set_rect(entry->button, &rect);
+	entry->rect = rect;
+}
+
+/** Paint window list entry.
+ *
+ * @param entry Window list entry
+ * @return EOK on success or an error code
+ */
+errno_t wndlist_paint_entry(wndlist_entry_t *entry)
+{
+	ui_t *ui;
+
+	ui = ui_window_get_ui(entry->wndlist->window);
+	if (ui_is_suspended(ui))
+		return EOK;
+
+	return ui_pbutton_paint(entry->button);
+}
+
+/** Unpaint window list entry.
+ *
+ * @param entry Window list entry
+ * @return EOK on success or an error code
+ */
+errno_t wndlist_unpaint_entry(wndlist_entry_t *entry)
+{
+	errno_t rc;
+	ui_t *ui;
+	gfx_context_t *gc;
+	ui_resource_t *res;
+	gfx_color_t *color;
+
+	ui = ui_window_get_ui(entry->wndlist->window);
+	gc = ui_window_get_gc(entry->wndlist->window);
+	res = ui_window_get_res(entry->wndlist->window);
+	color = ui_resource_get_wnd_face_color(res);
+
+	if (ui_is_suspended(ui))
+		return EOK;
+
+	rc = gfx_set_color(gc, color);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_fill_rect(gc, &entry->rect);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_update(gc);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Handle WM window added event.
+ *
+ * @param arg Argument (wndlist_t *)
+ * @param wnd_id Window ID
+ */
+static void wndlist_wm_window_added(void *arg, sysarg_t wnd_id)
+{
+	wndlist_t *wndlist = (wndlist_t *)arg;
+	wndmgt_window_info_t *winfo = NULL;
+	ui_t *ui;
+	errno_t rc;
+
+	ui = ui_window_get_ui(wndlist->window);
+	ui_lock(ui);
+
+	rc = wndmgt_get_window_info(wndlist->wndmgt, wnd_id, &winfo);
+	if (rc != EOK)
+		goto error;
+
+	if ((winfo->flags & (wndf_popup | wndf_system)) == 0) {
+		rc = wndlist_append(wndlist, wnd_id, winfo->caption,
+		    winfo->nfocus != 0, true);
+		if (rc != EOK) {
+			wndmgt_free_window_info(winfo);
+			goto error;
+		}
+	}
+
+	wndmgt_free_window_info(winfo);
+	ui_unlock(ui);
+	return;
+error:
+	if (winfo != NULL)
+		wndmgt_free_window_info(winfo);
+	ui_unlock(ui);
+}
+
+/** Handle WM window removed event.
+ *
+ * @param arg Argument (wndlist_t *)
+ * @param wnd_id Window ID
+ */
+static void wndlist_wm_window_removed(void *arg, sysarg_t wnd_id)
+{
+	wndlist_t *wndlist = (wndlist_t *)arg;
+	wndlist_entry_t *entry;
+	ui_t *ui;
+
+	ui = ui_window_get_ui(wndlist->window);
+	ui_lock(ui);
+
+	entry = wndlist_entry_by_id(wndlist, wnd_id);
+	if (entry == NULL) {
+		ui_unlock(ui);
+		return;
+	}
+
+	(void) wndlist_remove(wndlist, entry, true);
+	ui_unlock(ui);
+}
+
+/** Handle WM window changed event.
+ *
+ * @param arg Argument (wndlist_t *)
+ * @param wnd_id Window ID
+ */
+static void wndlist_wm_window_changed(void *arg, sysarg_t wnd_id)
+{
+	wndlist_t *wndlist = (wndlist_t *)arg;
+	wndmgt_window_info_t *winfo = NULL;
+	wndlist_entry_t *entry;
+	ui_t *ui;
+	errno_t rc;
+
+	ui = ui_window_get_ui(wndlist->window);
+	ui_lock(ui);
+
+	entry = wndlist_entry_by_id(wndlist, wnd_id);
+	if (entry == NULL) {
+		ui_unlock(ui);
+		return;
+	}
+
+	rc = wndmgt_get_window_info(wndlist->wndmgt, wnd_id, &winfo);
+	if (rc != EOK) {
+		ui_unlock(ui);
+		return;
+	}
+
+	(void) wndlist_update(wndlist, entry, winfo->caption,
+	    winfo->nfocus != 0);
+	wndmgt_free_window_info(winfo);
+	ui_unlock(ui);
+}
+
+/** Find window list entry by ID.
+ *
+ * @param wndlist Window list
+ * @param wnd_id Window ID
+ * @return Window list entry on success or @c NULL if not found
+ */
+wndlist_entry_t *wndlist_entry_by_id(wndlist_t *wndlist, sysarg_t wnd_id)
+{
+	wndlist_entry_t *entry;
+
+	entry = wndlist_first(wndlist);
+	while (entry != NULL) {
+		if (entry->wnd_id == wnd_id)
+			return entry;
+
+		entry = wndlist_next(entry);
+	}
+
+	return NULL;
+}
+
+/** Get first window list entry.
+ *
+ * @param wndlist Window list
+ * @return First entry or @c NULL if the list is empty
+ */
+wndlist_entry_t *wndlist_first(wndlist_t *wndlist)
+{
+	link_t *link;
+
+	link = list_first(&wndlist->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, wndlist_entry_t, lentries);
+}
+
+/** Get last window list entry.
+ *
+ * @param wndlist Window list
+ * @return Last entry or @c NULL if the list is empty
+ */
+wndlist_entry_t *wndlist_last(wndlist_t *wndlist)
+{
+	link_t *link;
+
+	link = list_last(&wndlist->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, wndlist_entry_t, lentries);
+}
+
+/** Get next window list entry.
+ *
+ * @param cur Current entry
+ * @return Next entry or @c NULL if @a cur is the last entry
+ */
+wndlist_entry_t *wndlist_next(wndlist_entry_t *cur)
+{
+	link_t *link;
+
+	link = list_next(&cur->lentries, &cur->wndlist->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, wndlist_entry_t, lentries);
+}
+
+/** Get number of window list entries.
+ *
+ * @param wndlist Window list
+ * @return Number of entries
+ */
+size_t wndlist_count(wndlist_t *wndlist)
+{
+	return list_count(&wndlist->entries);
+}
+
+/** Repaint window list.
+ *
+ * @param wndlist Window list
+ * @return @c EOK on success or an error code
+ */
+errno_t wndlist_repaint(wndlist_t *wndlist)
+{
+	if (ui_is_suspended(ui_window_get_ui(wndlist->window)))
+		return EOK;
+
+	return ui_window_paint(wndlist->window);
+}
+
+/** Window button was clicked.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (wndlist_entry_t *)
+ */
+static void wndlist_button_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	wndlist_entry_t *entry = (wndlist_entry_t *)arg;
+	sysarg_t dev_id;
+
+	/* ID of device that clicked the button */
+	dev_id = entry->wndlist->ev_idev_id;
+
+	(void) wndmgt_activate_window(entry->wndlist->wndmgt,
+	    dev_id, entry->wnd_id);
+}
+
+/** @}
+ */
Index: uspace/app/taskbar/wndlist.h
===================================================================
--- uspace/app/taskbar/wndlist.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/taskbar/wndlist.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,71 @@
+/*
+ * 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 taskbar
+ * @{
+ */
+/**
+ * @file Taskbar window list
+ */
+
+#ifndef WNDLIST_H
+#define WNDLIST_H
+
+#include <errno.h>
+#include <gfx/coord.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <ui/fixed.h>
+#include <ui/window.h>
+#include <wndmgt.h>
+#include "types/wndlist.h"
+
+extern errno_t wndlist_create(ui_window_t *, ui_fixed_t *, wndlist_t **);
+extern void wndlist_set_rect(wndlist_t *, gfx_rect_t *);
+extern errno_t wndlist_open_wm(wndlist_t *, const char *);
+extern void wndlist_destroy(wndlist_t *);
+extern errno_t wndlist_append(wndlist_t *, sysarg_t, const char *, bool,
+    bool);
+extern errno_t wndlist_remove(wndlist_t *, wndlist_entry_t *, bool);
+extern bool wndlist_update_pitch(wndlist_t *);
+extern errno_t wndlist_update(wndlist_t *, wndlist_entry_t *, const char *,
+    bool);
+extern void wndlist_set_entry_rect(wndlist_t *, wndlist_entry_t *);
+extern wndlist_entry_t *wndlist_entry_by_id(wndlist_t *, sysarg_t);
+extern wndlist_entry_t *wndlist_first(wndlist_t *);
+extern wndlist_entry_t *wndlist_last(wndlist_t *);
+extern wndlist_entry_t *wndlist_next(wndlist_entry_t *);
+extern size_t wndlist_count(wndlist_t *);
+extern errno_t wndlist_repaint(wndlist_t *);
+extern errno_t wndlist_paint_entry(wndlist_entry_t *);
+extern errno_t wndlist_unpaint_entry(wndlist_entry_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/terminal/main.c
===================================================================
--- uspace/app/terminal/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/terminal/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2021 Jiri Svoboda
  * All rights reserved.
  *
@@ -33,7 +33,6 @@
  */
 
+#include <ui/ui.h>
 #include <stdio.h>
-#include <task.h>
-#include <ui/ui.h>
 #include "terminal.h"
 
@@ -45,4 +44,5 @@
 	printf("Syntax: %s [<options>]\n", NAME);
 	printf("\t-d <display-spec> Use the specified display\n");
+	printf("\t-c <command>      Run command instead of shell\n");
 	printf("\t-topleft]         Place window to the top-left corner of "
 	    "the screen\n");
@@ -52,4 +52,5 @@
 {
 	const char *display_spec = UI_DISPLAY_DEFAULT;
+	const char *command = "/app/bdsh";
 	terminal_t *terminal = NULL;
 	terminal_flags_t flags = 0;
@@ -68,4 +69,13 @@
 
 			display_spec = argv[i++];
+		} else if (str_cmp(argv[i], "-c") == 0) {
+			++i;
+			if (i >= argc) {
+				printf("Argument missing.\n");
+				print_syntax();
+				return 1;
+			}
+
+			command = argv[i++];
 		} else if (str_cmp(argv[i], "-topleft") == 0) {
 			++i;
@@ -83,10 +93,11 @@
 	}
 
-	rc = terminal_create(display_spec, 640, 480, flags, &terminal);
+	rc = terminal_create(display_spec, 640, 480, flags, command, &terminal);
 	if (rc != EOK)
 		return 1;
 
-	task_retval(0);
-	async_manager();
+	ui_run(terminal->ui);
+
+	terminal_destroy(terminal);
 	return 0;
 }
Index: uspace/app/terminal/terminal.c
===================================================================
--- uspace/app/terminal/terminal.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/terminal/terminal.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2012 Petr Koupy
  * All rights reserved.
@@ -41,4 +41,5 @@
 #include <fbfont/font-8x16.h>
 #include <io/chargrid.h>
+#include <fibril.h>
 #include <gfx/bitmap.h>
 #include <gfx/context.h>
@@ -87,4 +88,5 @@
 static void term_set_rgb_color(con_srv_t *, pixel_t, pixel_t);
 static void term_set_cursor_visibility(con_srv_t *, bool);
+static errno_t term_set_caption(con_srv_t *, const char *);
 static errno_t term_get_event(con_srv_t *, cons_event_t *);
 static errno_t term_map(con_srv_t *, sysarg_t, sysarg_t, charfield_t **);
@@ -108,4 +110,5 @@
 	.set_rgb_color = term_set_rgb_color,
 	.set_cursor_visibility = term_set_cursor_visibility,
+	.set_caption = term_set_caption,
 	.get_event = term_get_event,
 	.map = term_map,
@@ -115,8 +118,8 @@
 
 static void terminal_close_event(ui_window_t *, void *);
-static void terminal_focus_event(ui_window_t *, void *);
+static void terminal_focus_event(ui_window_t *, void *, unsigned);
 static void terminal_kbd_event(ui_window_t *, void *, kbd_event_t *);
 static void terminal_pos_event(ui_window_t *, void *, pos_event_t *);
-static void terminal_unfocus_event(ui_window_t *, void *);
+static void terminal_unfocus_event(ui_window_t *, void *, unsigned);
 
 static ui_window_cb_t terminal_window_cb = {
@@ -128,4 +131,6 @@
 };
 
+static errno_t terminal_wait_fibril(void *);
+
 static terminal_t *srv_to_terminal(con_srv_t *srv)
 {
@@ -133,7 +138,7 @@
 }
 
-static void getterm(const char *svc, const char *app)
-{
-	task_spawnl(NULL, NULL, APP_GETTERM, APP_GETTERM, svc,
+static errno_t getterm(task_wait_t *wait, const char *svc, const char *app)
+{
+	return task_spawnl(NULL, wait, APP_GETTERM, APP_GETTERM, svc,
 	    LOCFS_MOUNT_POINT, "--msg", "--wait", "--", app, NULL);
 }
@@ -141,19 +146,19 @@
 static pixel_t color_table[16] = {
 	[COLOR_BLACK]       = PIXEL(255, 0, 0, 0),
-	[COLOR_BLUE]        = PIXEL(255, 0, 0, 240),
-	[COLOR_GREEN]       = PIXEL(255, 0, 240, 0),
-	[COLOR_CYAN]        = PIXEL(255, 0, 240, 240),
-	[COLOR_RED]         = PIXEL(255, 240, 0, 0),
-	[COLOR_MAGENTA]     = PIXEL(255, 240, 0, 240),
-	[COLOR_YELLOW]      = PIXEL(255, 240, 240, 0),
-	[COLOR_WHITE]       = PIXEL(255, 240, 240, 240),
-
-	[COLOR_BLACK + 8]   = PIXEL(255, 0, 0, 0),
-	[COLOR_BLUE + 8]    = PIXEL(255, 0, 0, 255),
-	[COLOR_GREEN + 8]   = PIXEL(255, 0, 255, 0),
-	[COLOR_CYAN + 8]    = PIXEL(255, 0, 255, 255),
-	[COLOR_RED + 8]     = PIXEL(255, 255, 0, 0),
-	[COLOR_MAGENTA + 8] = PIXEL(255, 255, 0, 255),
-	[COLOR_YELLOW + 8]  = PIXEL(255, 255, 255, 0),
+	[COLOR_BLUE]        = PIXEL(255, 0, 0, 170),
+	[COLOR_GREEN]       = PIXEL(255, 0, 170, 0),
+	[COLOR_CYAN]        = PIXEL(255, 0, 170, 170),
+	[COLOR_RED]         = PIXEL(255, 170, 0, 0),
+	[COLOR_MAGENTA]     = PIXEL(255, 170, 0, 170),
+	[COLOR_YELLOW]      = PIXEL(255, 170, 85, 0),
+	[COLOR_WHITE]       = PIXEL(255, 170, 170, 170),
+
+	[COLOR_BLACK + 8]   = PIXEL(255, 85, 85, 85),
+	[COLOR_BLUE + 8]    = PIXEL(255, 85, 85, 255),
+	[COLOR_GREEN + 8]   = PIXEL(255, 85, 255, 85),
+	[COLOR_CYAN + 8]    = PIXEL(255, 85, 255, 255),
+	[COLOR_RED + 8]     = PIXEL(255, 255, 85, 85),
+	[COLOR_MAGENTA + 8] = PIXEL(255, 255, 85, 255),
+	[COLOR_YELLOW + 8]  = PIXEL(255, 255, 255, 85),
 	[COLOR_WHITE + 8]   = PIXEL(255, 255, 255, 255),
 };
@@ -165,24 +170,23 @@
 		switch (attrs.val.style) {
 		case STYLE_NORMAL:
-			*bgcolor = color_table[COLOR_WHITE];
+			*bgcolor = color_table[COLOR_WHITE + 8];
 			*fgcolor = color_table[COLOR_BLACK];
 			break;
 		case STYLE_EMPHASIS:
-			*bgcolor = color_table[COLOR_WHITE];
-			*fgcolor = color_table[COLOR_RED];
+			*bgcolor = color_table[COLOR_WHITE + 8];
+			*fgcolor = color_table[COLOR_RED + 8];
 			break;
 		case STYLE_INVERTED:
 			*bgcolor = color_table[COLOR_BLACK];
-			*fgcolor = color_table[COLOR_WHITE];
+			*fgcolor = color_table[COLOR_WHITE + 8];
 			break;
 		case STYLE_SELECTED:
-			*bgcolor = color_table[COLOR_RED];
-			*fgcolor = color_table[COLOR_WHITE];
+			*bgcolor = color_table[COLOR_RED + 8];
+			*fgcolor = color_table[COLOR_WHITE + 8];
 			break;
 		}
 		break;
 	case CHAR_ATTR_INDEX:
-		*bgcolor = color_table[(attrs.val.index.bgcolor & 7) |
-		    ((attrs.val.index.attr & CATTR_BRIGHT) ? 8 : 0)];
+		*bgcolor = color_table[(attrs.val.index.bgcolor & 7)];
 		*fgcolor = color_table[(attrs.val.index.fgcolor & 7) |
 		    ((attrs.val.index.attr & CATTR_BRIGHT) ? 8 : 0)];
@@ -643,4 +647,24 @@
 }
 
+static errno_t term_set_caption(con_srv_t *srv, const char *caption)
+{
+	terminal_t *term = srv_to_terminal(srv);
+	const char *cap;
+
+	fibril_mutex_lock(&term->mtx);
+
+	if (str_size(caption) > 0)
+		cap = caption;
+	else
+		cap = "Terminal";
+
+	ui_window_set_caption(term->window, cap);
+	fibril_mutex_unlock(&term->mtx);
+
+	term_update(term);
+	gfx_update(term->gc);
+	return EOK;
+}
+
 static errno_t term_get_event(con_srv_t *srv, cons_event_t *event)
 {
@@ -805,15 +829,14 @@
 	terminal_t *term = (terminal_t *) arg;
 
-	(void) term;
-
-	// XXX This is not really a clean way of terminating
-	exit(0);
+	ui_quit(term->ui);
 }
 
 /** Handle window focus event. */
-static void terminal_focus_event(ui_window_t *window, void *arg)
+static void terminal_focus_event(ui_window_t *window, void *arg,
+    unsigned nfocus)
 {
 	terminal_t *term = (terminal_t *) arg;
 
+	(void)nfocus;
 	term->is_focused = true;
 	term_update(term);
@@ -843,5 +866,6 @@
 	sysarg_t sy = -term->off.y;
 
-	if (event->type == POS_PRESS || event->type == POS_RELEASE) {
+	if (event->type == POS_PRESS || event->type == POS_RELEASE ||
+	    event->type == POS_DCLICK) {
 		cevent.type = CEV_POS;
 		cevent.ev.pos.type = event->type;
@@ -856,11 +880,14 @@
 
 /** Handle window unfocus event. */
-static void terminal_unfocus_event(ui_window_t *window, void *arg)
+static void terminal_unfocus_event(ui_window_t *window, void *arg,
+    unsigned nfocus)
 {
 	terminal_t *term = (terminal_t *) arg;
 
-	term->is_focused = false;
-	term_update(term);
-	gfx_update(term->gc);
+	if (nfocus == 0) {
+		term->is_focused = false;
+		term_update(term);
+		gfx_update(term->gc);
+	}
 }
 
@@ -888,5 +915,6 @@
 
 errno_t terminal_create(const char *display_spec, sysarg_t width,
-    sysarg_t height, terminal_flags_t flags, terminal_t **rterm)
+    sysarg_t height, terminal_flags_t flags, const char *command,
+    terminal_t **rterm)
 {
 	terminal_t *term;
@@ -946,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);
@@ -994,5 +1022,5 @@
 	term->srvs.sarg = term;
 
-	rc = loc_server_register(NAME);
+	rc = loc_server_register(NAME, &term->srv);
 	if (rc != EOK) {
 		printf("Error registering server.\n");
@@ -1005,5 +1033,5 @@
 	    task_get_id());
 
-	rc = loc_service_register(vc, &term->dsid);
+	rc = loc_service_register(term->srv, vc, &term->dsid);
 	if (rc != EOK) {
 		printf("Error registering service.\n");
@@ -1013,5 +1041,13 @@
 
 	list_append(&term->link, &terms);
-	getterm(vc, "/app/bdsh");
+	rc = getterm(&term->wait, vc, command);
+	if (rc != EOK)
+		goto error;
+
+	term->wfid = fibril_create(terminal_wait_fibril, term);
+	if (term->wfid == 0)
+		goto error;
+
+	fibril_add_ready(term->wfid);
 
 	term->is_focused = true;
@@ -1027,4 +1063,8 @@
 	return EOK;
 error:
+	if (term->dsid != 0)
+		loc_service_unregister(term->srv, term->dsid);
+	if (term->srv != NULL)
+		loc_server_unregister(term->srv);
 	if (term->window != NULL)
 		ui_window_destroy(term->window);
@@ -1039,4 +1079,19 @@
 }
 
+static errno_t terminal_wait_fibril(void *arg)
+{
+	terminal_t *term = (terminal_t *)arg;
+	task_exit_t texit;
+	int retval;
+
+	/*
+	 * XXX There is no way to break the sleep if the task does not
+	 * exit.
+	 */
+	(void) task_wait(&term->wait, &texit, &retval);
+	ui_quit(term->ui);
+	return EOK;
+}
+
 /** @}
  */
Index: uspace/app/terminal/terminal.h
===================================================================
--- uspace/app/terminal/terminal.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/terminal/terminal.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2012 Petr Koupy
  * All rights reserved.
@@ -38,5 +38,7 @@
 #define TERMINAL_H
 
+#include <adt/prodcons.h>
 #include <errno.h>
+#include <fibril.h>
 #include <fibril_synch.h>
 #include <gfx/bitmap.h>
@@ -46,7 +48,7 @@
 #include <io/con_srv.h>
 #include <loc.h>
-#include <adt/prodcons.h>
 #include <stdatomic.h>
 #include <str.h>
+#include <task.h>
 #include <ui/ui.h>
 #include <ui/window.h>
@@ -89,10 +91,14 @@
 	charfield_t *ubuf;
 
+	loc_srv_t *srv;
 	service_id_t dsid;
 	con_srvs_t srvs;
+
+	task_wait_t wait;
+	fid_t wfid;
 } terminal_t;
 
 extern errno_t terminal_create(const char *, sysarg_t, sysarg_t,
-    terminal_flags_t, terminal_t **);
+    terminal_flags_t, const char *, terminal_t **);
 extern void terminal_destroy(terminal_t *);
 
Index: uspace/app/tester/console/console1.c
===================================================================
--- uspace/app/tester/console/console1.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/tester/console/console1.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -106,5 +106,5 @@
 		}
 		console_flush(console);
-		console_set_color(console, COLOR_WHITE, COLOR_BLACK, 0);
+		console_set_style(console, STYLE_NORMAL);
 		putchar('\n');
 
@@ -115,5 +115,5 @@
 		}
 		console_flush(console);
-		console_set_color(console, COLOR_WHITE, COLOR_BLACK, 0);
+		console_set_style(console, STYLE_NORMAL);
 		putchar('\n');
 
Index: uspace/app/tester/fault/fault1.c
===================================================================
--- uspace/app/tester/fault/fault1.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/tester/fault/fault1.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -30,4 +30,6 @@
 #include "../tester.h"
 
+#pragma GCC diagnostic ignored "-Warray-bounds"
+
 const char *test_fault1(void)
 {
Index: uspace/app/tester/ipc/readwrite.c
===================================================================
--- uspace/app/tester/ipc/readwrite.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/tester/ipc/readwrite.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+
+#include <as.h>
+#include <errno.h>
+#include <mem.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ipc_test.h>
+#include "../tester.h"
+
+enum {
+	rw_buf_size = 1024
+};
+
+static uint8_t rw_buf[rw_buf_size];
+
+const char *test_readwrite(void)
+{
+	ipc_test_t *test = NULL;
+	size_t i;
+	errno_t rc;
+
+	rc = ipc_test_create(&test);
+	if (rc != EOK)
+		return "Error contacting IPC test service.";
+
+	rc = ipc_test_set_rw_buf_size(test, rw_buf_size);
+	if (rc != EOK)
+		return "Error getting read-only area size.";
+
+	/*
+	 * Write all zeroes to remote buffer
+	 */
+	memset(rw_buf, 0x00, rw_buf_size);
+	rc = ipc_test_write(test, rw_buf, rw_buf_size);
+	if (rc != EOK)
+		return "Error writing remote buffer.";
+
+	TPRINTF("Successfully wrote zeroes to remote buffer.\n");
+
+	/*
+	 * Read back contents of remote buffer and verify
+	 */
+
+	/*
+	 * Make sure the contents of local buffer are different from what
+	 * we expect to read.
+	 */
+	memset(rw_buf, 0xff, rw_buf_size);
+	rc = ipc_test_read(test, rw_buf, rw_buf_size);
+	if (rc != EOK)
+		return "Error reading remote buffer.";
+
+	TPRINTF("Successfully read back remote buffer.\n");
+
+	/* Now verify what we have read */
+	for (i = 0; i < rw_buf_size; i++) {
+		if (rw_buf[i] != 0x00)
+			return "Failed verification of read data.";
+	}
+
+	TPRINTF("Read data succeeded verification.\n");
+
+	/*
+	 * Write all binary ones to remote buffer
+	 */
+	memset(rw_buf, 0xff, rw_buf_size);
+	rc = ipc_test_write(test, rw_buf, rw_buf_size);
+	if (rc != EOK)
+		return "Error writing remote buffer.";
+
+	TPRINTF("Successfully wrote binary ones to remote buffer.\n");
+
+	/*
+	 * Read back contents of remote buffer and verify
+	 */
+
+	/*
+	 * Make sure the contents of local buffer are different from what
+	 * we expect to read.
+	 */
+	memset(rw_buf, 0x00, rw_buf_size);
+	rc = ipc_test_read(test, rw_buf, rw_buf_size);
+	if (rc != EOK)
+		return "Error reading remote buffer.";
+
+	TPRINTF("Successfully read back remote buffer.\n");
+
+	/* Now verify what we have read */
+	for (i = 0; i < rw_buf_size; i++) {
+		if (rw_buf[i] != 0xff)
+			return "Failed verification of read data.";
+	}
+
+	TPRINTF("Read data succeeded verification.\n");
+
+	ipc_test_destroy(test);
+	return NULL;
+}
Index: uspace/app/tester/ipc/readwrite.def
===================================================================
--- uspace/app/tester/ipc/readwrite.def	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/app/tester/ipc/readwrite.def	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,6 @@
+{
+	"readwrite",
+	"IPC read/write test",
+	&test_readwrite,
+	true
+},
Index: uspace/app/tester/meson.build
===================================================================
--- uspace/app/tester/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/tester/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 #
-# Copyright (c) 2021 Jiri Svoboda
+# Copyright (c) 2023 Jiri Svoboda
 # Copyright (c) 2005 Martin Decky
 # Copyright (c) 2007 Jakub Jermar
@@ -29,5 +29,5 @@
 #
 
-deps = [ 'block', 'codepage', 'drv', 'math' ]
+deps = [ 'block', 'codepage', 'drv', 'math', 'ipctest', 'console' ]
 src = files(
 	'tester.c',
@@ -52,4 +52,5 @@
 	'float/float2.c',
 	'vfs/vfs1.c',
+	'ipc/readwrite.c',
 	'ipc/sharein.c',
 	'ipc/starve.c',
Index: uspace/app/tester/mm/common.c
===================================================================
--- uspace/app/tester/mm/common.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/tester/mm/common.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -35,4 +35,5 @@
 #include <stdlib.h>
 #include <errno.h>
+#include <malloc.h>
 #include "../tester.h"
 #include "common.h"
Index: uspace/app/tester/print/print5.c
===================================================================
--- uspace/app/tester/print/print5.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/tester/print/print5.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -38,4 +38,5 @@
  */
 #pragma GCC diagnostic ignored "-Wformat"
+#pragma GCC diagnostic ignored "-Wformat-overflow"
 
 #include <stdio.h>
Index: uspace/app/tester/tester.c
===================================================================
--- uspace/app/tester/tester.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/tester/tester.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -68,4 +68,5 @@
 #include "float/float2.def"
 #include "vfs/vfs1.def"
+#include "ipc/readwrite.def"
 #include "ipc/sharein.def"
 #include "ipc/starve.def"
Index: uspace/app/tester/tester.h
===================================================================
--- uspace/app/tester/tester.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/tester/tester.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -99,4 +99,5 @@
 extern const char *test_vfs1(void);
 extern const char *test_ping_pong(void);
+extern const char *test_readwrite(void);
 extern const char *test_sharein(void);
 extern const char *test_starve_ipc(void);
Index: uspace/app/tetris/meson.build
===================================================================
--- uspace/app/tetris/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/tetris/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -28,4 +28,5 @@
 #
 
+deps = [ 'console' ]
 src = files(
 	'shapes.c',
Index: uspace/app/tetris/scores.c
===================================================================
--- uspace/app/tetris/scores.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/tetris/scores.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -149,4 +149,7 @@
 			continue;
 
+		if ((ev.ev.key.mods & (KM_CTRL | KM_ALT)) != 0)
+			continue;
+
 		kev = &ev.ev.key;
 
Index: uspace/app/top/meson.build
===================================================================
--- uspace/app/top/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/top/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -28,4 +28,5 @@
 #
 
+deps = [ 'console' ]
 src = files(
 	'top.c',
Index: uspace/app/trace/ipcp.c
===================================================================
--- uspace/app/trace/ipcp.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/trace/ipcp.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -92,5 +92,5 @@
 }
 
-static hash_table_ops_t pending_call_ops = {
+static const hash_table_ops_t pending_call_ops = {
 	.hash = pending_call_hash,
 	.key_hash = pending_call_key_hash,
Index: uspace/app/trace/meson.build
===================================================================
--- uspace/app/trace/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/trace/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -28,4 +28,5 @@
 #
 
+deps = [ 'console' ]
 src = files(
 	'trace.c',
Index: uspace/app/trace/proto.c
===================================================================
--- uspace/app/trace/proto.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/trace/proto.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -76,5 +76,5 @@
 }
 
-static hash_table_ops_t srv_proto_ops = {
+static const hash_table_ops_t srv_proto_ops = {
 	.hash = srv_proto_hash,
 	.key_hash = srv_proto_key_hash,
@@ -103,5 +103,5 @@
 }
 
-static hash_table_ops_t method_oper_ops = {
+static const hash_table_ops_t method_oper_ops = {
 	.hash = method_oper_hash,
 	.key_hash = method_oper_key_hash,
Index: uspace/app/uidemo/uidemo.c
===================================================================
--- uspace/app/uidemo/uidemo.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/uidemo/uidemo.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -44,11 +44,16 @@
 #include <ui/image.h>
 #include <ui/label.h>
+#include <ui/list.h>
+#include <ui/menu.h>
 #include <ui/menubar.h>
+#include <ui/menudd.h>
 #include <ui/menuentry.h>
-#include <ui/menu.h>
 #include <ui/msgdialog.h>
 #include <ui/pbutton.h>
 #include <ui/promptdialog.h>
 #include <ui/resource.h>
+#include <ui/selectdialog.h>
+#include <ui/tab.h>
+#include <ui/tabset.h>
 #include <ui/ui.h>
 #include <ui/window.h>
@@ -85,4 +90,18 @@
 static ui_slider_cb_t slider_cb = {
 	.moved = slider_moved
+};
+
+static void scrollbar_up(ui_scrollbar_t *, void *);
+static void scrollbar_down(ui_scrollbar_t *, void *);
+static void scrollbar_page_up(ui_scrollbar_t *, void *);
+static void scrollbar_page_down(ui_scrollbar_t *, void *);
+static void scrollbar_moved(ui_scrollbar_t *, void *, gfx_coord_t);
+
+static ui_scrollbar_cb_t scrollbar_cb = {
+	.up = scrollbar_up,
+	.down = scrollbar_down,
+	.page_up = scrollbar_page_up,
+	.page_down = scrollbar_page_down,
+	.moved = scrollbar_moved
 };
 
@@ -91,4 +110,5 @@
 static void uidemo_file_exit(ui_menu_entry_t *, void *);
 static void uidemo_edit_modify(ui_menu_entry_t *, void *);
+static void uidemo_edit_insert_character(ui_menu_entry_t *, void *);
 
 static void file_dialog_bok(ui_file_dialog_t *, void *, const char *);
@@ -110,4 +130,14 @@
 	.bcancel = prompt_dialog_bcancel,
 	.close = prompt_dialog_close
+};
+
+static void select_dialog_bok(ui_select_dialog_t *, void *, void *);
+static void select_dialog_bcancel(ui_select_dialog_t *, void *);
+static void select_dialog_close(ui_select_dialog_t *, void *);
+
+static ui_select_dialog_cb_t select_dialog_cb = {
+	.bok = select_dialog_bok,
+	.bcancel = select_dialog_bcancel,
+	.close = select_dialog_close
 };
 
@@ -215,4 +245,99 @@
 }
 
+/** Scrollbar up button pressed.
+ *
+ * @param scrollbar Scrollbar
+ * @param arg Argument (demo)
+ */
+static void scrollbar_up(ui_scrollbar_t *scrollbar, void *arg)
+{
+	gfx_coord_t pos;
+
+	pos = ui_scrollbar_get_pos(scrollbar);
+	ui_scrollbar_set_pos(scrollbar, pos - 1);
+
+	pos = ui_scrollbar_get_pos(scrollbar);
+	scrollbar_moved(scrollbar, arg, pos);
+}
+
+/** Scrollbar down button pressed.
+ *
+ * @param scrollbar Scrollbar
+ * @param arg Argument (demo)
+ */
+static void scrollbar_down(ui_scrollbar_t *scrollbar, void *arg)
+{
+	gfx_coord_t pos;
+
+	pos = ui_scrollbar_get_pos(scrollbar);
+	ui_scrollbar_set_pos(scrollbar, pos + 1);
+
+	pos = ui_scrollbar_get_pos(scrollbar);
+	scrollbar_moved(scrollbar, arg, pos);
+}
+
+/** Scrollbar page up event.
+ *
+ * @param scrollbar Scrollbar
+ * @param arg Argument (demo)
+ */
+static void scrollbar_page_up(ui_scrollbar_t *scrollbar, void *arg)
+{
+	gfx_coord_t pos;
+
+	pos = ui_scrollbar_get_pos(scrollbar);
+	ui_scrollbar_set_pos(scrollbar, pos -
+	    ui_scrollbar_trough_length(scrollbar) / 4);
+
+	pos = ui_scrollbar_get_pos(scrollbar);
+	scrollbar_moved(scrollbar, arg, pos);
+}
+
+/** Scrollbar page down event.
+ *
+ * @param scrollbar Scrollbar
+ * @param arg Argument (demo)
+ */
+static void scrollbar_page_down(ui_scrollbar_t *scrollbar, void *arg)
+{
+	gfx_coord_t pos;
+
+	pos = ui_scrollbar_get_pos(scrollbar);
+	ui_scrollbar_set_pos(scrollbar, pos +
+	    ui_scrollbar_trough_length(scrollbar) / 4);
+
+	pos = ui_scrollbar_get_pos(scrollbar);
+	scrollbar_moved(scrollbar, arg, pos);
+}
+
+/** Scrollbar was moved.
+ *
+ * @param scrollbar Scrollbar
+ * @param arg Argument (demo)
+ * @param pos Position
+ */
+static void scrollbar_moved(ui_scrollbar_t *scrollbar, void *arg,
+    gfx_coord_t pos)
+{
+	ui_demo_t *demo = (ui_demo_t *) arg;
+	char *str;
+	errno_t rc;
+	int rv;
+
+	rv = asprintf(&str, "Scrollbar: %d of %d", (int) pos,
+	    ui_scrollbar_move_length(scrollbar));
+	if (rv < 0) {
+		printf("Out of memory.\n");
+		return;
+	}
+
+	rc = ui_entry_set_text(demo->entry, str);
+	if (rc != EOK)
+		printf("Error changing entry text.\n");
+	(void) ui_entry_paint(demo->entry);
+
+	free(str);
+}
+
 /** Display a message window.
  *
@@ -320,9 +445,64 @@
 	rc = ui_prompt_dialog_create(demo->ui, &pdparams, &dialog);
 	if (rc != EOK) {
-		printf("Error creating message dialog.\n");
+		printf("Error creating prompt dialog.\n");
 		return;
 	}
 
 	ui_prompt_dialog_set_cb(dialog, &prompt_dialog_cb, demo);
+}
+
+/** Edit / Insert Character menu entry selected.
+ *
+ * @param mentry Menu entry
+ * @param arg Argument (demo)
+ */
+static void uidemo_edit_insert_character(ui_menu_entry_t *mentry, void *arg)
+{
+	ui_demo_t *demo = (ui_demo_t *) arg;
+	ui_select_dialog_params_t sdparams;
+	ui_select_dialog_t *dialog;
+	ui_list_entry_attr_t attr;
+	errno_t rc;
+
+	ui_select_dialog_params_init(&sdparams);
+	sdparams.caption = "Insert Character";
+	sdparams.prompt = "Select character to insert";
+
+	rc = ui_select_dialog_create(demo->ui, &sdparams, &dialog);
+	if (rc != EOK) {
+		printf("Error creating select dialog.\n");
+		return;
+	}
+
+	ui_list_entry_attr_init(&attr);
+	attr.caption = "Dollar sign ($)";
+	attr.arg = (void *)'$';
+	rc = ui_select_dialog_append(dialog, &attr);
+	if (rc != EOK) {
+		printf("Error appending entry to list.\n");
+		return;
+	}
+
+	ui_list_entry_attr_init(&attr);
+	attr.caption = "Hash sign (#)";
+	attr.arg = (void *)'#';
+	rc = ui_select_dialog_append(dialog, &attr);
+	if (rc != EOK) {
+		printf("Error appending entry to list.\n");
+		return;
+	}
+
+	ui_list_entry_attr_init(&attr);
+	attr.caption = "Question mark (?)";
+	attr.arg = (void *)'?';
+	rc = ui_select_dialog_append(dialog, &attr);
+	if (rc != EOK) {
+		printf("Error appending entry to list.\n");
+		return;
+	}
+
+	ui_select_dialog_set_cb(dialog, &select_dialog_cb, demo);
+
+	(void) ui_select_dialog_paint(dialog);
 }
 
@@ -413,5 +593,5 @@
 /** Prompt dialog cancel button press.
  *
- * @param dialog File dialog
+ * @param dialog Prompt dialog
  * @param arg Argument (ui_demo_t *)
  */
@@ -426,5 +606,5 @@
 /** Prompt dialog close request.
  *
- * @param dialog File dialog
+ * @param dialog Prompt dialog
  * @param arg Argument (ui_demo_t *)
  */
@@ -435,4 +615,48 @@
 	(void) demo;
 	ui_prompt_dialog_destroy(dialog);
+}
+
+/** Select dialog OK button press.
+ *
+ * @param dialog Select dialog
+ * @param arg Argument (ui_demo_t *)
+ * @param text Submitted text
+ */
+static void select_dialog_bok(ui_select_dialog_t *dialog, void *arg,
+    void *earg)
+{
+	ui_demo_t *demo = (ui_demo_t *) arg;
+	char str[2];
+
+	ui_select_dialog_destroy(dialog);
+	str[0] = (char)(intptr_t)earg;
+	str[1] = '\0';
+	(void) ui_entry_insert_str(demo->entry, str);
+}
+
+/** Select dialog cancel button press.
+ *
+ * @param dialog Select dialog
+ * @param arg Argument (ui_demo_t *)
+ */
+static void select_dialog_bcancel(ui_select_dialog_t *dialog, void *arg)
+{
+	ui_demo_t *demo = (ui_demo_t *) arg;
+
+	(void) demo;
+	ui_select_dialog_destroy(dialog);
+}
+
+/** Select dialog close request.
+ *
+ * @param dialog Select dialog
+ * @param arg Argument (ui_demo_t *)
+ */
+static void select_dialog_close(ui_select_dialog_t *dialog, void *arg)
+{
+	ui_demo_t *demo = (ui_demo_t *) arg;
+
+	(void) demo;
+	ui_select_dialog_destroy(dialog);
 }
 
@@ -486,5 +710,7 @@
 	ui_menu_entry_t *mexit;
 	ui_menu_entry_t *mmodify;
+	ui_menu_entry_t *minsert_char;
 	ui_menu_entry_t *mabout;
+	ui_list_entry_attr_t eattr;
 	errno_t rc;
 
@@ -500,5 +726,5 @@
 	ui_wnd_params_init(&params);
 	params.caption = "UI Demo";
-	params.style |= ui_wds_resizable;
+	params.style |= ui_wds_maximize_btn | ui_wds_resizable;
 
 	/* FIXME: Auto layout */
@@ -506,11 +732,11 @@
 		params.rect.p0.x = 0;
 		params.rect.p0.y = 0;
-		params.rect.p1.x = 44;
-		params.rect.p1.y = 21;
+		params.rect.p1.x = 46;
+		params.rect.p1.y = 25;
 	} else {
 		params.rect.p0.x = 0;
 		params.rect.p0.y = 0;
-		params.rect.p1.x = 220;
-		params.rect.p1.y = 350;
+		params.rect.p1.x = 255;
+		params.rect.p1.y = 410;
 	}
 
@@ -539,5 +765,5 @@
 	}
 
-	rc = ui_menu_create(demo.mbar, "File", &demo.mfile);
+	rc = ui_menu_dd_create(demo.mbar, "~F~ile", NULL, &demo.mfile);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -545,5 +771,5 @@
 	}
 
-	rc = ui_menu_entry_create(demo.mfile, "Message", "", &mmsg);
+	rc = ui_menu_entry_create(demo.mfile, "~M~essage", "", &mmsg);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -553,5 +779,5 @@
 	ui_menu_entry_set_cb(mmsg, uidemo_file_message, (void *) &demo);
 
-	rc = ui_menu_entry_create(demo.mfile, "Load", "", &mload);
+	rc = ui_menu_entry_create(demo.mfile, "~L~oad", "", &mload);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -561,5 +787,5 @@
 	ui_menu_entry_set_cb(mload, uidemo_file_load, (void *) &demo);
 
-	rc = ui_menu_entry_create(demo.mfile, "Foo", "Ctrl-Alt-Del", &mfoo);
+	rc = ui_menu_entry_create(demo.mfile, "~F~oo", "Ctrl-Alt-Del", &mfoo);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -567,5 +793,5 @@
 	}
 
-	rc = ui_menu_entry_create(demo.mfile, "Bar", "", &mbar);
+	rc = ui_menu_entry_create(demo.mfile, "~B~ar", "", &mbar);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -573,5 +799,5 @@
 	}
 
-	rc = ui_menu_entry_create(demo.mfile, "Foobar", "", &mfoobar);
+	rc = ui_menu_entry_create(demo.mfile, "F~o~obar", "", &mfoobar);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -579,4 +805,6 @@
 	}
 
+	ui_menu_entry_set_disabled(mfoobar, true);
+
 	rc = ui_menu_entry_sep_create(demo.mfile, &msep);
 	if (rc != EOK) {
@@ -585,5 +813,5 @@
 	}
 
-	rc = ui_menu_entry_create(demo.mfile, "Exit", "Alt-F4", &mexit);
+	rc = ui_menu_entry_create(demo.mfile, "E~x~it", "Alt-F4", &mexit);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -593,5 +821,5 @@
 	ui_menu_entry_set_cb(mexit, uidemo_file_exit, (void *) &demo);
 
-	rc = ui_menu_create(demo.mbar, "Edit", &demo.medit);
+	rc = ui_menu_dd_create(demo.mbar, "~E~dit", NULL, &demo.medit);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -599,5 +827,5 @@
 	}
 
-	rc = ui_menu_entry_create(demo.medit, "Modify", "", &mmodify);
+	rc = ui_menu_entry_create(demo.medit, "~M~odify", "", &mmodify);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -607,5 +835,6 @@
 	ui_menu_entry_set_cb(mmodify, uidemo_edit_modify, (void *) &demo);
 
-	rc = ui_menu_create(demo.mbar, "Preferences", &demo.mpreferences);
+	rc = ui_menu_entry_create(demo.medit, "~I~nsert Character",
+	    "", &minsert_char);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -613,5 +842,9 @@
 	}
 
-	rc = ui_menu_create(demo.mbar, "Help", &demo.mhelp);
+	ui_menu_entry_set_cb(minsert_char, uidemo_edit_insert_character,
+	    (void *) &demo);
+
+	rc = ui_menu_dd_create(demo.mbar, "~P~references", NULL,
+	    &demo.mpreferences);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -619,5 +852,11 @@
 	}
 
-	rc = ui_menu_entry_create(demo.mhelp, "About", "Ctrl-H, F1", &mabout);
+	rc = ui_menu_dd_create(demo.mbar, "~H~elp", NULL, &demo.mhelp);
+	if (rc != EOK) {
+		printf("Error creating menu.\n");
+		return rc;
+	}
+
+	rc = ui_menu_entry_create(demo.mhelp, "~A~bout", "Ctrl-H, F1", &mabout);
 	if (rc != EOK) {
 		printf("Error creating menu.\n");
@@ -634,7 +873,8 @@
 		rect.p0.x = 4;
 		rect.p0.y = 30;
-		rect.p1.x = 216;
+		rect.p1.x = 251;
 		rect.p1.y = 52;
 	}
+
 	ui_menu_bar_set_rect(demo.mbar, &rect);
 
@@ -645,4 +885,49 @@
 	}
 
+	rc = ui_tab_set_create(ui_res, &demo.tabset);
+	if (rc != EOK) {
+		printf("Error creating tab set.\n");
+		return rc;
+	}
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 2;
+		rect.p0.y = 2;
+		rect.p1.x = 44;
+		rect.p1.y = 24;
+	} else {
+		rect.p0.x = 8;
+		rect.p0.y = 53;
+		rect.p1.x = 250;
+		rect.p1.y = 405;
+	}
+
+	ui_tab_set_set_rect(demo.tabset, &rect);
+
+	rc = ui_tab_create(demo.tabset, "Basic", &demo.tbasic);
+	if (rc != EOK) {
+		printf("Error creating tab.\n");
+		return rc;
+	}
+
+	rc = ui_tab_create(demo.tabset, "Lists", &demo.tlists);
+	if (rc != EOK) {
+		printf("Error creating tab.\n");
+		return rc;
+	}
+
+	rc = ui_fixed_add(demo.fixed, ui_tab_set_ctl(demo.tabset));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		return rc;
+	}
+
+	rc = ui_fixed_create(&demo.bfixed);
+	if (rc != EOK) {
+		printf("Error creating fixed layout.\n");
+		return rc;
+	}
+
 	rc = ui_entry_create(window, "", &demo.entry);
 	if (rc != EOK) {
@@ -653,13 +938,13 @@
 	/* FIXME: Auto layout */
 	if (ui_is_textmode(ui)) {
-		rect.p0.x = 2;
-		rect.p0.y = 3;
-		rect.p1.x = 42;
-		rect.p1.y = 4;
+		rect.p0.x = 4;
+		rect.p0.y = 5;
+		rect.p1.x = 41;
+		rect.p1.y = 6;
 	} else {
 		rect.p0.x = 15;
-		rect.p0.y = 53;
+		rect.p0.y = 88;
 		rect.p1.x = 205;
-		rect.p1.y = 78;
+		rect.p1.y = 113;
 	}
 
@@ -667,5 +952,5 @@
 	ui_entry_set_halign(demo.entry, gfx_halign_center);
 
-	rc = ui_fixed_add(demo.fixed, ui_entry_ctl(demo.entry));
+	rc = ui_fixed_add(demo.bfixed, ui_entry_ctl(demo.entry));
 	if (rc != EOK) {
 		printf("Error adding control to layout.\n");
@@ -681,13 +966,13 @@
 	/* FIXME: Auto layout */
 	if (ui_is_textmode(ui)) {
-		rect.p0.x = 2;
-		rect.p0.y = 5;
-		rect.p1.x = 42;
-		rect.p1.y = 6;
+		rect.p0.x = 4;
+		rect.p0.y = 7;
+		rect.p1.x = 41;
+		rect.p1.y = 8;
 	} else {
 		rect.p0.x = 60;
-		rect.p0.y = 88;
+		rect.p0.y = 123;
 		rect.p1.x = 160;
-		rect.p1.y = 101;
+		rect.p1.y = 136;
 	}
 
@@ -695,5 +980,5 @@
 	ui_label_set_halign(demo.label, gfx_halign_center);
 
-	rc = ui_fixed_add(demo.fixed, ui_label_ctl(demo.label));
+	rc = ui_fixed_add(demo.bfixed, ui_label_ctl(demo.label));
 	if (rc != EOK) {
 		printf("Error adding control to layout.\n");
@@ -711,13 +996,13 @@
 	/* FIXME: Auto layout */
 	if (ui_is_textmode(ui)) {
-		rect.p0.x = 2;
-		rect.p0.y = 7;
-		rect.p1.x = 12;
-		rect.p1.y = 8;
+		rect.p0.x = 4;
+		rect.p0.y = 9;
+		rect.p1.x = 15;
+		rect.p1.y = 10;
 	} else {
 		rect.p0.x = 15;
-		rect.p0.y = 111;
+		rect.p0.y = 146;
 		rect.p1.x = 105;
-		rect.p1.y = 139;
+		rect.p1.y = 174;
 	}
 
@@ -726,5 +1011,5 @@
 	ui_pbutton_set_default(demo.pb1, true);
 
-	rc = ui_fixed_add(demo.fixed, ui_pbutton_ctl(demo.pb1));
+	rc = ui_fixed_add(demo.bfixed, ui_pbutton_ctl(demo.pb1));
 	if (rc != EOK) {
 		printf("Error adding control to layout.\n");
@@ -741,18 +1026,18 @@
 
 	if (ui_is_textmode(ui)) {
-		rect.p0.x = 32;
-		rect.p0.y = 7;
-		rect.p1.x = 42;
-		rect.p1.y = 8;
+		rect.p0.x = 30;
+		rect.p0.y = 9;
+		rect.p1.x = 41;
+		rect.p1.y = 10;
 	} else {
 		rect.p0.x = 115;
-		rect.p0.y = 111;
+		rect.p0.y = 146;
 		rect.p1.x = 205;
-		rect.p1.y = 139;
+		rect.p1.y = 174;
 	}
 
 	ui_pbutton_set_rect(demo.pb2, &rect);
 
-	rc = ui_fixed_add(demo.fixed, ui_pbutton_ctl(demo.pb2));
+	rc = ui_fixed_add(demo.bfixed, ui_pbutton_ctl(demo.pb2));
 	if (rc != EOK) {
 		printf("Error adding control to layout.\n");
@@ -764,5 +1049,5 @@
 		bparams.rect.p0.x = 0;
 		bparams.rect.p0.y = 0;
-		bparams.rect.p1.x = 40;
+		bparams.rect.p1.x = 37;
 		bparams.rect.p1.y = 2;
 	} else {
@@ -788,9 +1073,9 @@
 
 	if (ui_is_textmode(ui)) {
-		off.x = 2;
-		off.y = 9;
+		off.x = 4;
+		off.y = 11;
 	} else {
 		off.x = 15;
-		off.y = 155;
+		off.y = 190;
 	}
 
@@ -806,5 +1091,5 @@
 	ui_image_set_rect(demo.image, &rect);
 
-	rc = ui_fixed_add(demo.fixed, ui_image_ctl(demo.image));
+	rc = ui_fixed_add(demo.bfixed, ui_image_ctl(demo.image));
 	if (rc != EOK) {
 		printf("Error adding control to layout.\n");
@@ -822,18 +1107,18 @@
 	/* FIXME: Auto layout */
 	if (ui_is_textmode(ui)) {
-		rect.p0.x = 2;
-		rect.p0.y = 12;
-		rect.p1.x = 12;
-		rect.p1.y = 13;
+		rect.p0.x = 4;
+		rect.p0.y = 14;
+		rect.p1.x = 14;
+		rect.p1.y = 15;
 	} else {
 		rect.p0.x = 15;
-		rect.p0.y = 190;
+		rect.p0.y = 225;
 		rect.p1.x = 140;
-		rect.p1.y = 210;
+		rect.p1.y = 245;
 	}
 
 	ui_checkbox_set_rect(demo.checkbox, &rect);
 
-	rc = ui_fixed_add(demo.fixed, ui_checkbox_ctl(demo.checkbox));
+	rc = ui_fixed_add(demo.bfixed, ui_checkbox_ctl(demo.checkbox));
 	if (rc != EOK) {
 		printf("Error adding control to layout.\n");
@@ -859,17 +1144,17 @@
 	/* FIXME: Auto layout */
 	if (ui_is_textmode(ui)) {
-		rect.p0.x = 2;
-		rect.p0.y = 14;
-		rect.p1.x = 12;
-		rect.p1.y = 15;
+		rect.p0.x = 4;
+		rect.p0.y = 16;
+		rect.p1.x = 14;
+		rect.p1.y = 17;
 	} else {
 		rect.p0.x = 15;
-		rect.p0.y = 220;
+		rect.p0.y = 255;
 		rect.p1.x = 140;
-		rect.p1.y = 240;
+		rect.p1.y = 275;
 	}
 	ui_rbutton_set_rect(demo.rbleft, &rect);
 
-	rc = ui_fixed_add(demo.fixed, ui_rbutton_ctl(demo.rbleft));
+	rc = ui_fixed_add(demo.bfixed, ui_rbutton_ctl(demo.rbleft));
 	if (rc != EOK) {
 		printf("Error adding control to layout.\n");
@@ -886,18 +1171,18 @@
 	/* FIXME: Auto layout */
 	if (ui_is_textmode(ui)) {
-		rect.p0.x = 2;
-		rect.p0.y = 15;
-		rect.p1.x = 12;
-		rect.p1.y = 16;
+		rect.p0.x = 4;
+		rect.p0.y = 17;
+		rect.p1.x = 14;
+		rect.p1.y = 18;
 	} else {
 		rect.p0.x = 15;
-		rect.p0.y = 250;
+		rect.p0.y = 285;
 		rect.p1.x = 140;
-		rect.p1.y = 270;
+		rect.p1.y = 305;
 	}
 	ui_rbutton_set_rect(demo.rbcenter, &rect);
 	ui_rbutton_select(demo.rbcenter);
 
-	rc = ui_fixed_add(demo.fixed, ui_rbutton_ctl(demo.rbcenter));
+	rc = ui_fixed_add(demo.bfixed, ui_rbutton_ctl(demo.rbcenter));
 	if (rc != EOK) {
 		printf("Error adding control to layout.\n");
@@ -914,17 +1199,17 @@
 	/* FIXME: Auto layout */
 	if (ui_is_textmode(ui)) {
-		rect.p0.x = 2;
-		rect.p0.y = 16;
-		rect.p1.x = 12;
-		rect.p1.y = 17;
+		rect.p0.x = 4;
+		rect.p0.y = 18;
+		rect.p1.x = 14;
+		rect.p1.y = 19;
 	} else {
 		rect.p0.x = 15;
-		rect.p0.y = 280;
+		rect.p0.y = 315;
 		rect.p1.x = 140;
-		rect.p1.y = 300;
+		rect.p1.y = 335;
 	}
 	ui_rbutton_set_rect(demo.rbright, &rect);
 
-	rc = ui_fixed_add(demo.fixed, ui_rbutton_ctl(demo.rbright));
+	rc = ui_fixed_add(demo.bfixed, ui_rbutton_ctl(demo.rbright));
 	if (rc != EOK) {
 		printf("Error adding control to layout.\n");
@@ -932,5 +1217,5 @@
 	}
 
-	rc = ui_slider_create(ui_res, "Slide!", &demo.slider);
+	rc = ui_slider_create(ui_res, &demo.slider);
 	if (rc != EOK) {
 		printf("Error creating button.\n");
@@ -942,22 +1227,167 @@
 	/* FIXME: Auto layout */
 	if (ui_is_textmode(ui)) {
-		rect.p0.x = 2;
-		rect.p0.y = 18;
-		rect.p1.x = 12;
-		rect.p1.y = 19;
+		rect.p0.x = 4;
+		rect.p0.y = 20;
+		rect.p1.x = 32;
+		rect.p1.y = 21;
 	} else {
 		rect.p0.x = 15;
-		rect.p0.y = 310;
+		rect.p0.y = 345;
 		rect.p1.x = 130;
-		rect.p1.y = 330;
+		rect.p1.y = 365;
 	}
 
 	ui_slider_set_rect(demo.slider, &rect);
 
-	rc = ui_fixed_add(demo.fixed, ui_slider_ctl(demo.slider));
+	rc = ui_fixed_add(demo.bfixed, ui_slider_ctl(demo.slider));
 	if (rc != EOK) {
 		printf("Error adding control to layout.\n");
 		return rc;
 	}
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &demo.hscrollbar);
+	if (rc != EOK) {
+		printf("Error creating scrollbar.\n");
+		return rc;
+	}
+
+	ui_scrollbar_set_cb(demo.hscrollbar, &scrollbar_cb, (void *) &demo);
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 4;
+		rect.p0.y = 22;
+		rect.p1.x = 42;
+		rect.p1.y = 23;
+	} else {
+		rect.p0.x = 15;
+		rect.p0.y = 375;
+		rect.p1.x = 220;
+		rect.p1.y = 398;
+	}
+
+	ui_scrollbar_set_rect(demo.hscrollbar, &rect);
+
+	ui_scrollbar_set_thumb_length(demo.hscrollbar,
+	    ui_scrollbar_trough_length(demo.hscrollbar) / 4);
+
+	rc = ui_fixed_add(demo.bfixed, ui_scrollbar_ctl(demo.hscrollbar));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		return rc;
+	}
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_vert, &demo.vscrollbar);
+	if (rc != EOK) {
+		printf("Error creating button.\n");
+		return rc;
+	}
+
+	ui_scrollbar_set_cb(demo.vscrollbar, &scrollbar_cb, (void *) &demo);
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 42;
+		rect.p0.y = 5;
+		rect.p1.x = 43;
+		rect.p1.y = 22;
+	} else {
+		rect.p0.x = 220;
+		rect.p0.y = 88;
+		rect.p1.x = 243;
+		rect.p1.y = 375;
+	}
+
+	ui_scrollbar_set_rect(demo.vscrollbar, &rect);
+
+	ui_scrollbar_set_thumb_length(demo.vscrollbar,
+	    ui_scrollbar_trough_length(demo.vscrollbar) / 4);
+
+	rc = ui_fixed_add(demo.bfixed, ui_scrollbar_ctl(demo.vscrollbar));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		return rc;
+	}
+
+	ui_tab_add(demo.tbasic, ui_fixed_ctl(demo.bfixed));
+
+	rc = ui_fixed_create(&demo.lfixed);
+	if (rc != EOK) {
+		printf("Error creating fixed layout.\n");
+		return rc;
+	}
+
+	rc = ui_list_create(window, false, &demo.list);
+	if (rc != EOK) {
+		printf("Error creating list.\n");
+		return rc;
+	}
+
+	ui_list_entry_attr_init(&eattr);
+
+	eattr.caption = "One";
+	rc = ui_list_entry_append(demo.list, &eattr, NULL);
+	if (rc != EOK) {
+		printf("Error adding list entry.\n");
+		return rc;
+	}
+
+	eattr.caption = "Two";
+	rc = ui_list_entry_append(demo.list, &eattr, NULL);
+	if (rc != EOK) {
+		printf("Error adding list entry.\n");
+		return rc;
+	}
+
+	eattr.caption = "Three";
+	rc = ui_list_entry_append(demo.list, &eattr, NULL);
+	if (rc != EOK) {
+		printf("Error adding list entry.\n");
+		return rc;
+	}
+
+	eattr.caption = "Four";
+	rc = ui_list_entry_append(demo.list, &eattr, NULL);
+	if (rc != EOK) {
+		printf("Error adding list entry.\n");
+		return rc;
+	}
+
+	eattr.caption = "Five";
+	rc = ui_list_entry_append(demo.list, &eattr, NULL);
+	if (rc != EOK) {
+		printf("Error adding list entry.\n");
+		return rc;
+	}
+
+	eattr.caption = "Six";
+	rc = ui_list_entry_append(demo.list, &eattr, NULL);
+	if (rc != EOK) {
+		printf("Error adding list entry.\n");
+		return rc;
+	}
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 4;
+		rect.p0.y = 5;
+		rect.p1.x = 41;
+		rect.p1.y = 10;
+	} else {
+		rect.p0.x = 15;
+		rect.p0.y = 88;
+		rect.p1.x = 245;
+		rect.p1.y = 173;
+	}
+
+	ui_list_set_rect(demo.list, &rect);
+
+	rc = ui_fixed_add(demo.lfixed, ui_list_ctl(demo.list));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		return rc;
+	}
+
+	ui_tab_add(demo.tlists, ui_fixed_ctl(demo.lfixed));
 
 	ui_window_add(window, ui_fixed_ctl(demo.fixed));
@@ -1019,5 +1449,5 @@
 int main(int argc, char *argv[])
 {
-	const char *display_spec = UI_DISPLAY_DEFAULT;
+	const char *display_spec = UI_ANY_DEFAULT;
 	errno_t rc;
 	int i;
Index: uspace/app/uidemo/uidemo.h
===================================================================
--- uspace/app/uidemo/uidemo.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/uidemo/uidemo.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -42,9 +42,13 @@
 #include <ui/fixed.h>
 #include <ui/label.h>
+#include <ui/list.h>
 #include <ui/menu.h>
 #include <ui/menubar.h>
 #include <ui/pbutton.h>
 #include <ui/rbutton.h>
+#include <ui/scrollbar.h>
 #include <ui/slider.h>
+#include <ui/tab.h>
+#include <ui/tabset.h>
 #include <ui/ui.h>
 #include <ui/window.h>
@@ -55,4 +59,6 @@
 	ui_window_t *window;
 	ui_fixed_t *fixed;
+	ui_fixed_t *bfixed;
+	ui_fixed_t *lfixed;
 	ui_menu_bar_t *mbar;
 	ui_menu_t *mfile;
@@ -60,4 +66,7 @@
 	ui_menu_t *mpreferences;
 	ui_menu_t *mhelp;
+	ui_tab_set_t *tabset;
+	ui_tab_t *tbasic;
+	ui_tab_t *tlists;
 	ui_entry_t *entry;
 	ui_image_t *image;
@@ -71,4 +80,7 @@
 	ui_rbutton_t *rbright;
 	ui_slider_t *slider;
+	ui_scrollbar_t *hscrollbar;
+	ui_scrollbar_t *vscrollbar;
+	ui_list_t *list;
 } ui_demo_t;
 
Index: uspace/app/viewer/viewer.c
===================================================================
--- uspace/app/viewer/viewer.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/viewer/viewer.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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 = 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/app/wifi_supplicant/wifi_supplicant.c
===================================================================
--- uspace/app/wifi_supplicant/wifi_supplicant.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/app/wifi_supplicant/wifi_supplicant.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -75,15 +75,9 @@
 }
 
-static char *nic_addr_format(nic_address_t *addr)
-{
-	char *str;
-	int rc = asprintf(&str, "%02x:%02x:%02x:%02x:%02x:%02x",
+static void nic_addr_format(nic_address_t *addr, char *out, size_t out_size)
+{
+	snprintf(out, out_size, "%02x:%02x:%02x:%02x:%02x:%02x",
 	    addr->address[0], addr->address[1], addr->address[2],
 	    addr->address[3], addr->address[4], addr->address[5]);
-
-	if (rc < 0)
-		return NULL;
-
-	return str;
 }
 
@@ -263,7 +257,9 @@
 	for (uint8_t i = 0; i < scan_results.length; i++) {
 		ieee80211_scan_result_t result = scan_results.results[i];
+		char mac_addr_hex[18];
+		nic_addr_format(&result.bssid, mac_addr_hex, 18);
 
 		printf("%16.16s %17s %4d %5s %5s %7s %7s\n",
-		    result.ssid, nic_addr_format(&result.bssid),
+		    result.ssid, mac_addr_hex,
 		    result.channel,
 		    enum_name(ieee80211_security_type_strs, result.security.type),
Index: uspace/drv/audio/hdaudio/codec.c
===================================================================
--- uspace/drv/audio/hdaudio/codec.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/audio/hdaudio/codec.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2014 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -512,4 +512,5 @@
 	codec->address = address;
 	codec->in_aw = -1;
+	codec->out_aw = -1;
 
 	rc = hda_get_subnc(codec, 0, &sfg, &nfg);
@@ -584,6 +585,4 @@
 					goto error;
 			} else if (awtype == awt_audio_output) {
-				codec->out_aw_list[codec->out_aw_num++] = aw;
-
 				rc = hda_get_supp_rates(codec, aw, &rates);
 				if (rc != EOK)
@@ -594,6 +593,18 @@
 					goto error;
 
-				ddf_msg(LVL_DEBUG, "Output widget %d: rates=0x%x formats=0x%x",
+				if (rates != 0 && formats != 0 &&
+				    codec->out_aw < 0) {
+					ddf_msg(LVL_DEBUG, "Selected output "
+					    "widget %d\n", aw);
+					codec->out_aw = aw;
+				} else {
+					ddf_msg(LVL_DEBUG, "Ignoring output "
+					    "widget %d\n", aw);
+				}
+
+				ddf_msg(LVL_NOTE, "Output widget %d: rates=0x%x formats=0x%x",
 				    aw, rates, formats);
+				codec->out_aw_rates = rates;
+				codec->out_aw_formats = formats;
 			} else if (awtype == awt_audio_input) {
 				if (codec->in_aw < 0) {
@@ -616,4 +627,6 @@
 				ddf_msg(LVL_DEBUG, "Input widget %d: rates=0x%x formats=0x%x",
 				    aw, rates, formats);
+				codec->in_aw_rates = rates;
+				codec->in_aw_formats = formats;
 			}
 
@@ -644,22 +657,16 @@
 {
 	errno_t rc;
-	int out_aw;
-	int i;
-
-	for (i = 0; i < codec->out_aw_num; i++) {
-		out_aw = codec->out_aw_list[i];
-
-		/* Configure converter */
-
-		ddf_msg(LVL_DEBUG, "Configure output converter format");
-		rc = hda_set_converter_fmt(codec, out_aw, stream->fmt);
-		if (rc != EOK)
-			goto error;
-
-		ddf_msg(LVL_DEBUG, "Configure output converter stream, channel");
-		rc = hda_set_converter_ctl(codec, out_aw, stream->sid, 0);
-		if (rc != EOK)
-			goto error;
-	}
+
+	/* Configure converter */
+	ddf_msg(LVL_DEBUG, "Configure output converter format / %d",
+	    codec->out_aw);
+	rc = hda_set_converter_fmt(codec, codec->out_aw, stream->fmt);
+	if (rc != EOK)
+		goto error;
+
+	ddf_msg(LVL_DEBUG, "Configure output converter stream, channel");
+	rc = hda_set_converter_ctl(codec, codec->out_aw, stream->sid, 0);
+	if (rc != EOK)
+		goto error;
 
 	return EOK;
Index: uspace/drv/audio/hdaudio/codec.h
===================================================================
--- uspace/drv/audio/hdaudio/codec.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/audio/hdaudio/codec.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2014 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,13 +39,15 @@
 #include "stream.h"
 
-#define MAX_OUT_AW 256
-
 typedef struct hda_codec {
 	hda_t *hda;
 	uint8_t address;
-	uint8_t out_aw_list[MAX_OUT_AW];
+	int out_aw;
+	uint32_t out_aw_rates;
+	uint32_t out_aw_formats;
 	int out_aw_num;
 	int out_aw_sel;
 	int in_aw;
+	uint32_t in_aw_rates;
+	uint32_t in_aw_formats;
 } hda_codec_t;
 
Index: uspace/drv/audio/hdaudio/hdactl.c
===================================================================
--- uspace/drv/audio/hdaudio/hdactl.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/audio/hdaudio/hdactl.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2014 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -670,6 +670,28 @@
 }
 
+static void hda_ctl_check_fifo_error(hda_ctl_t *ctl)
+{
+	int i;
+	uint8_t sts;
+
+	/*
+	 * XXX Ideally the interrupt handler would tell us which stream
+	 * has the error.
+	 */
+
+	for (i = 0; i < 30; i++) {
+		sts = hda_reg8_read(&ctl->hda->regs->sdesc[i].sts);
+		if ((sts & BIT_V(uint8_t, sdsts_fifoe)) != 0 && (sts & 0x80) == 0) {
+			ddf_msg(LVL_WARN, "sts[%d] = 0x%hhx\n", i, sts);
+			hda_reg8_write(&ctl->hda->regs->sdesc[i].sts,
+			    BIT_V(uint8_t, sdsts_fifoe));
+		}
+	}
+}
+
 void hda_ctl_interrupt(hda_ctl_t *ctl)
 {
+	ddf_msg(LVL_DEBUG, "hda_ctl_interrupt");
+	hda_ctl_check_fifo_error(ctl);
 	hda_ctl_process_rirb(ctl);
 }
Index: uspace/drv/audio/hdaudio/hdaudio.c
===================================================================
--- uspace/drv/audio/hdaudio/hdaudio.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/audio/hdaudio/hdaudio.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2014 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -58,5 +58,5 @@
 static errno_t hda_fun_offline(ddf_fun_t *fun);
 
-static void hdaudio_interrupt(ipc_call_t *, ddf_dev_t *);
+static void hdaudio_interrupt(ipc_call_t *, void *);
 
 static driver_ops_t driver_ops = {
@@ -140,5 +140,5 @@
 		.cmd = CMD_PIO_WRITE_8,
 		.addr = NULL, /* sdesc[x].sts */
-		.value = 0x4 /* XXX sdesc.sts.BCIS */
+		.value = BIT_V(uint8_t, sdsts_bcis)
 	},
 	/* 4 */
@@ -266,5 +266,5 @@
 	cap_irq_handle_t irq_cap;
 	rc = register_interrupt_handler(dev, res.irqs.irqs[0],
-	    hdaudio_interrupt, &irq_code, &irq_cap);
+	    hdaudio_interrupt, (void *)hda, &irq_code, &irq_cap);
 	if (rc != EOK) {
 		ddf_msg(LVL_ERROR, "Failed registering interrupt handler: %s",
@@ -377,7 +377,12 @@
 }
 
-static void hdaudio_interrupt(ipc_call_t *icall, ddf_dev_t *dev)
-{
-	hda_t *hda = (hda_t *)ddf_dev_data_get(dev);
+/** HD Audio interrupt handler.
+ *
+ * @param icall IRQ event notification
+ * @param arg Argument (hda_t *)
+ */
+static void hdaudio_interrupt(ipc_call_t *icall, void *arg)
+{
+	hda_t *hda = (hda_t *)arg;
 
 	if (0)
Index: uspace/drv/audio/hdaudio/pcm_iface.c
===================================================================
--- uspace/drv/audio/hdaudio/pcm_iface.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/audio/hdaudio/pcm_iface.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2014 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -132,5 +132,5 @@
 	    *channels, *rate, *format);
 
-	if (*channels != 1) {
+	if (*channels != 2) {
 		*channels = 2;
 		rc = ELIMIT;
Index: uspace/drv/audio/hdaudio/spec/regs.h
===================================================================
--- uspace/drv/audio/hdaudio/spec/regs.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/audio/hdaudio/spec/regs.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2014 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -165,4 +165,5 @@
 } hda_regs_t;
 
+/** Stream Descriptor Control bits */
 typedef enum {
 	/** Descriptor Error Interrupt Enable */
@@ -177,4 +178,16 @@
 	sdctl1_srst = 0
 } hda_sdesc_ctl1_bits;
+
+/** Stream Descriptor Status bits */
+typedef enum {
+	/** FIFO Ready */
+	sdctl1_fifordy = 3,
+	/** Descriptor Error */
+	sdsts_dese = 2,
+	/** FIFO Error */
+	sdsts_fifoe = 1,
+	/** Buffer Completion Interrupt Status */
+	sdsts_bcis = 2
+} hda_sdesc_sts_bits;
 
 typedef enum {
Index: uspace/drv/audio/hdaudio/stream.c
===================================================================
--- uspace/drv/audio/hdaudio/stream.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/audio/hdaudio/stream.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2014 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -166,6 +166,9 @@
 	uint8_t ctl3;
 
+	/* Stream ID */
 	ctl3 = (stream->sid << 4);
-	ctl1 = 0x4;
+
+	/* Interrupt on buffer completion */
+	ctl1 = BIT_V(uint8_t, sdctl1_ioce);
 
 	sdregs = &stream->hda->regs->sdesc[stream->sdid];
Index: uspace/drv/audio/sb16/main.c
===================================================================
--- uspace/drv/audio/sb16/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/audio/sb16/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -76,7 +76,12 @@
 }
 
-static void irq_handler(ipc_call_t *call, ddf_dev_t *dev)
-{
-	sb16_t *sb16_dev = ddf_dev_data_get(dev);
+/** SB16 IRQ handler.
+ *
+ * @param call IRQ event notification
+ * @param arg Argument (sb16_t *)
+ */
+static void irq_handler(ipc_call_t *call, void *arg)
+{
+	sb16_t *sb16_dev = (sb16_t *)arg;
 	sb16_interrupt(sb16_dev);
 }
@@ -124,5 +129,5 @@
 
 	rc = register_interrupt_handler(device, irq, irq_handler,
-	    &irq_code, &irq_cap);
+	    (void *)soft_state, &irq_code, &irq_cap);
 	if (rc != EOK) {
 		ddf_log_error("Failed to register irq handler: %s.",
Index: uspace/drv/block/ahci/ahci.c
===================================================================
--- uspace/drv/block/ahci/ahci.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/block/ahci/ahci.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -162,10 +162,4 @@
 }
 
-/** Get AHCI structure from DDF device. */
-static ahci_dev_t *dev_ahci_dev(ddf_dev_t *dev)
-{
-	return ddf_dev_data_get(dev);
-}
-
 /** Get SATA device name.
  *
@@ -891,10 +885,9 @@
  *
  * @param icall The IPC call structure.
- * @param dev   DDF device structure.
- *
- */
-static void ahci_interrupt(ipc_call_t *icall, ddf_dev_t *dev)
-{
-	ahci_dev_t *ahci = dev_ahci_dev(dev);
+ * @param arg   Argument (ahci_dev_t *)
+ */
+static void ahci_interrupt(ipc_call_t *icall, void *arg)
+{
+	ahci_dev_t *ahci = (ahci_dev_t *)arg;
 	unsigned int port = ipc_get_arg1(icall);
 	ahci_port_is_t pxis = ipc_get_arg2(icall);
@@ -1186,5 +1179,6 @@
 	cap_irq_handle_t irq_cap;
 	errno_t rc = register_interrupt_handler(dev,
-	    hw_res_parsed.irqs.irqs[0], ahci_interrupt, &ct, &irq_cap);
+	    hw_res_parsed.irqs.irqs[0], ahci_interrupt, (void *)ahci, &ct,
+	    &irq_cap);
 	if (rc != EOK) {
 		ddf_msg(LVL_ERROR, "Failed registering interrupt handler.");
Index: pace/drv/block/ata_bd/ata_bd.c
===================================================================
--- uspace/drv/block/ata_bd/ata_bd.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,1293 +1,0 @@
-/*
- * Copyright (c) 2013 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 ata_bd
- * @{
- */
-
-/**
- * @file
- * @brief ATA disk driver
- *
- * This driver supports CHS, 28-bit and 48-bit LBA addressing, as well as
- * PACKET devices. It only uses PIO transfers. There is no support DMA
- * or any other fancy features such as S.M.A.R.T, removable devices, etc.
- *
- * This driver is based on the ATA-1, ATA-2, ATA-3 and ATA/ATAPI-4 through 7
- * standards, as published by the ANSI, NCITS and INCITS standards bodies,
- * which are freely available. This driver contains no vendor-specific
- * code at this moment.
- *
- * The driver services a single controller which can have up to two disks
- * attached.
- */
-
-#include <ddi.h>
-#include <ddf/log.h>
-#include <async.h>
-#include <as.h>
-#include <bd_srv.h>
-#include <fibril_synch.h>
-#include <scsi/mmc.h>
-#include <scsi/sbc.h>
-#include <scsi/spc.h>
-#include <stdint.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stddef.h>
-#include <str.h>
-#include <inttypes.h>
-#include <errno.h>
-#include <byteorder.h>
-#include <macros.h>
-
-#include "ata_hw.h"
-#include "ata_bd.h"
-#include "main.h"
-
-#define NAME       "ata_bd"
-
-/** Number of defined legacy controller base addresses. */
-#define LEGACY_CTLS 4
-
-/**
- * Size of data returned from Identify Device or Identify Packet Device
- * command.
- */
-static const size_t identify_data_size = 512;
-
-static errno_t ata_bd_init_io(ata_ctrl_t *ctrl);
-static void ata_bd_fini_io(ata_ctrl_t *ctrl);
-
-static errno_t ata_bd_open(bd_srvs_t *, bd_srv_t *);
-static errno_t ata_bd_close(bd_srv_t *);
-static errno_t ata_bd_read_blocks(bd_srv_t *, uint64_t ba, size_t cnt, void *buf,
-    size_t);
-static errno_t ata_bd_read_toc(bd_srv_t *, uint8_t session, void *buf, size_t);
-static errno_t ata_bd_write_blocks(bd_srv_t *, uint64_t ba, size_t cnt,
-    const void *buf, size_t);
-static errno_t ata_bd_get_block_size(bd_srv_t *, size_t *);
-static errno_t ata_bd_get_num_blocks(bd_srv_t *, aoff64_t *);
-static errno_t ata_bd_sync_cache(bd_srv_t *, aoff64_t, size_t);
-
-static errno_t ata_rcmd_read(disk_t *disk, uint64_t ba, size_t cnt,
-    void *buf);
-static errno_t ata_rcmd_write(disk_t *disk, uint64_t ba, size_t cnt,
-    const void *buf);
-static errno_t ata_rcmd_flush_cache(disk_t *disk);
-static errno_t disk_init(ata_ctrl_t *ctrl, disk_t *d, int disk_id);
-static errno_t ata_identify_dev(disk_t *disk, void *buf);
-static errno_t ata_identify_pkt_dev(disk_t *disk, void *buf);
-static errno_t ata_cmd_packet(disk_t *disk, const void *cpkt, size_t cpkt_size,
-    void *obuf, size_t obuf_size, size_t *rcvd_size);
-static errno_t ata_pcmd_inquiry(disk_t *disk, void *obuf, size_t obuf_size,
-    size_t *rcvd_size);
-static errno_t ata_pcmd_read_12(disk_t *disk, uint64_t ba, size_t cnt,
-    void *obuf, size_t obuf_size);
-static errno_t ata_pcmd_read_capacity(disk_t *disk, uint64_t *nblocks,
-    size_t *block_size);
-static errno_t ata_pcmd_read_toc(disk_t *disk, uint8_t ses,
-    void *obuf, size_t obuf_size);
-static void disk_print_summary(disk_t *d);
-static errno_t coord_calc(disk_t *d, uint64_t ba, block_coord_t *bc);
-static void coord_sc_program(ata_ctrl_t *ctrl, const block_coord_t *bc,
-    uint16_t scnt);
-static errno_t wait_status(ata_ctrl_t *ctrl, unsigned set, unsigned n_reset,
-    uint8_t *pstatus, unsigned timeout);
-
-bd_ops_t ata_bd_ops = {
-	.open = ata_bd_open,
-	.close = ata_bd_close,
-	.read_blocks = ata_bd_read_blocks,
-	.read_toc = ata_bd_read_toc,
-	.write_blocks = ata_bd_write_blocks,
-	.get_block_size = ata_bd_get_block_size,
-	.get_num_blocks = ata_bd_get_num_blocks,
-	.sync_cache = ata_bd_sync_cache
-};
-
-static disk_t *bd_srv_disk(bd_srv_t *bd)
-{
-	return (disk_t *)bd->srvs->sarg;
-}
-
-static int disk_dev_idx(disk_t *disk)
-{
-	return (disk->disk_id & 1);
-}
-
-/** Initialize ATA controller. */
-errno_t ata_ctrl_init(ata_ctrl_t *ctrl, ata_base_t *res)
-{
-	int i;
-	errno_t rc;
-	int n_disks;
-
-	ddf_msg(LVL_DEBUG, "ata_ctrl_init()");
-
-	fibril_mutex_initialize(&ctrl->lock);
-	ctrl->cmd_physical = res->cmd;
-	ctrl->ctl_physical = res->ctl;
-
-	ddf_msg(LVL_NOTE, "I/O address %p/%p", (void *) ctrl->cmd_physical,
-	    (void *) ctrl->ctl_physical);
-
-	rc = ata_bd_init_io(ctrl);
-	if (rc != EOK)
-		return rc;
-
-	for (i = 0; i < MAX_DISKS; i++) {
-		ddf_msg(LVL_DEBUG, "Identify drive %d...", i);
-
-		rc = disk_init(ctrl, &ctrl->disk[i], i);
-
-		if (rc == EOK) {
-			disk_print_summary(&ctrl->disk[i]);
-		} else {
-			ddf_msg(LVL_DEBUG, "Not found.");
-		}
-	}
-
-	n_disks = 0;
-
-	for (i = 0; i < MAX_DISKS; i++) {
-		/* Skip unattached drives. */
-		if (ctrl->disk[i].present == false)
-			continue;
-
-		rc = ata_fun_create(&ctrl->disk[i]);
-		if (rc != EOK) {
-			ddf_msg(LVL_ERROR, "Unable to create function for "
-			    "disk %d.", i);
-			goto error;
-		}
-		++n_disks;
-	}
-
-	if (n_disks == 0) {
-		ddf_msg(LVL_WARN, "No disks detected.");
-		rc = ENOENT;
-		goto error;
-	}
-
-	return EOK;
-error:
-	for (i = 0; i < MAX_DISKS; i++) {
-		if (ata_fun_remove(&ctrl->disk[i]) != EOK) {
-			ddf_msg(LVL_ERROR, "Unable to clean up function for "
-			    "disk %d.", i);
-		}
-	}
-	ata_bd_fini_io(ctrl);
-	return rc;
-}
-
-/** Remove ATA controller. */
-errno_t ata_ctrl_remove(ata_ctrl_t *ctrl)
-{
-	int i;
-	errno_t rc;
-
-	ddf_msg(LVL_DEBUG, ": ata_ctrl_remove()");
-
-	fibril_mutex_lock(&ctrl->lock);
-
-	for (i = 0; i < MAX_DISKS; i++) {
-		rc = ata_fun_remove(&ctrl->disk[i]);
-		if (rc != EOK) {
-			ddf_msg(LVL_ERROR, "Unable to clean up function for "
-			    "disk %d.", i);
-			return rc;
-		}
-	}
-
-	ata_bd_fini_io(ctrl);
-	fibril_mutex_unlock(&ctrl->lock);
-
-	return EOK;
-}
-
-/** Surprise removal of ATA controller. */
-errno_t ata_ctrl_gone(ata_ctrl_t *ctrl)
-{
-	int i;
-	errno_t rc;
-
-	ddf_msg(LVL_DEBUG, "ata_ctrl_gone()");
-
-	fibril_mutex_lock(&ctrl->lock);
-
-	for (i = 0; i < MAX_DISKS; i++) {
-		rc = ata_fun_unbind(&ctrl->disk[i]);
-		if (rc != EOK) {
-			ddf_msg(LVL_ERROR, "Unable to clean up function for "
-			    "disk %d.", i);
-			return rc;
-		}
-	}
-
-	ata_bd_fini_io(ctrl);
-	fibril_mutex_unlock(&ctrl->lock);
-
-	return EOK;
-}
-
-/** Print one-line device summary. */
-static void disk_print_summary(disk_t *d)
-{
-	uint64_t mbytes;
-	char *atype = NULL;
-	char *cap = NULL;
-	int rc;
-
-	if (d->dev_type == ata_reg_dev) {
-		switch (d->amode) {
-		case am_chs:
-			rc = asprintf(&atype, "CHS %u cylinders, %u heads, "
-			    "%u sectors", d->geom.cylinders, d->geom.heads,
-			    d->geom.sectors);
-			if (rc < 0) {
-				/* Out of memory */
-				atype = NULL;
-			}
-			break;
-		case am_lba28:
-			atype = str_dup("LBA-28");
-			break;
-		case am_lba48:
-			atype = str_dup("LBA-48");
-			break;
-		}
-	} else {
-		atype = str_dup("PACKET");
-	}
-
-	if (atype == NULL)
-		return;
-
-	mbytes = d->blocks / (2 * 1024);
-	if (mbytes > 0) {
-		rc = asprintf(&cap, " %" PRIu64 " MB.", mbytes);
-		if (rc < 0) {
-			cap = NULL;
-			goto cleanup;
-		}
-	}
-
-	ddf_msg(LVL_NOTE, "%s: %s %" PRIu64 " blocks%s", d->model, atype,
-	    d->blocks, cap);
-cleanup:
-	free(atype);
-	free(cap);
-}
-
-/** Enable device I/O. */
-static errno_t ata_bd_init_io(ata_ctrl_t *ctrl)
-{
-	errno_t rc;
-	void *vaddr;
-
-	rc = pio_enable((void *) ctrl->cmd_physical, sizeof(ata_cmd_t), &vaddr);
-	if (rc != EOK) {
-		ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
-		return rc;
-	}
-
-	ctrl->cmd = vaddr;
-
-	rc = pio_enable((void *) ctrl->ctl_physical, sizeof(ata_ctl_t), &vaddr);
-	if (rc != EOK) {
-		ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
-		return rc;
-	}
-
-	ctrl->ctl = vaddr;
-
-	return EOK;
-}
-
-/** Clean up device I/O. */
-static void ata_bd_fini_io(ata_ctrl_t *ctrl)
-{
-	(void) ctrl;
-	/* XXX TODO */
-}
-
-/** Initialize a disk.
- *
- * Probes for a disk, determines its parameters and initializes
- * the disk structure.
- */
-static errno_t disk_init(ata_ctrl_t *ctrl, disk_t *d, int disk_id)
-{
-	identify_data_t idata;
-	uint8_t model[40];
-	scsi_std_inquiry_data_t inq_data;
-	size_t isize;
-	uint16_t w;
-	uint8_t c;
-	uint16_t bc;
-	uint64_t nblocks;
-	size_t block_size;
-	size_t pos, len;
-	errno_t rc;
-	unsigned i;
-
-	d->ctrl = ctrl;
-	d->disk_id = disk_id;
-	d->present = false;
-	d->afun = NULL;
-
-	/* Try identify command. */
-	rc = ata_identify_dev(d, &idata);
-	if (rc == EOK) {
-		/* Success. It's a register (non-packet) device. */
-		ddf_msg(LVL_DEBUG, "ATA register-only device found.");
-		d->dev_type = ata_reg_dev;
-	} else if (rc == EIO) {
-		/*
-		 * There is something, but not a register device. Check to see
-		 * whether the IDENTIFY command left the packet signature in
-		 * the registers in case this is a packet device.
-		 *
-		 * According to the ATA specification, the LBA low and
-		 * interrupt reason registers should be set to 0x01. However,
-		 * there are many devices that do not follow this and only set
-		 * the byte count registers. So, only check these.
-		 */
-		bc = ((uint16_t)pio_read_8(&ctrl->cmd->cylinder_high) << 8) |
-		    pio_read_8(&ctrl->cmd->cylinder_low);
-
-		if (bc == PDEV_SIGNATURE_BC) {
-			rc = ata_identify_pkt_dev(d, &idata);
-			if (rc == EOK) {
-				/* We have a packet device. */
-				d->dev_type = ata_pkt_dev;
-			} else {
-				return EIO;
-			}
-		} else {
-			/* Nope. Something's there, but not recognized. */
-			return EIO;
-		}
-	} else {
-		/* Operation timed out. That means there is no device there. */
-		return EIO;
-	}
-
-	if (d->dev_type == ata_pkt_dev) {
-		/* Packet device */
-		d->amode = 0;
-
-		d->geom.cylinders = 0;
-		d->geom.heads = 0;
-		d->geom.sectors = 0;
-
-		d->blocks = 0;
-	} else if ((idata.caps & rd_cap_lba) == 0) {
-		/* Device only supports CHS addressing. */
-		d->amode = am_chs;
-
-		d->geom.cylinders = idata.cylinders;
-		d->geom.heads = idata.heads;
-		d->geom.sectors = idata.sectors;
-
-		d->blocks = d->geom.cylinders * d->geom.heads * d->geom.sectors;
-	} else if ((idata.cmd_set1 & cs1_addr48) == 0) {
-		/* Device only supports LBA-28 addressing. */
-		d->amode = am_lba28;
-
-		d->geom.cylinders = 0;
-		d->geom.heads = 0;
-		d->geom.sectors = 0;
-
-		d->blocks =
-		    (uint32_t) idata.total_lba28_0 |
-		    ((uint32_t) idata.total_lba28_1 << 16);
-	} else {
-		/* Device supports LBA-48 addressing. */
-		d->amode = am_lba48;
-
-		d->geom.cylinders = 0;
-		d->geom.heads = 0;
-		d->geom.sectors = 0;
-
-		d->blocks =
-		    (uint64_t) idata.total_lba48_0 |
-		    ((uint64_t) idata.total_lba48_1 << 16) |
-		    ((uint64_t) idata.total_lba48_2 << 32) |
-		    ((uint64_t) idata.total_lba48_3 << 48);
-	}
-
-	/*
-	 * Convert model name to string representation.
-	 */
-	for (i = 0; i < 20; i++) {
-		w = idata.model_name[i];
-		model[2 * i] = w >> 8;
-		model[2 * i + 1] = w & 0x00ff;
-	}
-
-	len = 40;
-	while (len > 0 && model[len - 1] == 0x20)
-		--len;
-
-	pos = 0;
-	for (i = 0; i < len; ++i) {
-		c = model[i];
-		if (c >= 0x80)
-			c = '?';
-
-		chr_encode(c, d->model, &pos, 40);
-	}
-	d->model[pos] = '\0';
-
-	if (d->dev_type == ata_pkt_dev) {
-		/* Send inquiry. */
-		rc = ata_pcmd_inquiry(d, &inq_data, sizeof(inq_data), &isize);
-		if (rc != EOK || isize < sizeof(inq_data)) {
-			ddf_msg(LVL_ERROR, "Device inquiry failed.");
-			d->present = false;
-			return EIO;
-		}
-
-		/* Check device type. */
-		if (INQUIRY_PDEV_TYPE(inq_data.pqual_devtype) != SCSI_DEV_CD_DVD)
-			ddf_msg(LVL_WARN, "Peripheral device type is not CD-ROM.");
-
-		rc = ata_pcmd_read_capacity(d, &nblocks, &block_size);
-		if (rc != EOK) {
-			ddf_msg(LVL_ERROR, "Read capacity command failed.");
-			d->present = false;
-			return EIO;
-		}
-
-		d->blocks = nblocks;
-		d->block_size = block_size;
-	} else {
-		/* Assume register Read always uses 512-byte blocks. */
-		d->block_size = 512;
-	}
-
-	d->present = true;
-	return EOK;
-}
-
-static errno_t ata_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
-{
-	return EOK;
-}
-
-static errno_t ata_bd_close(bd_srv_t *bd)
-{
-	return EOK;
-}
-
-/** Read multiple blocks from the device. */
-static errno_t ata_bd_read_blocks(bd_srv_t *bd, uint64_t ba, size_t cnt,
-    void *buf, size_t size)
-{
-	disk_t *disk = bd_srv_disk(bd);
-	errno_t rc;
-
-	if (size < cnt * disk->block_size)
-		return EINVAL;
-
-	while (cnt > 0) {
-		if (disk->dev_type == ata_reg_dev) {
-			rc = ata_rcmd_read(disk, ba, 1, buf);
-		} else {
-			rc = ata_pcmd_read_12(disk, ba, 1, buf,
-			    disk->block_size);
-		}
-
-		if (rc != EOK)
-			return rc;
-
-		++ba;
-		--cnt;
-		buf += disk->block_size;
-	}
-
-	return EOK;
-}
-
-/** Read TOC from device. */
-static errno_t ata_bd_read_toc(bd_srv_t *bd, uint8_t session, void *buf, size_t size)
-{
-	disk_t *disk = bd_srv_disk(bd);
-
-	return ata_pcmd_read_toc(disk, session, buf, size);
-}
-
-/** Write multiple blocks to the device. */
-static errno_t ata_bd_write_blocks(bd_srv_t *bd, uint64_t ba, size_t cnt,
-    const void *buf, size_t size)
-{
-	disk_t *disk = bd_srv_disk(bd);
-	errno_t rc;
-
-	if (disk->dev_type != ata_reg_dev)
-		return ENOTSUP;
-
-	if (size < cnt * disk->block_size)
-		return EINVAL;
-
-	while (cnt > 0) {
-		rc = ata_rcmd_write(disk, ba, 1, buf);
-		if (rc != EOK)
-			return rc;
-
-		++ba;
-		--cnt;
-		buf += disk->block_size;
-	}
-
-	return EOK;
-}
-
-/** Get device block size. */
-static errno_t ata_bd_get_block_size(bd_srv_t *bd, size_t *rbsize)
-{
-	disk_t *disk = bd_srv_disk(bd);
-
-	*rbsize = disk->block_size;
-	return EOK;
-}
-
-/** Get device number of blocks. */
-static errno_t ata_bd_get_num_blocks(bd_srv_t *bd, aoff64_t *rnb)
-{
-	disk_t *disk = bd_srv_disk(bd);
-
-	*rnb = disk->blocks;
-	return EOK;
-}
-
-/** Flush cache. */
-static errno_t ata_bd_sync_cache(bd_srv_t *bd, uint64_t ba, size_t cnt)
-{
-	disk_t *disk = bd_srv_disk(bd);
-
-	/* ATA cannot flush just some blocks, we just flush everything. */
-	(void)ba;
-	(void)cnt;
-
-	return ata_rcmd_flush_cache(disk);
-}
-
-/** PIO data-in command protocol. */
-static errno_t ata_pio_data_in(disk_t *disk, void *obuf, size_t obuf_size,
-    size_t blk_size, size_t nblocks)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	uint16_t data;
-	size_t i;
-	uint8_t status;
-
-	/* XXX Support multiple blocks */
-	assert(nblocks == 1);
-	assert(blk_size % 2 == 0);
-
-	if (wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK)
-		return EIO;
-
-	if ((status & SR_DRQ) != 0) {
-		/* Read data from the device buffer. */
-
-		for (i = 0; i < blk_size / 2; i++) {
-			data = pio_read_16(&ctrl->cmd->data_port);
-			((uint16_t *) obuf)[i] = data;
-		}
-	}
-
-	if ((status & SR_ERR) != 0)
-		return EIO;
-
-	return EOK;
-}
-
-/** PIO data-out command protocol. */
-static errno_t ata_pio_data_out(disk_t *disk, const void *buf, size_t buf_size,
-    size_t blk_size, size_t nblocks)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	size_t i;
-	uint8_t status;
-
-	/* XXX Support multiple blocks */
-	assert(nblocks == 1);
-	assert(blk_size % 2 == 0);
-
-	if (wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK)
-		return EIO;
-
-	if ((status & SR_DRQ) != 0) {
-		/* Write data to the device buffer. */
-
-		for (i = 0; i < blk_size / 2; i++) {
-			pio_write_16(&ctrl->cmd->data_port, ((uint16_t *) buf)[i]);
-		}
-	}
-
-	if (status & SR_ERR)
-		return EIO;
-
-	return EOK;
-}
-
-/** PIO non-data command protocol. */
-static errno_t ata_pio_nondata(disk_t *disk)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	uint8_t status;
-
-	if (wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK)
-		return EIO;
-
-	if (status & SR_ERR)
-		return EIO;
-
-	return EOK;
-}
-
-/** Issue IDENTIFY DEVICE command.
- *
- * Reads @c identify data into the provided buffer. This is used to detect
- * whether an ATA device is present and if so, to determine its parameters.
- *
- * @param disk		Disk
- * @param buf		Pointer to a 512-byte buffer.
- *
- * @return		ETIMEOUT on timeout (this can mean the device is
- *			not present). EIO if device responds with error.
- */
-static errno_t ata_identify_dev(disk_t *disk, void *buf)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	uint8_t status;
-	uint8_t drv_head;
-
-	drv_head = ((disk_dev_idx(disk) != 0) ? DHR_DRV : 0);
-
-	if (wait_status(ctrl, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
-		return ETIMEOUT;
-
-	pio_write_8(&ctrl->cmd->drive_head, drv_head);
-
-	/*
-	 * Do not wait for DRDY to be set in case this is a packet device.
-	 * We determine whether the device is present by waiting for DRQ to be
-	 * set after issuing the command.
-	 */
-	if (wait_status(ctrl, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
-		return ETIMEOUT;
-
-	pio_write_8(&ctrl->cmd->command, CMD_IDENTIFY_DRIVE);
-
-	if (wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_PROBE) != EOK)
-		return ETIMEOUT;
-
-	/*
-	 * If ERR is set, this may be a packet device, so return EIO to cause
-	 * the caller to check for one.
-	 */
-	if ((status & SR_ERR) != 0)
-		return EIO;
-
-	/*
-	 * For probing purposes we need to wait for some status bit to become
-	 * active - otherwise we could be fooled just by receiving all zeroes.
-	 */
-	if (wait_status(ctrl, SR_DRQ, ~SR_BSY, &status, TIMEOUT_PROBE) != EOK)
-		return ETIMEOUT;
-
-	return ata_pio_data_in(disk, buf, identify_data_size,
-	    identify_data_size, 1);
-}
-
-/** Issue Identify Packet Device command.
- *
- * Reads @c identify data into the provided buffer. This is used to detect
- * whether an ATAPI device is present and if so, to determine its parameters.
- *
- * @param disk		Disk
- * @param buf		Pointer to a 512-byte buffer.
- */
-static errno_t ata_identify_pkt_dev(disk_t *disk, void *buf)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	uint8_t drv_head;
-
-	drv_head = ((disk_dev_idx(disk) != 0) ? DHR_DRV : 0);
-
-	if (wait_status(ctrl, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
-		return EIO;
-
-	pio_write_8(&ctrl->cmd->drive_head, drv_head);
-
-	/* For ATAPI commands we do not need to wait for DRDY. */
-	if (wait_status(ctrl, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
-		return EIO;
-
-	pio_write_8(&ctrl->cmd->command, CMD_IDENTIFY_PKT_DEV);
-
-	return ata_pio_data_in(disk, buf, identify_data_size,
-	    identify_data_size, 1);
-}
-
-/** Issue packet command (i. e. write a command packet to the device).
- *
- * Only data-in commands are supported (e.g. inquiry, read).
- *
- * @param disk		Disk
- * @param obuf		Buffer for storing data read from device
- * @param obuf_size	Size of obuf in bytes
- * @param rcvd_size	Place to store number of bytes read or @c NULL
- *
- * @return EOK on success, EIO on error.
- */
-static errno_t ata_cmd_packet(disk_t *disk, const void *cpkt, size_t cpkt_size,
-    void *obuf, size_t obuf_size, size_t *rcvd_size)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	size_t i;
-	uint8_t status;
-	uint8_t drv_head;
-	size_t data_size;
-	uint16_t val;
-
-	fibril_mutex_lock(&ctrl->lock);
-
-	/* New value for Drive/Head register */
-	drv_head =
-	    ((disk_dev_idx(disk) != 0) ? DHR_DRV : 0);
-
-	if (wait_status(ctrl, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	pio_write_8(&ctrl->cmd->drive_head, drv_head);
-
-	if (wait_status(ctrl, 0, ~(SR_BSY | SR_DRQ), NULL, TIMEOUT_BSY) != EOK) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	/* Byte count <- max. number of bytes we can read in one transfer. */
-	pio_write_8(&ctrl->cmd->cylinder_low, 0xfe);
-	pio_write_8(&ctrl->cmd->cylinder_high, 0xff);
-
-	pio_write_8(&ctrl->cmd->command, CMD_PACKET);
-
-	if (wait_status(ctrl, SR_DRQ, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	/* Write command packet. */
-	for (i = 0; i < (cpkt_size + 1) / 2; i++)
-		pio_write_16(&ctrl->cmd->data_port, ((uint16_t *) cpkt)[i]);
-
-	if (wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	if ((status & SR_DRQ) == 0) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	/* Read byte count. */
-	data_size = (uint16_t) pio_read_8(&ctrl->cmd->cylinder_low) +
-	    ((uint16_t) pio_read_8(&ctrl->cmd->cylinder_high) << 8);
-
-	/* Check whether data fits into output buffer. */
-	if (data_size > obuf_size) {
-		/* Output buffer is too small to store data. */
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	/* Read data from the device buffer. */
-	for (i = 0; i < (data_size + 1) / 2; i++) {
-		val = pio_read_16(&ctrl->cmd->data_port);
-		((uint16_t *) obuf)[i] = val;
-	}
-
-	fibril_mutex_unlock(&ctrl->lock);
-
-	if (status & SR_ERR)
-		return EIO;
-
-	if (rcvd_size != NULL)
-		*rcvd_size = data_size;
-	return EOK;
-}
-
-/** Issue ATAPI Inquiry.
- *
- * @param disk		Disk
- * @param obuf		Buffer for storing inquiry data read from device
- * @param obuf_size	Size of obuf in bytes
- *
- * @return EOK on success, EIO on error.
- */
-static errno_t ata_pcmd_inquiry(disk_t *disk, void *obuf, size_t obuf_size,
-    size_t *rcvd_size)
-{
-	uint8_t cpb[12];
-	scsi_cdb_inquiry_t *cp = (scsi_cdb_inquiry_t *)cpb;
-	errno_t rc;
-
-	memset(cpb, 0, sizeof(cpb));
-
-	/*
-	 * For SFF 8020 compliance the inquiry must be padded to 12 bytes
-	 * and allocation length must fit in one byte.
-	 */
-	cp->op_code = SCSI_CMD_INQUIRY;
-
-	/* Allocation length */
-	cp->alloc_len = host2uint16_t_be(min(obuf_size, 0xff));
-
-	rc = ata_cmd_packet(disk, cpb, sizeof(cpb), obuf, obuf_size, rcvd_size);
-	if (rc != EOK)
-		return rc;
-
-	return EOK;
-}
-
-/** Issue ATAPI read capacity(10) command.
- *
- * @param disk		Disk
- * @param nblocks	Place to store number of blocks
- * @param block_size	Place to store block size
- *
- * @return EOK on success, EIO on error.
- */
-static errno_t ata_pcmd_read_capacity(disk_t *disk, uint64_t *nblocks,
-    size_t *block_size)
-{
-	scsi_cdb_read_capacity_10_t cdb;
-	scsi_read_capacity_10_data_t data;
-	size_t rsize;
-	errno_t rc;
-
-	memset(&cdb, 0, sizeof(cdb));
-	cdb.op_code = SCSI_CMD_READ_CAPACITY_10;
-
-	rc = ata_cmd_packet(disk, &cdb, sizeof(cdb), &data, sizeof(data), &rsize);
-	if (rc != EOK)
-		return rc;
-
-	if (rsize != sizeof(data))
-		return EIO;
-
-	*nblocks = uint32_t_be2host(data.last_lba) + 1;
-	*block_size = uint32_t_be2host(data.block_size);
-
-	return EOK;
-}
-
-/** Issue ATAPI read(12) command.
- *
- * Output buffer must be large enough to hold the data, otherwise the
- * function will fail.
- *
- * @param disk		Disk
- * @param ba		Starting block address
- * @param cnt		Number of blocks to read
- * @param obuf		Buffer for storing inquiry data read from device
- * @param obuf_size	Size of obuf in bytes
- *
- * @return EOK on success, EIO on error.
- */
-static errno_t ata_pcmd_read_12(disk_t *disk, uint64_t ba, size_t cnt,
-    void *obuf, size_t obuf_size)
-{
-	scsi_cdb_read_12_t cp;
-	errno_t rc;
-
-	if (ba > UINT32_MAX)
-		return EINVAL;
-
-	memset(&cp, 0, sizeof(cp));
-
-	cp.op_code = SCSI_CMD_READ_12;
-	cp.lba = host2uint32_t_be(ba);
-	cp.xfer_len = host2uint32_t_be(cnt);
-
-	rc = ata_cmd_packet(disk, &cp, sizeof(cp), obuf, obuf_size, NULL);
-	if (rc != EOK)
-		return rc;
-
-	return EOK;
-}
-
-/** Issue ATAPI read TOC command.
- *
- * Read TOC in 'multi-session' format (first and last session number
- * with last session LBA).
- *
- * http://suif.stanford.edu/~csapuntz/specs/INF-8020.PDF page 171
- *
- * Output buffer must be large enough to hold the data, otherwise the
- * function will fail.
- *
- * @param disk		Disk
- * @param session	Starting session
- * @param obuf		Buffer for storing inquiry data read from device
- * @param obuf_size	Size of obuf in bytes
- *
- * @return EOK on success, EIO on error.
- */
-static errno_t ata_pcmd_read_toc(disk_t *disk, uint8_t session, void *obuf,
-    size_t obuf_size)
-{
-	uint8_t cpb[12];
-	scsi_cdb_read_toc_t *cp = (scsi_cdb_read_toc_t *)cpb;
-	errno_t rc;
-
-	memset(cpb, 0, sizeof(cpb));
-
-	cp->op_code = SCSI_CMD_READ_TOC;
-	cp->msf = 0;
-	cp->format = 0x01; /* 0x01 = multi-session mode */
-	cp->track_sess_no = session;
-	cp->alloc_len = host2uint16_t_be(obuf_size);
-	cp->control = 0x40; /* 0x01 = multi-session mode (shifted to MSB) */
-
-	rc = ata_cmd_packet(disk, cpb, sizeof(cpb), obuf, obuf_size, NULL);
-	if (rc != EOK)
-		return rc;
-
-	return EOK;
-}
-
-/** Read a physical block from the device.
- *
- * @param disk		Disk
- * @param ba		Address the first block.
- * @param cnt		Number of blocks to transfer.
- * @param buf		Buffer for holding the data.
- *
- * @return EOK on success, EIO on error.
- */
-static errno_t ata_rcmd_read(disk_t *disk, uint64_t ba, size_t blk_cnt,
-    void *buf)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	uint8_t drv_head;
-	block_coord_t bc;
-	errno_t rc;
-
-	/* Silence warning. */
-	memset(&bc, 0, sizeof(bc));
-
-	/* Compute block coordinates. */
-	if (coord_calc(disk, ba, &bc) != EOK)
-		return EINVAL;
-
-	/* New value for Drive/Head register */
-	drv_head =
-	    ((disk_dev_idx(disk) != 0) ? DHR_DRV : 0) |
-	    ((disk->amode != am_chs) ? DHR_LBA : 0) |
-	    (bc.h & 0x0f);
-
-	fibril_mutex_lock(&ctrl->lock);
-
-	/* Program a Read Sectors operation. */
-
-	if (wait_status(ctrl, 0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	pio_write_8(&ctrl->cmd->drive_head, drv_head);
-
-	if (wait_status(ctrl, SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	/* Program block coordinates into the device. */
-	coord_sc_program(ctrl, &bc, 1);
-
-	pio_write_8(&ctrl->cmd->command, disk->amode == am_lba48 ?
-	    CMD_READ_SECTORS_EXT : CMD_READ_SECTORS);
-
-	rc = ata_pio_data_in(disk, buf, blk_cnt * disk->block_size,
-	    disk->block_size, blk_cnt);
-
-	fibril_mutex_unlock(&ctrl->lock);
-
-	return rc;
-}
-
-/** Write a physical block to the device.
- *
- * @param disk		Disk
- * @param ba		Address of the first block.
- * @param cnt		Number of blocks to transfer.
- * @param buf		Buffer holding the data to write.
- *
- * @return EOK on success, EIO on error.
- */
-static errno_t ata_rcmd_write(disk_t *disk, uint64_t ba, size_t cnt,
-    const void *buf)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	uint8_t drv_head;
-	block_coord_t bc;
-	errno_t rc;
-
-	/* Silence warning. */
-	memset(&bc, 0, sizeof(bc));
-
-	/* Compute block coordinates. */
-	if (coord_calc(disk, ba, &bc) != EOK)
-		return EINVAL;
-
-	/* New value for Drive/Head register */
-	drv_head =
-	    ((disk_dev_idx(disk) != 0) ? DHR_DRV : 0) |
-	    ((disk->amode != am_chs) ? DHR_LBA : 0) |
-	    (bc.h & 0x0f);
-
-	fibril_mutex_lock(&ctrl->lock);
-
-	/* Program a Write Sectors operation. */
-
-	if (wait_status(ctrl, 0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	pio_write_8(&ctrl->cmd->drive_head, drv_head);
-
-	if (wait_status(ctrl, SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	/* Program block coordinates into the device. */
-	coord_sc_program(ctrl, &bc, 1);
-
-	pio_write_8(&ctrl->cmd->command, disk->amode == am_lba48 ?
-	    CMD_WRITE_SECTORS_EXT : CMD_WRITE_SECTORS);
-
-	rc = ata_pio_data_out(disk, buf, cnt * disk->block_size,
-	    disk->block_size, cnt);
-
-	fibril_mutex_unlock(&ctrl->lock);
-	return rc;
-}
-
-/** Flush cached data to nonvolatile storage.
- *
- * @param disk		Disk
- *
- * @return EOK on success, EIO on error.
- */
-static errno_t ata_rcmd_flush_cache(disk_t *disk)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	uint8_t drv_head;
-	errno_t rc;
-
-	/* New value for Drive/Head register */
-	drv_head =
-	    (disk_dev_idx(disk) != 0) ? DHR_DRV : 0;
-
-	fibril_mutex_lock(&ctrl->lock);
-
-	/* Program a Flush Cache operation. */
-
-	if (wait_status(ctrl, 0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	pio_write_8(&ctrl->cmd->drive_head, drv_head);
-
-	if (wait_status(ctrl, SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	pio_write_8(&ctrl->cmd->command, CMD_FLUSH_CACHE);
-
-	rc = ata_pio_nondata(disk);
-
-	fibril_mutex_unlock(&ctrl->lock);
-	return rc;
-}
-
-/** Calculate block coordinates.
- *
- * Calculates block coordinates in the best coordinate system supported
- * by the device. These can be later programmed into the device using
- * @c coord_sc_program().
- *
- * @return EOK on success or EINVAL if block index is past end of device.
- */
-static errno_t coord_calc(disk_t *d, uint64_t ba, block_coord_t *bc)
-{
-	uint64_t c;
-	uint64_t idx;
-
-	/* Check device bounds. */
-	if (ba >= d->blocks)
-		return EINVAL;
-
-	bc->amode = d->amode;
-
-	switch (d->amode) {
-	case am_chs:
-		/* Compute CHS coordinates. */
-		c = ba / (d->geom.heads * d->geom.sectors);
-		idx = ba % (d->geom.heads * d->geom.sectors);
-
-		bc->cyl_lo = c & 0xff;
-		bc->cyl_hi = (c >> 8) & 0xff;
-		bc->h      = (idx / d->geom.sectors) & 0x0f;
-		bc->sector = (1 + (idx % d->geom.sectors)) & 0xff;
-		break;
-
-	case am_lba28:
-		/* Compute LBA-28 coordinates. */
-		bc->c0 = ba & 0xff;		/* bits 0-7 */
-		bc->c1 = (ba >> 8) & 0xff;	/* bits 8-15 */
-		bc->c2 = (ba >> 16) & 0xff;	/* bits 16-23 */
-		bc->h  = (ba >> 24) & 0x0f;	/* bits 24-27 */
-		break;
-
-	case am_lba48:
-		/* Compute LBA-48 coordinates. */
-		bc->c0 = ba & 0xff;		/* bits 0-7 */
-		bc->c1 = (ba >> 8) & 0xff;	/* bits 8-15 */
-		bc->c2 = (ba >> 16) & 0xff;	/* bits 16-23 */
-		bc->c3 = (ba >> 24) & 0xff;	/* bits 24-31 */
-		bc->c4 = (ba >> 32) & 0xff;	/* bits 32-39 */
-		bc->c5 = (ba >> 40) & 0xff;	/* bits 40-47 */
-		bc->h  = 0;
-		break;
-	}
-
-	return EOK;
-}
-
-/** Program block coordinates and sector count into ATA registers.
- *
- * Note that bc->h must be programmed separately into the device/head register.
- *
- * @param ctrl		Controller
- * @param bc		Block coordinates
- * @param scnt		Sector count
- */
-static void coord_sc_program(ata_ctrl_t *ctrl, const block_coord_t *bc,
-    uint16_t scnt)
-{
-	ata_cmd_t *cmd = ctrl->cmd;
-
-	if (bc->amode == am_lba48) {
-		/* Write high-order bits. */
-		pio_write_8(&cmd->sector_count, scnt >> 8);
-		pio_write_8(&cmd->sector_number, bc->c3);
-		pio_write_8(&cmd->cylinder_low, bc->c4);
-		pio_write_8(&cmd->cylinder_high, bc->c5);
-	}
-
-	/* Write low-order bits. */
-	pio_write_8(&cmd->sector_count, scnt & 0x00ff);
-	pio_write_8(&cmd->sector_number, bc->c0);
-	pio_write_8(&cmd->cylinder_low, bc->c1);
-	pio_write_8(&cmd->cylinder_high, bc->c2);
-}
-
-/** Wait until some status bits are set and some are reset.
- *
- * Example: wait_status(ctrl, SR_DRDY, ~SR_BSY, ...) waits for SR_DRDY to become
- * set and SR_BSY to become reset.
- *
- * @param ctrl		Controller
- * @param set		Combination if bits which must be all set.
- * @param n_reset	Negated combination of bits which must be all reset.
- * @param pstatus	Pointer where to store last read status or NULL.
- * @param timeout	Timeout in 10ms units.
- *
- * @return		EOK on success, EIO on timeout.
- */
-static errno_t wait_status(ata_ctrl_t *ctrl, unsigned set, unsigned n_reset,
-    uint8_t *pstatus, unsigned timeout)
-{
-	uint8_t status;
-	int cnt;
-
-	status = pio_read_8(&ctrl->cmd->status);
-
-	/*
-	 * This is crude, yet simple. First try with 1us delays
-	 * (most likely the device will respond very fast). If not,
-	 * start trying every 10 ms.
-	 */
-
-	cnt = 100;
-	while ((status & ~n_reset) != 0 || (status & set) != set) {
-		--cnt;
-		if (cnt <= 0)
-			break;
-
-		status = pio_read_8(&ctrl->cmd->status);
-	}
-
-	cnt = timeout;
-	while ((status & ~n_reset) != 0 || (status & set) != set) {
-		fibril_usleep(10000);
-		--cnt;
-		if (cnt <= 0)
-			break;
-
-		status = pio_read_8(&ctrl->cmd->status);
-	}
-
-	if (pstatus)
-		*pstatus = status;
-
-	if (cnt == 0)
-		return EIO;
-
-	return EOK;
-}
-
-/**
- * @}
- */
Index: pace/drv/block/ata_bd/ata_bd.h
===================================================================
--- uspace/drv/block/ata_bd/ata_bd.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,164 +1,0 @@
-/*
- * Copyright (c) 2009 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 ata_bd
- * @{
- */
-/** @file ATA driver definitions.
- */
-
-#ifndef __ATA_BD_H__
-#define __ATA_BD_H__
-
-#include <async.h>
-#include <bd_srv.h>
-#include <ddf/driver.h>
-#include <fibril_synch.h>
-#include <str.h>
-#include <stdint.h>
-#include <stddef.h>
-#include "ata_hw.h"
-
-#define NAME "ata_bd"
-
-/** Base addresses for ATA I/O blocks. */
-typedef struct {
-	uintptr_t cmd;	/**< Command block base address. */
-	uintptr_t ctl;	/**< Control block base address. */
-} ata_base_t;
-
-/** Timeout definitions. Unit is 10 ms. */
-enum ata_timeout {
-	TIMEOUT_PROBE	=  100, /*  1 s */
-	TIMEOUT_BSY	=  100, /*  1 s */
-	TIMEOUT_DRDY	= 1000  /* 10 s */
-};
-
-enum ata_dev_type {
-	ata_reg_dev,	/* Register device (no packet feature set support) */
-	ata_pkt_dev	/* Packet device (supports packet feature set). */
-};
-
-/** Register device block addressing mode. */
-enum rd_addr_mode {
-	am_chs,		/**< CHS block addressing */
-	am_lba28,	/**< LBA-28 block addressing */
-	am_lba48	/**< LBA-48 block addressing */
-};
-
-/** Block coordinates */
-typedef struct {
-	enum rd_addr_mode amode;
-
-	union {
-		/** CHS coordinates */
-		struct {
-			uint8_t sector;
-			uint8_t cyl_lo;
-			uint8_t cyl_hi;
-		};
-		/** LBA coordinates */
-		struct {
-			uint8_t c0;
-			uint8_t c1;
-			uint8_t c2;
-			uint8_t c3;
-			uint8_t c4;
-			uint8_t c5;
-		};
-	};
-
-	/** Lower 4 bits for device/head register */
-	uint8_t h;
-} block_coord_t;
-
-/** ATA device state structure. */
-typedef struct {
-	bool present;
-	struct ata_ctrl *ctrl;
-	struct ata_fun *afun;
-
-	/** Device type */
-	enum ata_dev_type dev_type;
-
-	/** Addressing mode to use (if register device) */
-	enum rd_addr_mode amode;
-
-	/*
-	 * Geometry. Only valid if operating in CHS mode.
-	 */
-	struct {
-		unsigned heads;
-		unsigned cylinders;
-		unsigned sectors;
-	} geom;
-
-	uint64_t blocks;
-	size_t block_size;
-
-	char model[STR_BOUNDS(40) + 1];
-
-	int disk_id;
-} disk_t;
-
-/** ATA controller */
-typedef struct ata_ctrl {
-	/** DDF device */
-	ddf_dev_t *dev;
-	/** I/O base address of the command registers */
-	uintptr_t cmd_physical;
-	/** I/O base address of the control registers */
-	uintptr_t ctl_physical;
-
-	/** Command registers */
-	ata_cmd_t *cmd;
-	/** Control registers */
-	ata_ctl_t *ctl;
-
-	/** Per-disk state. */
-	disk_t disk[MAX_DISKS];
-
-	fibril_mutex_t lock;
-} ata_ctrl_t;
-
-typedef struct ata_fun {
-	ddf_fun_t *fun;
-	disk_t *disk;
-	bd_srvs_t bds;
-} ata_fun_t;
-
-extern errno_t ata_ctrl_init(ata_ctrl_t *, ata_base_t *);
-extern errno_t ata_ctrl_remove(ata_ctrl_t *);
-extern errno_t ata_ctrl_gone(ata_ctrl_t *);
-
-extern bd_ops_t ata_bd_ops;
-
-#endif
-
-/** @}
- */
Index: pace/drv/block/ata_bd/ata_bd.ma
===================================================================
--- uspace/drv/block/ata_bd/ata_bd.ma	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,1 +1,0 @@
-10 isa/ata_bd
Index: pace/drv/block/ata_bd/ata_hw.h
===================================================================
--- uspace/drv/block/ata_bd/ata_hw.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,258 +1,0 @@
-/*
- * Copyright (c) 2009 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 ata_bd
- * @{
- */
-/** @file ATA hardware protocol (registers, data structures).
- */
-
-#ifndef __ATA_HW_H__
-#define __ATA_HW_H__
-
-#include <stdint.h>
-
-enum {
-	CTL_READ_START  = 0,
-	CTL_WRITE_START = 1,
-};
-
-enum {
-	STATUS_FAILURE = 0
-};
-
-enum {
-	MAX_DISKS	= 2
-};
-
-/** ATA Command Register Block. */
-typedef union {
-	/* Read/Write */
-	struct {
-		uint16_t data_port;
-		uint8_t sector_count;
-		uint8_t sector_number;
-		uint8_t cylinder_low;
-		uint8_t cylinder_high;
-		uint8_t drive_head;
-		uint8_t pad_rw0;
-	};
-
-	/* Read Only */
-	struct {
-		uint8_t pad_ro0;
-		uint8_t error;
-		uint8_t pad_ro1[5];
-		uint8_t status;
-	};
-
-	/* Write Only */
-	struct {
-		uint8_t pad_wo0;
-		uint8_t features;
-		uint8_t pad_wo1[5];
-		uint8_t command;
-	};
-} ata_cmd_t;
-
-typedef union {
-	/* Read */
-	struct {
-		uint8_t pad0[6];
-		uint8_t alt_status;
-		uint8_t drive_address;
-	};
-
-	/* Write */
-	struct {
-		uint8_t pad1[6];
-		uint8_t device_control;
-		uint8_t pad2;
-	};
-} ata_ctl_t;
-
-enum devctl_bits {
-	DCR_SRST	= 0x04, /**< Software Reset */
-	DCR_nIEN	= 0x02  /**< Interrupt Enable (negated) */
-};
-
-enum status_bits {
-	SR_BSY		= 0x80, /**< Busy */
-	SR_DRDY		= 0x40, /**< Drive Ready */
-	SR_DWF		= 0x20, /**< Drive Write Fault */
-	SR_DSC		= 0x10, /**< Drive Seek Complete */
-	SR_DRQ		= 0x08, /**< Data Request */
-	SR_CORR		= 0x04, /**< Corrected Data */
-	SR_IDX		= 0x02, /**< Index */
-	SR_ERR		= 0x01  /**< Error */
-};
-
-enum drive_head_bits {
-	DHR_LBA		= 0x40,	/**< Use LBA addressing mode */
-	DHR_DRV		= 0x10	/**< Select device 1 */
-};
-
-enum error_bits {
-	ER_BBK		= 0x80, /**< Bad Block Detected */
-	ER_UNC		= 0x40, /**< Uncorrectable Data Error */
-	ER_MC		= 0x20, /**< Media Changed */
-	ER_IDNF		= 0x10, /**< ID Not Found */
-	ER_MCR		= 0x08, /**< Media Change Request */
-	ER_ABRT		= 0x04, /**< Aborted Command */
-	ER_TK0NF	= 0x02, /**< Track 0 Not Found */
-	ER_AMNF		= 0x01  /**< Address Mark Not Found */
-};
-
-enum ata_command {
-	CMD_READ_SECTORS	= 0x20,
-	CMD_READ_SECTORS_EXT	= 0x24,
-	CMD_WRITE_SECTORS	= 0x30,
-	CMD_WRITE_SECTORS_EXT	= 0x34,
-	CMD_PACKET		= 0xA0,
-	CMD_IDENTIFY_PKT_DEV	= 0xA1,
-	CMD_IDENTIFY_DRIVE	= 0xEC,
-	CMD_FLUSH_CACHE		= 0xE7
-};
-
-/** Data returned from identify device and identify packet device command. */
-typedef struct {
-	uint16_t gen_conf;
-	uint16_t cylinders;
-	uint16_t _res2;
-	uint16_t heads;
-	uint16_t _vs4;
-	uint16_t _vs5;
-	uint16_t sectors;
-	uint16_t _vs7;
-	uint16_t _vs8;
-	uint16_t _vs9;
-
-	uint16_t serial_number[10];
-	uint16_t _vs20;
-	uint16_t _vs21;
-	uint16_t vs_bytes;
-	uint16_t firmware_rev[4];
-	uint16_t model_name[20];
-
-	uint16_t max_rw_multiple;
-	uint16_t _res48;
-	uint16_t caps;		/* Different meaning for packet device */
-	uint16_t _res50;
-	uint16_t pio_timing;
-	uint16_t dma_timing;
-
-	uint16_t validity;
-	uint16_t cur_cyl;
-	uint16_t cur_heads;
-	uint16_t cur_sectors;
-	uint16_t cur_capacity0;
-	uint16_t cur_capacity1;
-	uint16_t mss;
-	uint16_t total_lba28_0;
-	uint16_t total_lba28_1;
-	uint16_t sw_dma;
-	uint16_t mw_dma;
-	uint16_t pio_modes;
-	uint16_t min_mw_dma_cycle;
-	uint16_t rec_mw_dma_cycle;
-	uint16_t min_raw_pio_cycle;
-	uint16_t min_iordy_pio_cycle;
-
-	uint16_t _res69;
-	uint16_t _res70;
-	uint16_t _res71;
-	uint16_t _res72;
-	uint16_t _res73;
-	uint16_t _res74;
-
-	uint16_t queue_depth;
-	uint16_t _res76[1 + 79 - 76];
-	uint16_t version_maj;
-	uint16_t version_min;
-	uint16_t cmd_set0;
-	uint16_t cmd_set1;
-	uint16_t csf_sup_ext;
-	uint16_t csf_enabled0;
-	uint16_t csf_enabled1;
-	uint16_t csf_default;
-	uint16_t udma;
-
-	uint16_t _res89[1 + 99 - 89];
-
-	/* Total number of blocks in LBA-48 addressing */
-	uint16_t total_lba48_0;
-	uint16_t total_lba48_1;
-	uint16_t total_lba48_2;
-	uint16_t total_lba48_3;
-
-	/* Note: more fields are defined in ATA/ATAPI-7 */
-	uint16_t _res104[1 + 127 - 104];
-	uint16_t _vs128[1 + 159 - 128];
-	uint16_t _res160[1 + 255 - 160];
-} identify_data_t;
-
-/** Capability bits for register device. */
-enum ata_regdev_caps {
-	rd_cap_iordy		= 0x0800,
-	rd_cap_iordy_cbd	= 0x0400,
-	rd_cap_lba		= 0x0200,
-	rd_cap_dma		= 0x0100
-};
-
-/** Capability bits for packet device. */
-enum ata_pktdev_caps {
-	pd_cap_ildma		= 0x8000,
-	pd_cap_cmdqueue		= 0x4000,
-	pd_cap_overlap		= 0x2000,
-	pd_cap_need_softreset	= 0x1000,	/* Obsolete (ATAPI-6) */
-	pd_cap_iordy		= 0x0800,
-	pd_cap_iordy_dis	= 0x0400,
-	pd_cap_lba		= 0x0200,	/* Must be on */
-	pd_cap_dma		= 0x0100
-};
-
-/** Bits of @c identify_data_t.cmd_set1 */
-enum ata_cs1 {
-	cs1_addr48	= 0x0400	/**< 48-bit address feature set */
-};
-
-/** Extract value of device type from scsi_std_inquiry_data_t.pqual_devtype */
-#define INQUIRY_PDEV_TYPE(val) ((val) & 0x1f)
-
-enum ata_pdev_signature {
-	/**
-	 * Signature put by a packet device in byte count register
-	 * in response to Identify command.
-	 */
-	PDEV_SIGNATURE_BC	= 0xEB14
-};
-
-#endif
-
-/** @}
- */
Index: pace/drv/block/ata_bd/doc/doxygroups.h
===================================================================
--- uspace/drv/block/ata_bd/doc/doxygroups.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,4 +1,0 @@
-/** @addtogroup ata_bd ata_bd
- * @brief ATA disk driver
- * @ingroup drvs
- */
Index: pace/drv/block/ata_bd/main.c
===================================================================
--- uspace/drv/block/ata_bd/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,349 +1,0 @@
-/*
- * Copyright (c) 2013 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 ata_bd
- * @{
- */
-
-/** @file
- */
-
-#include <assert.h>
-#include <stdio.h>
-#include <errno.h>
-#include <str_error.h>
-#include <ddf/driver.h>
-#include <ddf/log.h>
-#include <device/hw_res_parsed.h>
-
-#include "ata_bd.h"
-#include "main.h"
-
-static errno_t ata_dev_add(ddf_dev_t *dev);
-static errno_t ata_dev_remove(ddf_dev_t *dev);
-static errno_t ata_dev_gone(ddf_dev_t *dev);
-static errno_t ata_fun_online(ddf_fun_t *fun);
-static errno_t ata_fun_offline(ddf_fun_t *fun);
-
-static void ata_bd_connection(ipc_call_t *, void *);
-
-static driver_ops_t driver_ops = {
-	.dev_add = &ata_dev_add,
-	.dev_remove = &ata_dev_remove,
-	.dev_gone = &ata_dev_gone,
-	.fun_online = &ata_fun_online,
-	.fun_offline = &ata_fun_offline
-};
-
-static driver_t ata_driver = {
-	.name = NAME,
-	.driver_ops = &driver_ops
-};
-
-static errno_t ata_get_res(ddf_dev_t *dev, ata_base_t *ata_res)
-{
-	async_sess_t *parent_sess;
-	hw_res_list_parsed_t hw_res;
-	errno_t rc;
-
-	parent_sess = ddf_dev_parent_sess_get(dev);
-	if (parent_sess == NULL)
-		return ENOMEM;
-
-	hw_res_list_parsed_init(&hw_res);
-	rc = hw_res_get_list_parsed(parent_sess, &hw_res, 0);
-	if (rc != EOK)
-		return rc;
-
-	if (hw_res.io_ranges.count != 2) {
-		rc = EINVAL;
-		goto error;
-	}
-
-	addr_range_t *cmd_rng = &hw_res.io_ranges.ranges[0];
-	addr_range_t *ctl_rng = &hw_res.io_ranges.ranges[1];
-	ata_res->cmd = RNGABS(*cmd_rng);
-	ata_res->ctl = RNGABS(*ctl_rng);
-
-	if (RNGSZ(*ctl_rng) < sizeof(ata_ctl_t)) {
-		rc = EINVAL;
-		goto error;
-	}
-
-	if (RNGSZ(*cmd_rng) < sizeof(ata_cmd_t)) {
-		rc = EINVAL;
-		goto error;
-	}
-
-	return EOK;
-error:
-	hw_res_list_parsed_clean(&hw_res);
-	return rc;
-}
-
-/** Add new device
- *
- * @param  dev New device
- * @return     EOK on success or an error code.
- */
-static errno_t ata_dev_add(ddf_dev_t *dev)
-{
-	ata_ctrl_t *ctrl;
-	ata_base_t res;
-	errno_t rc;
-
-	rc = ata_get_res(dev, &res);
-	if (rc != EOK) {
-		ddf_msg(LVL_ERROR, "Invalid HW resource configuration.");
-		return EINVAL;
-	}
-
-	ctrl = ddf_dev_data_alloc(dev, sizeof(ata_ctrl_t));
-	if (ctrl == NULL) {
-		ddf_msg(LVL_ERROR, "Failed allocating soft state.");
-		rc = ENOMEM;
-		goto error;
-	}
-
-	ctrl->dev = dev;
-
-	rc = ata_ctrl_init(ctrl, &res);
-	if (rc == ENOENT)
-		goto error;
-
-	if (rc != EOK) {
-		ddf_msg(LVL_ERROR, "Failed initializing ATA controller.");
-		rc = EIO;
-		goto error;
-	}
-
-	return EOK;
-error:
-	return rc;
-}
-
-static char *ata_fun_name(disk_t *disk)
-{
-	char *fun_name;
-
-	if (asprintf(&fun_name, "d%u", disk->disk_id) < 0)
-		return NULL;
-
-	return fun_name;
-}
-
-errno_t ata_fun_create(disk_t *disk)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	errno_t rc;
-	char *fun_name = NULL;
-	ddf_fun_t *fun = NULL;
-	ata_fun_t *afun = NULL;
-	bool bound = false;
-
-	fun_name = ata_fun_name(disk);
-	if (fun_name == NULL) {
-		ddf_msg(LVL_ERROR, "Out of memory.");
-		rc = ENOMEM;
-		goto error;
-	}
-
-	fun = ddf_fun_create(ctrl->dev, fun_exposed, fun_name);
-	if (fun == NULL) {
-		ddf_msg(LVL_ERROR, "Failed creating DDF function.");
-		rc = ENOMEM;
-		goto error;
-	}
-
-	/* Allocate soft state */
-	afun = ddf_fun_data_alloc(fun, sizeof(ata_fun_t));
-	if (afun == NULL) {
-		ddf_msg(LVL_ERROR, "Failed allocating softstate.");
-		rc = ENOMEM;
-		goto error;
-	}
-
-	afun->fun = fun;
-	afun->disk = disk;
-
-	bd_srvs_init(&afun->bds);
-	afun->bds.ops = &ata_bd_ops;
-	afun->bds.sarg = disk;
-
-	/* Set up a connection handler. */
-	ddf_fun_set_conn_handler(fun, ata_bd_connection);
-
-	rc = ddf_fun_bind(fun);
-	if (rc != EOK) {
-		ddf_msg(LVL_ERROR, "Failed binding DDF function %s: %s",
-		    fun_name, str_error(rc));
-		goto error;
-	}
-
-	bound = true;
-
-	rc = ddf_fun_add_to_category(fun, "disk");
-	if (rc != EOK) {
-		ddf_msg(LVL_ERROR, "Failed adding function %s to "
-		    "category 'disk': %s", fun_name, str_error(rc));
-		goto error;
-	}
-
-	free(fun_name);
-	disk->afun = afun;
-	return EOK;
-error:
-	if (bound)
-		ddf_fun_unbind(fun);
-	if (fun != NULL)
-		ddf_fun_destroy(fun);
-	if (fun_name != NULL)
-		free(fun_name);
-
-	return rc;
-}
-
-errno_t ata_fun_remove(disk_t *disk)
-{
-	errno_t rc;
-	char *fun_name;
-
-	if (disk->afun == NULL)
-		return EOK;
-
-	fun_name = ata_fun_name(disk);
-	if (fun_name == NULL) {
-		ddf_msg(LVL_ERROR, "Out of memory.");
-		rc = ENOMEM;
-		goto error;
-	}
-
-	ddf_msg(LVL_DEBUG, "ata_fun_remove(%p, '%s')", disk, fun_name);
-	rc = ddf_fun_offline(disk->afun->fun);
-	if (rc != EOK) {
-		ddf_msg(LVL_ERROR, "Error offlining function '%s'.", fun_name);
-		goto error;
-	}
-
-	rc = ddf_fun_unbind(disk->afun->fun);
-	if (rc != EOK) {
-		ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.", fun_name);
-		goto error;
-	}
-
-	ddf_fun_destroy(disk->afun->fun);
-	disk->afun = NULL;
-	free(fun_name);
-	return EOK;
-error:
-	if (fun_name != NULL)
-		free(fun_name);
-	return rc;
-}
-
-errno_t ata_fun_unbind(disk_t *disk)
-{
-	errno_t rc;
-	char *fun_name;
-
-	if (disk->afun == NULL)
-		return EOK;
-
-	fun_name = ata_fun_name(disk);
-	if (fun_name == NULL) {
-		ddf_msg(LVL_ERROR, "Out of memory.");
-		rc = ENOMEM;
-		goto error;
-	}
-
-	ddf_msg(LVL_DEBUG, "ata_fun_unbind(%p, '%s')", disk, fun_name);
-	rc = ddf_fun_unbind(disk->afun->fun);
-	if (rc != EOK) {
-		ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.", fun_name);
-		goto error;
-	}
-
-	ddf_fun_destroy(disk->afun->fun);
-	disk->afun = NULL;
-	free(fun_name);
-	return EOK;
-error:
-	if (fun_name != NULL)
-		free(fun_name);
-	return rc;
-}
-
-static errno_t ata_dev_remove(ddf_dev_t *dev)
-{
-	ata_ctrl_t *ctrl = (ata_ctrl_t *)ddf_dev_data_get(dev);
-
-	ddf_msg(LVL_DEBUG, "ata_dev_remove(%p)", dev);
-
-	return ata_ctrl_remove(ctrl);
-}
-
-static errno_t ata_dev_gone(ddf_dev_t *dev)
-{
-	ata_ctrl_t *ctrl = (ata_ctrl_t *)ddf_dev_data_get(dev);
-
-	ddf_msg(LVL_DEBUG, "ata_dev_gone(%p)", dev);
-
-	return ata_ctrl_gone(ctrl);
-}
-
-static errno_t ata_fun_online(ddf_fun_t *fun)
-{
-	ddf_msg(LVL_DEBUG, "ata_fun_online()");
-	return ddf_fun_online(fun);
-}
-
-static errno_t ata_fun_offline(ddf_fun_t *fun)
-{
-	ddf_msg(LVL_DEBUG, "ata_fun_offline()");
-	return ddf_fun_offline(fun);
-}
-
-/** Block device connection handler */
-static void ata_bd_connection(ipc_call_t *icall, void *arg)
-{
-	ata_fun_t *afun;
-
-	afun = (ata_fun_t *) ddf_fun_data_get((ddf_fun_t *)arg);
-	bd_conn(icall, &afun->bds);
-}
-
-int main(int argc, char *argv[])
-{
-	printf(NAME ": HelenOS ATA(PI) device driver\n");
-	ddf_log_init(NAME);
-	return ddf_driver_main(&ata_driver);
-}
-
-/**
- * @}
- */
Index: pace/drv/block/ata_bd/main.h
===================================================================
--- uspace/drv/block/ata_bd/main.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,47 +1,0 @@
-/*
- * Copyright (c) 2013 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 ata_bd
- * @{
- */
-/** @file ATA driver main module
- */
-
-#ifndef __ATA_MAIN_H__
-#define __ATA_MAIN_H__
-
-#include "ata_bd.h"
-
-extern errno_t ata_fun_create(disk_t *);
-extern errno_t ata_fun_remove(disk_t *);
-extern errno_t ata_fun_unbind(disk_t *);
-
-#endif
-
-/** @}
- */
Index: pace/drv/block/ata_bd/meson.build
===================================================================
--- uspace/drv/block/ata_bd/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,30 +1,0 @@
-#
-# Copyright (c) 2013 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.
-#
-
-deps = [ 'scsi' ]
-src = files('ata_bd.c', 'main.c')
Index: uspace/drv/block/ddisk/ddisk.c
===================================================================
--- uspace/drv/block/ddisk/ddisk.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/block/ddisk/ddisk.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -64,5 +64,5 @@
 static void ddisk_bd_connection(ipc_call_t *, void *);
 
-static void ddisk_irq_handler(ipc_call_t *, ddf_dev_t *);
+static void ddisk_irq_handler(ipc_call_t *, void *);
 
 static driver_ops_t driver_ops = {
@@ -176,10 +176,15 @@
 };
 
-void ddisk_irq_handler(ipc_call_t *icall, ddf_dev_t *dev)
+/** Ddisk IRQ handler.
+ *
+ * @param icall IRQ event notification
+ * @param arg Argument (ddisk_t *)
+ */
+void ddisk_irq_handler(ipc_call_t *icall, void *arg)
 {
 	ddf_msg(LVL_DEBUG, "ddisk_irq_handler(), status=%" PRIx32,
 	    (uint32_t) ipc_get_arg1(icall));
 
-	ddisk_t *ddisk = (ddisk_t *) ddf_dev_data_get(dev);
+	ddisk_t *ddisk = (ddisk_t *)arg;
 
 	fibril_mutex_lock(&ddisk->lock);
@@ -512,5 +517,5 @@
 	ddisk_irq_commands[3].addr = (void *) &res_phys->command;
 	rc = register_interrupt_handler(dev, ddisk->ddisk_res.irq,
-	    ddisk_irq_handler, &ddisk_irq_code, &ddisk->irq_cap);
+	    ddisk_irq_handler, (void *)ddisk, &ddisk_irq_code, &ddisk->irq_cap);
 	if (rc != EOK) {
 		ddf_msg(LVL_ERROR, "Failed to register interrupt handler.");
Index: uspace/drv/block/isa-ide/doc/doxygroups.h
===================================================================
--- uspace/drv/block/isa-ide/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/drv/block/isa-ide/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,4 @@
+/** @addtogroup isa-ide isa-ide
+ * @brief ISA IDE driver
+ * @ingroup drvs
+ */
Index: uspace/drv/block/isa-ide/isa-ide.c
===================================================================
--- uspace/drv/block/isa-ide/isa-ide.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/drv/block/isa-ide/isa-ide.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,508 @@
+/*
+ * 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 isa-ide
+ * @{
+ */
+
+/**
+ * @file
+ * @brief ISA IDE driver
+ */
+
+#include <ddi.h>
+#include <ddf/interrupt.h>
+#include <ddf/log.h>
+#include <async.h>
+#include <as.h>
+#include <fibril_synch.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <str.h>
+#include <inttypes.h>
+#include <errno.h>
+
+#include "isa-ide.h"
+#include "main.h"
+
+static errno_t isa_ide_init_io(isa_ide_channel_t *);
+static void isa_ide_fini_io(isa_ide_channel_t *);
+static errno_t isa_ide_init_irq(isa_ide_channel_t *);
+static void isa_ide_fini_irq(isa_ide_channel_t *);
+static void isa_ide_irq_handler(ipc_call_t *, void *);
+
+static void isa_ide_write_data_16(void *, uint16_t *, size_t);
+static void isa_ide_read_data_16(void *, uint16_t *, size_t);
+static void isa_ide_write_cmd_8(void *, uint16_t, uint8_t);
+static uint8_t isa_ide_read_cmd_8(void *, uint16_t);
+static void isa_ide_write_ctl_8(void *, uint16_t, uint8_t);
+static uint8_t isa_ide_read_ctl_8(void *, uint16_t);
+static errno_t isa_ide_irq_enable(void *);
+static errno_t isa_ide_irq_disable(void *);
+static errno_t isa_ide_add_device(void *, unsigned, void *);
+static errno_t isa_ide_remove_device(void *, unsigned);
+static void isa_ide_msg_debug(void *, char *);
+static void isa_ide_msg_note(void *, char *);
+static void isa_ide_msg_warn(void *, char *);
+static void isa_ide_msg_error(void *, char *);
+
+static const irq_pio_range_t isa_ide_irq_ranges[] = {
+	{
+		.base = 0,
+		.size = sizeof(ata_cmd_t)
+	}
+};
+
+/** IDE interrupt pseudo code. */
+static const irq_cmd_t isa_ide_irq_cmds[] = {
+	{
+		.cmd = CMD_PIO_READ_8,
+		.addr = NULL,  /* will be patched in run-time */
+		.dstarg = 1
+	},
+	{
+		.cmd = CMD_ACCEPT
+	}
+};
+
+/** Initialize ISA IDE channel. */
+errno_t isa_ide_channel_init(isa_ide_ctrl_t *ctrl, isa_ide_channel_t *chan,
+    unsigned chan_id, isa_ide_hwres_t *res)
+{
+	errno_t rc;
+	bool irq_inited = false;
+	ata_params_t params;
+
+	ddf_msg(LVL_DEBUG, "isa_ide_ctrl_init()");
+
+	chan->ctrl = ctrl;
+	chan->chan_id = chan_id;
+	fibril_mutex_initialize(&chan->lock);
+	if (chan_id == 0) {
+		chan->cmd_physical = res->cmd1;
+		chan->ctl_physical = res->ctl1;
+		chan->irq = res->irq1;
+	} else {
+		chan->cmd_physical = res->cmd2;
+		chan->ctl_physical = res->ctl2;
+		chan->irq = res->irq2;
+	}
+
+	ddf_msg(LVL_NOTE, "I/O address %p/%p", (void *) chan->cmd_physical,
+	    (void *) chan->ctl_physical);
+
+	ddf_msg(LVL_DEBUG, "Init I/O");
+	rc = isa_ide_init_io(chan);
+	if (rc != EOK)
+		return rc;
+
+	ddf_msg(LVL_DEBUG, "Init IRQ");
+	rc = isa_ide_init_irq(chan);
+	if (rc != EOK) {
+		ddf_msg(LVL_NOTE, "init IRQ failed");
+		return rc;
+	}
+
+	irq_inited = true;
+
+	ddf_msg(LVL_DEBUG, "isa_ide_ctrl_init(): Initialize IDE channel");
+
+	params.arg = (void *)chan;
+	params.have_irq = (chan->irq >= 0) ? true : false;
+	params.write_data_16 = isa_ide_write_data_16;
+	params.read_data_16 = isa_ide_read_data_16;
+	params.write_cmd_8 = isa_ide_write_cmd_8;
+	params.read_cmd_8 = isa_ide_read_cmd_8;
+	params.write_ctl_8 = isa_ide_write_ctl_8;
+	params.read_ctl_8 = isa_ide_read_ctl_8;
+	params.irq_enable = isa_ide_irq_enable;
+	params.irq_disable = isa_ide_irq_disable;
+	params.add_device = isa_ide_add_device;
+	params.remove_device = isa_ide_remove_device;
+	params.msg_debug = isa_ide_msg_debug;
+	params.msg_note = isa_ide_msg_note;
+	params.msg_warn = isa_ide_msg_warn;
+	params.msg_error = isa_ide_msg_error;
+
+	rc = ata_channel_create(&params, &chan->channel);
+	if (rc != EOK)
+		goto error;
+
+	rc = ata_channel_initialize(chan->channel);
+	if (rc != EOK)
+		goto error;
+
+	ddf_msg(LVL_DEBUG, "isa_ide_ctrl_init: DONE");
+	return EOK;
+error:
+	if (irq_inited)
+		isa_ide_fini_irq(chan);
+	isa_ide_fini_io(chan);
+	return rc;
+}
+
+/** Finalize ISA IDE channel. */
+errno_t isa_ide_channel_fini(isa_ide_channel_t *chan)
+{
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, ": isa_ide_ctrl_remove()");
+
+	fibril_mutex_lock(&chan->lock);
+
+	rc = ata_channel_destroy(chan->channel);
+	if (rc != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return rc;
+	}
+
+	isa_ide_fini_irq(chan);
+	isa_ide_fini_io(chan);
+	fibril_mutex_unlock(&chan->lock);
+
+	return EOK;
+}
+
+/** Enable device I/O. */
+static errno_t isa_ide_init_io(isa_ide_channel_t *chan)
+{
+	errno_t rc;
+	void *vaddr;
+
+	rc = pio_enable((void *) chan->cmd_physical, sizeof(ata_cmd_t), &vaddr);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
+		return rc;
+	}
+
+	chan->cmd = vaddr;
+
+	rc = pio_enable((void *) chan->ctl_physical, sizeof(ata_ctl_t), &vaddr);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
+		return rc;
+	}
+
+	chan->ctl = vaddr;
+	return EOK;
+}
+
+/** Clean up device I/O. */
+static void isa_ide_fini_io(isa_ide_channel_t *chan)
+{
+	(void) chan;
+	/* XXX TODO */
+}
+
+/** Initialize IRQ. */
+static errno_t isa_ide_init_irq(isa_ide_channel_t *chan)
+{
+	irq_code_t irq_code;
+	irq_pio_range_t *ranges;
+	irq_cmd_t *cmds;
+	errno_t rc;
+
+	if (chan->irq < 0)
+		return EOK;
+
+	ranges = malloc(sizeof(isa_ide_irq_ranges));
+	if (ranges == NULL)
+		return ENOMEM;
+
+	cmds = malloc(sizeof(isa_ide_irq_cmds));
+	if (cmds == NULL) {
+		free(cmds);
+		return ENOMEM;
+	}
+
+	memcpy(ranges, &isa_ide_irq_ranges, sizeof(isa_ide_irq_ranges));
+	ranges[0].base = chan->cmd_physical;
+	memcpy(cmds, &isa_ide_irq_cmds, sizeof(isa_ide_irq_cmds));
+	cmds[0].addr = &chan->cmd->status;
+
+	irq_code.rangecount = sizeof(isa_ide_irq_ranges) / sizeof(irq_pio_range_t);
+	irq_code.ranges = ranges;
+	irq_code.cmdcount = sizeof(isa_ide_irq_cmds) / sizeof(irq_cmd_t);
+	irq_code.cmds = cmds;
+
+	ddf_msg(LVL_NOTE, "IRQ %d", chan->irq);
+	rc = register_interrupt_handler(chan->ctrl->dev, chan->irq,
+	    isa_ide_irq_handler, (void *)chan, &irq_code, &chan->ihandle);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error registering IRQ.");
+		goto error;
+	}
+
+	ddf_msg(LVL_DEBUG, "Interrupt handler registered");
+	free(ranges);
+	free(cmds);
+	return EOK;
+error:
+	free(ranges);
+	free(cmds);
+	return rc;
+}
+
+/** Clean up IRQ. */
+static void isa_ide_fini_irq(isa_ide_channel_t *chan)
+{
+	errno_t rc;
+	async_sess_t *parent_sess;
+
+	parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
+
+	rc = hw_res_disable_interrupt(parent_sess, chan->irq);
+	if (rc != EOK)
+		ddf_msg(LVL_ERROR, "Error disabling IRQ.");
+
+	(void) unregister_interrupt_handler(chan->ctrl->dev, chan->ihandle);
+}
+
+/** Interrupt handler.
+ *
+ * @param call Call data
+ * @param arg Argument (isa_ide_channel_t *)
+ */
+static void isa_ide_irq_handler(ipc_call_t *call, void *arg)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+	uint8_t status;
+	async_sess_t *parent_sess;
+
+	status = ipc_get_arg1(call);
+	ata_channel_irq(chan->channel, status);
+
+	parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
+	hw_res_clear_interrupt(parent_sess, chan->irq);
+}
+
+/** Write the data register callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param data Data
+ * @param nwords Number of words to write
+ */
+static void isa_ide_write_data_16(void *arg, uint16_t *data, size_t nwords)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+	size_t i;
+
+	for (i = 0; i < nwords; i++)
+		pio_write_16(&chan->cmd->data_port, data[i]);
+}
+
+/** Read the data register callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param buf Destination buffer
+ * @param nwords Number of words to read
+ */
+static void isa_ide_read_data_16(void *arg, uint16_t *buf, size_t nwords)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+	size_t i;
+
+	for (i = 0; i < nwords; i++)
+		buf[i] = pio_read_16(&chan->cmd->data_port);
+}
+
+/** Write command register callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param off Register offset
+ * @param value Value to write to command register
+ */
+static void isa_ide_write_cmd_8(void *arg, uint16_t off, uint8_t value)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+
+	pio_write_8(((ioport8_t *)chan->cmd) + off, value);
+}
+
+/** Read command register callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param off Register offset
+ * @return value Value read from command register
+ */
+static uint8_t isa_ide_read_cmd_8(void *arg, uint16_t off)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+
+	return pio_read_8(((ioport8_t *)chan->cmd) + off);
+}
+
+/** Write control register callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param off Register offset
+ * @param value Value to write to control register
+ */
+static void isa_ide_write_ctl_8(void *arg, uint16_t off, uint8_t value)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+
+	pio_write_8(((ioport8_t *)chan->ctl) + off, value);
+}
+
+/** Read control register callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param off Register offset
+ * @return value Value read from control register
+ */
+static uint8_t isa_ide_read_ctl_8(void *arg, uint16_t off)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+
+	return pio_read_8(((ioport8_t *)chan->ctl) + off);
+}
+
+/** Enable IRQ callback handler
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @return EOK on success or an error code
+ */
+static errno_t isa_ide_irq_enable(void *arg)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+	async_sess_t *parent_sess;
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "Enable IRQ %d for channel %u",
+	    chan->irq, chan->chan_id);
+
+	parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
+
+	rc = hw_res_enable_interrupt(parent_sess, chan->irq);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error enabling IRQ.");
+		return rc;
+	}
+
+	return EOK;
+}
+
+/** Disable IRQ callback handler
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @return EOK on success or an error code
+ */
+static errno_t isa_ide_irq_disable(void *arg)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+	async_sess_t *parent_sess;
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "Disable IRQ");
+
+	parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
+
+	rc = hw_res_disable_interrupt(parent_sess, chan->irq);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error disabling IRQ.");
+		return rc;
+	}
+
+	return EOK;
+}
+
+/** Add ATA device callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param idx Device index
+ * $param charg Connection handler argument
+ * @return EOK on success or an error code
+ */
+static errno_t isa_ide_add_device(void *arg, unsigned idx, void *charg)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+	return isa_ide_fun_create(chan, idx, charg);
+}
+
+/** Remove ATA device callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param idx Device index
+ * @return EOK on success or an error code
+ */
+static errno_t isa_ide_remove_device(void *arg, unsigned idx)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+	return isa_ide_fun_remove(chan, idx);
+}
+
+/** Debug message callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param msg Message
+ */
+static void isa_ide_msg_debug(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_DEBUG, "%s", msg);
+}
+
+/** Notice message callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param msg Message
+ */
+static void isa_ide_msg_note(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_NOTE, "%s", msg);
+}
+
+/** Warning message callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param msg Message
+ */
+static void isa_ide_msg_warn(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_WARN, "%s", msg);
+}
+
+/** Error message callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param msg Message
+ */
+static void isa_ide_msg_error(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_ERROR, "%s", msg);
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/block/isa-ide/isa-ide.h
===================================================================
--- uspace/drv/block/isa-ide/isa-ide.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/drv/block/isa-ide/isa-ide.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,110 @@
+/*
+ * 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 isa-ide
+ * @{
+ */
+/** @file ISA IDE driver definitions.
+ */
+
+#ifndef ISA_IDE_H
+#define ISA_IDE_H
+
+#include <ata/ata.h>
+#include <ata/ata_hw.h>
+#include <ddf/driver.h>
+#include <fibril_synch.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#define NAME "isa-ide"
+
+/** ISA IDE hardware resources */
+typedef struct {
+	uintptr_t cmd1;	/**< Primary channel command block base address. */
+	uintptr_t ctl1;	/**< Primary channel control block base address. */
+	uintptr_t cmd2;	/**< Secondary channel command block base address. */
+	uintptr_t ctl2;	/**< Secondary channel control block base address. */
+	int irq1;	/**< Primary channel IRQ */
+	int irq2;	/**< Secondary channel IRQ */
+} isa_ide_hwres_t;
+
+/** ISA IDE channel */
+typedef struct isa_ide_channel {
+	/** Parent controller */
+	struct isa_ide_ctrl *ctrl;
+	/** I/O base address of the command registers */
+	uintptr_t cmd_physical;
+	/** I/O base address of the control registers */
+	uintptr_t ctl_physical;
+
+	/** Command registers */
+	ata_cmd_t *cmd;
+	/** Control registers */
+	ata_ctl_t *ctl;
+	/** IRQ (-1 if not used) */
+	int irq;
+	/** IRQ handle */
+	cap_irq_handle_t ihandle;
+
+	/** Synchronize controller access */
+	fibril_mutex_t lock;
+	/** Value of status register read by interrupt handler */
+	uint8_t irq_status;
+
+	/** Libata ATA channel */
+	ata_channel_t *channel;
+	struct isa_ide_fun *fun[2];
+
+	/** Channel ID */
+	unsigned chan_id;
+} isa_ide_channel_t;
+
+/** ISA IDE controller */
+typedef struct isa_ide_ctrl {
+	/** DDF device */
+	ddf_dev_t *dev;
+
+	/** Primary and secondary channel */
+	isa_ide_channel_t channel[2];
+} isa_ide_ctrl_t;
+
+/** ISA IDE function */
+typedef struct isa_ide_fun {
+	ddf_fun_t *fun;
+	void *charg;
+} isa_ide_fun_t;
+
+extern errno_t isa_ide_channel_init(isa_ide_ctrl_t *, isa_ide_channel_t *,
+    unsigned, isa_ide_hwres_t *);
+extern errno_t isa_ide_channel_fini(isa_ide_channel_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/drv/block/isa-ide/isa-ide.ma
===================================================================
--- uspace/drv/block/isa-ide/isa-ide.ma	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/drv/block/isa-ide/isa-ide.ma	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,1 @@
+10 isa/ide
Index: uspace/drv/block/isa-ide/main.c
===================================================================
--- uspace/drv/block/isa-ide/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/drv/block/isa-ide/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,405 @@
+/*
+ * 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 isa-ide
+ * @{
+ */
+
+/** @file
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <errno.h>
+#include <str_error.h>
+#include <ddf/driver.h>
+#include <ddf/log.h>
+#include <device/hw_res_parsed.h>
+
+#include "isa-ide.h"
+#include "main.h"
+
+static errno_t isa_ide_dev_add(ddf_dev_t *dev);
+static errno_t isa_ide_dev_remove(ddf_dev_t *dev);
+static errno_t isa_ide_dev_gone(ddf_dev_t *dev);
+static errno_t isa_ide_fun_online(ddf_fun_t *fun);
+static errno_t isa_ide_fun_offline(ddf_fun_t *fun);
+
+static void isa_ide_connection(ipc_call_t *, void *);
+
+static driver_ops_t driver_ops = {
+	.dev_add = &isa_ide_dev_add,
+	.dev_remove = &isa_ide_dev_remove,
+	.dev_gone = &isa_ide_dev_gone,
+	.fun_online = &isa_ide_fun_online,
+	.fun_offline = &isa_ide_fun_offline
+};
+
+static driver_t isa_ide_driver = {
+	.name = NAME,
+	.driver_ops = &driver_ops
+};
+
+static errno_t isa_ide_get_res(ddf_dev_t *dev, isa_ide_hwres_t *res)
+{
+	async_sess_t *parent_sess;
+	hw_res_list_parsed_t hw_res;
+	hw_res_flags_t flags;
+	errno_t rc;
+
+	parent_sess = ddf_dev_parent_sess_get(dev);
+	if (parent_sess == NULL)
+		return ENOMEM;
+
+	rc = hw_res_get_flags(parent_sess, &flags);
+	if (rc != EOK)
+		return rc;
+
+	/*
+	 * Prevent attaching to the legacy ISA IDE register block
+	 * on a system with PCI not to conflict with PCI IDE.
+	 *
+	 * XXX This is a simplification. If we had a PCI-based system without
+	 * PCI-IDE or with PCI-IDE disabled and would still like to use
+	 * an ISA IDE controller, this would prevent us from doing so.
+	 */
+	if (flags & hwf_isa_bridge) {
+		ddf_msg(LVL_NOTE, "Will not attach to PCI/ISA bridge.");
+		return EIO;
+	}
+
+	hw_res_list_parsed_init(&hw_res);
+	rc = hw_res_get_list_parsed(parent_sess, &hw_res, 0);
+	if (rc != EOK)
+		return rc;
+
+	if (hw_res.io_ranges.count != 4) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	/* I/O ranges */
+
+	addr_range_t *cmd1_rng = &hw_res.io_ranges.ranges[0];
+	addr_range_t *ctl1_rng = &hw_res.io_ranges.ranges[1];
+	addr_range_t *cmd2_rng = &hw_res.io_ranges.ranges[2];
+	addr_range_t *ctl2_rng = &hw_res.io_ranges.ranges[3];
+	res->cmd1 = RNGABS(*cmd1_rng);
+	res->ctl1 = RNGABS(*ctl1_rng);
+	res->cmd2 = RNGABS(*cmd2_rng);
+	res->ctl2 = RNGABS(*ctl2_rng);
+
+	if (RNGSZ(*ctl1_rng) < sizeof(ata_ctl_t)) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	if (RNGSZ(*cmd1_rng) < sizeof(ata_cmd_t)) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	if (RNGSZ(*ctl2_rng) < sizeof(ata_ctl_t)) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	if (RNGSZ(*cmd2_rng) < sizeof(ata_cmd_t)) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	/* IRQ */
+	if (hw_res.irqs.count > 0) {
+		res->irq1 = hw_res.irqs.irqs[0];
+	} else {
+		res->irq1 = -1;
+	}
+
+	if (hw_res.irqs.count > 1) {
+		res->irq2 = hw_res.irqs.irqs[1];
+	} else {
+		res->irq2 = -1;
+	}
+
+	return EOK;
+error:
+	hw_res_list_parsed_clean(&hw_res);
+	return rc;
+}
+
+/** Add new device
+ *
+ * @param  dev New device
+ * @return     EOK on success or an error code.
+ */
+static errno_t isa_ide_dev_add(ddf_dev_t *dev)
+{
+	isa_ide_ctrl_t *ctrl;
+	isa_ide_hwres_t res;
+	errno_t rc;
+
+	rc = isa_ide_get_res(dev, &res);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Invalid HW resource configuration.");
+		return EINVAL;
+	}
+
+	ctrl = ddf_dev_data_alloc(dev, sizeof(isa_ide_ctrl_t));
+	if (ctrl == NULL) {
+		ddf_msg(LVL_ERROR, "Failed allocating soft state.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ctrl->dev = dev;
+
+	rc = isa_ide_channel_init(ctrl, &ctrl->channel[0], 0, &res);
+	if (rc == ENOENT)
+		goto error;
+
+	rc = isa_ide_channel_init(ctrl, &ctrl->channel[1], 1, &res);
+	if (rc == ENOENT)
+		goto error;
+
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed initializing ATA controller.");
+		rc = EIO;
+		goto error;
+	}
+
+	return EOK;
+error:
+	return rc;
+}
+
+static char *isa_ide_fun_name(isa_ide_channel_t *chan, unsigned idx)
+{
+	char *fun_name;
+
+	if (asprintf(&fun_name, "c%ud%u", chan->chan_id, idx) < 0)
+		return NULL;
+
+	return fun_name;
+}
+
+errno_t isa_ide_fun_create(isa_ide_channel_t *chan, unsigned idx, void *charg)
+{
+	errno_t rc;
+	char *fun_name = NULL;
+	ddf_fun_t *fun = NULL;
+	isa_ide_fun_t *ifun = NULL;
+	bool bound = false;
+
+	fun_name = isa_ide_fun_name(chan, idx);
+	if (fun_name == NULL) {
+		ddf_msg(LVL_ERROR, "Out of memory.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	fun = ddf_fun_create(chan->ctrl->dev, fun_exposed, fun_name);
+	if (fun == NULL) {
+		ddf_msg(LVL_ERROR, "Failed creating DDF function.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	/* Allocate soft state */
+	ifun = ddf_fun_data_alloc(fun, sizeof(isa_ide_fun_t));
+	if (ifun == NULL) {
+		ddf_msg(LVL_ERROR, "Failed allocating softstate.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ifun->fun = fun;
+	ifun->charg = charg;
+
+	/* Set up a connection handler. */
+	ddf_fun_set_conn_handler(fun, isa_ide_connection);
+
+	rc = ddf_fun_bind(fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed binding DDF function %s: %s",
+		    fun_name, str_error(rc));
+		goto error;
+	}
+
+	bound = true;
+
+	rc = ddf_fun_add_to_category(fun, "disk");
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed adding function %s to "
+		    "category 'disk': %s", fun_name, str_error(rc));
+		goto error;
+	}
+
+	free(fun_name);
+	return EOK;
+error:
+	if (bound)
+		ddf_fun_unbind(fun);
+	if (fun != NULL)
+		ddf_fun_destroy(fun);
+	if (fun_name != NULL)
+		free(fun_name);
+
+	return rc;
+}
+
+errno_t isa_ide_fun_remove(isa_ide_channel_t *chan, unsigned idx)
+{
+	errno_t rc;
+	char *fun_name;
+	isa_ide_fun_t *ifun = chan->fun[idx];
+
+	fun_name = isa_ide_fun_name(chan, idx);
+	if (fun_name == NULL) {
+		ddf_msg(LVL_ERROR, "Out of memory.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ddf_msg(LVL_DEBUG, "isa_ide_fun_remove(%p, '%s')", ifun, fun_name);
+	rc = ddf_fun_offline(ifun->fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error offlining function '%s'.", fun_name);
+		goto error;
+	}
+
+	rc = ddf_fun_unbind(ifun->fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.", fun_name);
+		goto error;
+	}
+
+	ddf_fun_destroy(ifun->fun);
+	free(fun_name);
+	return EOK;
+error:
+	if (fun_name != NULL)
+		free(fun_name);
+	return rc;
+}
+
+errno_t isa_ide_fun_unbind(isa_ide_channel_t *chan, unsigned idx)
+{
+	errno_t rc;
+	char *fun_name;
+	isa_ide_fun_t *ifun = chan->fun[idx];
+
+	fun_name = isa_ide_fun_name(chan, idx);
+	if (fun_name == NULL) {
+		ddf_msg(LVL_ERROR, "Out of memory.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ddf_msg(LVL_DEBUG, "isa_ide_fun_unbind(%p, '%s')", ifun, fun_name);
+	rc = ddf_fun_unbind(ifun->fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.", fun_name);
+		goto error;
+	}
+
+	ddf_fun_destroy(ifun->fun);
+	free(fun_name);
+	return EOK;
+error:
+	if (fun_name != NULL)
+		free(fun_name);
+	return rc;
+}
+
+static errno_t isa_ide_dev_remove(ddf_dev_t *dev)
+{
+	isa_ide_ctrl_t *ctrl = (isa_ide_ctrl_t *)ddf_dev_data_get(dev);
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "isa_ide_dev_remove(%p)", dev);
+
+	rc = isa_ide_channel_fini(&ctrl->channel[0]);
+	if (rc != EOK)
+		return rc;
+
+	rc = isa_ide_channel_fini(&ctrl->channel[1]);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+static errno_t isa_ide_dev_gone(ddf_dev_t *dev)
+{
+	isa_ide_ctrl_t *ctrl = (isa_ide_ctrl_t *)ddf_dev_data_get(dev);
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "isa_ide_dev_gone(%p)", dev);
+
+	rc = isa_ide_channel_fini(&ctrl->channel[0]);
+	if (rc != EOK)
+		return rc;
+
+	rc = isa_ide_channel_fini(&ctrl->channel[1]);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+static errno_t isa_ide_fun_online(ddf_fun_t *fun)
+{
+	ddf_msg(LVL_DEBUG, "isa_ide_fun_online()");
+	return ddf_fun_online(fun);
+}
+
+static errno_t isa_ide_fun_offline(ddf_fun_t *fun)
+{
+	ddf_msg(LVL_DEBUG, "isa_ide_fun_offline()");
+	return ddf_fun_offline(fun);
+}
+
+static void isa_ide_connection(ipc_call_t *icall, void *arg)
+{
+	isa_ide_fun_t *ifun;
+
+	ifun = (isa_ide_fun_t *) ddf_fun_data_get((ddf_fun_t *)arg);
+	ata_connection(icall, ifun->charg);
+}
+
+int main(int argc, char *argv[])
+{
+	printf(NAME ": HelenOS ISA IDE device driver\n");
+	ddf_log_init(NAME);
+	return ddf_driver_main(&isa_ide_driver);
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/block/isa-ide/main.h
===================================================================
--- uspace/drv/block/isa-ide/main.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/drv/block/isa-ide/main.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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 isa-ide
+ * @{
+ */
+/** @file ISA IDE driver main module
+ */
+
+#ifndef MAIN_H
+#define MAIN_H
+
+#include "isa-ide.h"
+
+extern errno_t isa_ide_fun_create(isa_ide_channel_t *, unsigned, void *);
+extern errno_t isa_ide_fun_remove(isa_ide_channel_t *, unsigned);
+extern errno_t isa_ide_fun_unbind(isa_ide_channel_t *, unsigned);
+
+#endif
+
+/** @}
+ */
Index: uspace/drv/block/isa-ide/meson.build
===================================================================
--- uspace/drv/block/isa-ide/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/drv/block/isa-ide/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,30 @@
+#
+# 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.
+#
+
+deps = [ 'ata', 'scsi' ]
+src = files('isa-ide.c', 'main.c')
Index: uspace/drv/block/pci-ide/doc/doxygroups.h
===================================================================
--- uspace/drv/block/pci-ide/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/drv/block/pci-ide/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,4 @@
+/** @addtogroup pci-ide pci-ide
+ * @brief PCI IDE driver
+ * @ingroup drvs
+ */
Index: uspace/drv/block/pci-ide/main.c
===================================================================
--- uspace/drv/block/pci-ide/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/drv/block/pci-ide/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,383 @@
+/*
+ * 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 pci-ide
+ * @{
+ */
+
+/** @file
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <errno.h>
+#include <str_error.h>
+#include <ddf/driver.h>
+#include <ddf/log.h>
+#include <device/hw_res_parsed.h>
+
+#include "pci-ide.h"
+#include "pci-ide_hw.h"
+#include "main.h"
+
+static errno_t pci_ide_dev_add(ddf_dev_t *dev);
+static errno_t pci_ide_dev_remove(ddf_dev_t *dev);
+static errno_t pci_ide_dev_gone(ddf_dev_t *dev);
+static errno_t pci_ide_fun_online(ddf_fun_t *fun);
+static errno_t pci_ide_fun_offline(ddf_fun_t *fun);
+
+static void pci_ide_connection(ipc_call_t *, void *);
+
+static driver_ops_t driver_ops = {
+	.dev_add = &pci_ide_dev_add,
+	.dev_remove = &pci_ide_dev_remove,
+	.dev_gone = &pci_ide_dev_gone,
+	.fun_online = &pci_ide_fun_online,
+	.fun_offline = &pci_ide_fun_offline
+};
+
+static driver_t pci_ide_driver = {
+	.name = NAME,
+	.driver_ops = &driver_ops
+};
+
+static errno_t pci_ide_get_res(ddf_dev_t *dev, pci_ide_hwres_t *res)
+{
+	async_sess_t *parent_sess;
+	hw_res_list_parsed_t hw_res;
+	errno_t rc;
+
+	parent_sess = ddf_dev_parent_sess_get(dev);
+	if (parent_sess == NULL)
+		return ENOMEM;
+
+	hw_res_list_parsed_init(&hw_res);
+	rc = hw_res_get_list_parsed(parent_sess, &hw_res, 0);
+	if (rc != EOK)
+		return rc;
+
+	if (hw_res.io_ranges.count != 1) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	/* Legacty ISA I/O ranges are fixed */
+
+	res->cmd1 = pci_ide_ata_cmd_p;
+	res->ctl1 = pci_ide_ata_ctl_p;
+	res->cmd2 = pci_ide_ata_cmd_s;
+	res->ctl2 = pci_ide_ata_ctl_s;
+
+	/* PCI I/O range */
+	addr_range_t *bmregs_rng = &hw_res.io_ranges.ranges[0];
+	res->bmregs = RNGABS(*bmregs_rng);
+
+	ddf_msg(LVL_NOTE, "sizes: %zu", RNGSZ(*bmregs_rng));
+
+	if (RNGSZ(*bmregs_rng) < sizeof(pci_ide_regs_t)) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	/* IRQ */
+	if (hw_res.irqs.count > 0) {
+		res->irq1 = hw_res.irqs.irqs[0];
+	} else {
+		res->irq1 = -1;
+	}
+
+	if (hw_res.irqs.count > 1) {
+		res->irq2 = hw_res.irqs.irqs[1];
+	} else {
+		res->irq2 = -1;
+	}
+
+	return EOK;
+error:
+	hw_res_list_parsed_clean(&hw_res);
+	return rc;
+}
+
+/** Add new device
+ *
+ * @param  dev New device
+ * @return     EOK on success or an error code.
+ */
+static errno_t pci_ide_dev_add(ddf_dev_t *dev)
+{
+	pci_ide_ctrl_t *ctrl;
+	pci_ide_hwres_t res;
+	errno_t rc;
+
+	rc = pci_ide_get_res(dev, &res);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Invalid HW resource configuration.");
+		return EINVAL;
+	}
+
+	ctrl = ddf_dev_data_alloc(dev, sizeof(pci_ide_ctrl_t));
+	if (ctrl == NULL) {
+		ddf_msg(LVL_ERROR, "Failed allocating soft state.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ctrl->dev = dev;
+
+	rc = pci_ide_ctrl_init(ctrl, &res);
+	if (rc != EOK)
+		goto error;
+
+	rc = pci_ide_channel_init(ctrl, &ctrl->channel[0], 0, &res);
+	if (rc == ENOENT)
+		goto error;
+
+	rc = pci_ide_channel_init(ctrl, &ctrl->channel[1], 1, &res);
+	if (rc == ENOENT)
+		goto error;
+
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed initializing ATA controller.");
+		rc = EIO;
+		goto error;
+	}
+
+	return EOK;
+error:
+	return rc;
+}
+
+static char *pci_ide_fun_name(pci_ide_channel_t *chan, unsigned idx)
+{
+	char *fun_name;
+
+	if (asprintf(&fun_name, "c%ud%u", chan->chan_id, idx) < 0)
+		return NULL;
+
+	return fun_name;
+}
+
+errno_t pci_ide_fun_create(pci_ide_channel_t *chan, unsigned idx, void *charg)
+{
+	errno_t rc;
+	char *fun_name = NULL;
+	ddf_fun_t *fun = NULL;
+	pci_ide_fun_t *ifun = NULL;
+	bool bound = false;
+
+	fun_name = pci_ide_fun_name(chan, idx);
+	if (fun_name == NULL) {
+		ddf_msg(LVL_ERROR, "Out of memory.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	fun = ddf_fun_create(chan->ctrl->dev, fun_exposed, fun_name);
+	if (fun == NULL) {
+		ddf_msg(LVL_ERROR, "Failed creating DDF function.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	/* Allocate soft state */
+	ifun = ddf_fun_data_alloc(fun, sizeof(pci_ide_fun_t));
+	if (ifun == NULL) {
+		ddf_msg(LVL_ERROR, "Failed allocating softstate.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ifun->fun = fun;
+	ifun->charg = charg;
+
+	/* Set up a connection handler. */
+	ddf_fun_set_conn_handler(fun, pci_ide_connection);
+
+	rc = ddf_fun_bind(fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed binding DDF function %s: %s",
+		    fun_name, str_error(rc));
+		goto error;
+	}
+
+	bound = true;
+
+	rc = ddf_fun_add_to_category(fun, "disk");
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed adding function %s to "
+		    "category 'disk': %s", fun_name, str_error(rc));
+		goto error;
+	}
+
+	free(fun_name);
+	return EOK;
+error:
+	if (bound)
+		ddf_fun_unbind(fun);
+	if (fun != NULL)
+		ddf_fun_destroy(fun);
+	if (fun_name != NULL)
+		free(fun_name);
+
+	return rc;
+}
+
+errno_t pci_ide_fun_remove(pci_ide_channel_t *chan, unsigned idx)
+{
+	errno_t rc;
+	char *fun_name;
+	pci_ide_fun_t *ifun = chan->fun[idx];
+
+	fun_name = pci_ide_fun_name(chan, idx);
+	if (fun_name == NULL) {
+		ddf_msg(LVL_ERROR, "Out of memory.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ddf_msg(LVL_DEBUG, "pci_ide_fun_remove(%p, '%s')", ifun, fun_name);
+	rc = ddf_fun_offline(ifun->fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error offlining function '%s'.", fun_name);
+		goto error;
+	}
+
+	rc = ddf_fun_unbind(ifun->fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.", fun_name);
+		goto error;
+	}
+
+	ddf_fun_destroy(ifun->fun);
+	free(fun_name);
+	return EOK;
+error:
+	if (fun_name != NULL)
+		free(fun_name);
+	return rc;
+}
+
+errno_t pci_ide_fun_unbind(pci_ide_channel_t *chan, unsigned idx)
+{
+	errno_t rc;
+	char *fun_name;
+	pci_ide_fun_t *ifun = chan->fun[idx];
+
+	fun_name = pci_ide_fun_name(chan, idx);
+	if (fun_name == NULL) {
+		ddf_msg(LVL_ERROR, "Out of memory.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ddf_msg(LVL_DEBUG, "pci_ide_fun_unbind(%p, '%s')", ifun, fun_name);
+	rc = ddf_fun_unbind(ifun->fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.", fun_name);
+		goto error;
+	}
+
+	ddf_fun_destroy(ifun->fun);
+	free(fun_name);
+	return EOK;
+error:
+	if (fun_name != NULL)
+		free(fun_name);
+	return rc;
+}
+
+static errno_t pci_ide_dev_remove(ddf_dev_t *dev)
+{
+	pci_ide_ctrl_t *ctrl = (pci_ide_ctrl_t *)ddf_dev_data_get(dev);
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "pci_ide_dev_remove(%p)", dev);
+
+	rc = pci_ide_channel_fini(&ctrl->channel[0]);
+	if (rc != EOK)
+		return rc;
+
+	rc = pci_ide_channel_fini(&ctrl->channel[1]);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+static errno_t pci_ide_dev_gone(ddf_dev_t *dev)
+{
+	pci_ide_ctrl_t *ctrl = (pci_ide_ctrl_t *)ddf_dev_data_get(dev);
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "pci_ide_dev_gone(%p)", dev);
+
+	rc = pci_ide_ctrl_fini(ctrl);
+	if (rc != EOK)
+		return rc;
+
+	rc = pci_ide_channel_fini(&ctrl->channel[0]);
+	if (rc != EOK)
+		return rc;
+
+	rc = pci_ide_channel_fini(&ctrl->channel[1]);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+static errno_t pci_ide_fun_online(ddf_fun_t *fun)
+{
+	ddf_msg(LVL_DEBUG, "pci_ide_fun_online()");
+	return ddf_fun_online(fun);
+}
+
+static errno_t pci_ide_fun_offline(ddf_fun_t *fun)
+{
+	ddf_msg(LVL_DEBUG, "pci_ide_fun_offline()");
+	return ddf_fun_offline(fun);
+}
+
+static void pci_ide_connection(ipc_call_t *icall, void *arg)
+{
+	pci_ide_fun_t *ifun;
+
+	ifun = (pci_ide_fun_t *) ddf_fun_data_get((ddf_fun_t *)arg);
+	ata_connection(icall, ifun->charg);
+}
+
+int main(int argc, char *argv[])
+{
+	printf(NAME ": HelenOS PCI IDE device driver\n");
+	ddf_log_init(NAME);
+	return ddf_driver_main(&pci_ide_driver);
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/block/pci-ide/main.h
===================================================================
--- uspace/drv/block/pci-ide/main.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/drv/block/pci-ide/main.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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 pci-ide
+ * @{
+ */
+/** @file PCI IDE driver main module
+ */
+
+#ifndef MAIN_H
+#define MAIN_H
+
+#include "pci-ide.h"
+
+extern errno_t pci_ide_fun_create(pci_ide_channel_t *, unsigned, void *);
+extern errno_t pci_ide_fun_remove(pci_ide_channel_t *, unsigned);
+extern errno_t pci_ide_fun_unbind(pci_ide_channel_t *, unsigned);
+
+#endif
+
+/** @}
+ */
Index: uspace/drv/block/pci-ide/meson.build
===================================================================
--- uspace/drv/block/pci-ide/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/drv/block/pci-ide/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,30 @@
+#
+# 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.
+#
+
+deps = [ 'ata', 'scsi' ]
+src = files('pci-ide.c', 'main.c')
Index: uspace/drv/block/pci-ide/pci-ide.c
===================================================================
--- uspace/drv/block/pci-ide/pci-ide.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/drv/block/pci-ide/pci-ide.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,719 @@
+/*
+ * 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 pci-ide
+ * @{
+ */
+
+/**
+ * @file
+ * @brief PCI IDE driver
+ */
+
+#include <assert.h>
+#include <ddi.h>
+#include <ddf/interrupt.h>
+#include <ddf/log.h>
+#include <async.h>
+#include <as.h>
+#include <fibril_synch.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <str.h>
+#include <inttypes.h>
+#include <errno.h>
+
+#include "pci-ide.h"
+#include "main.h"
+
+static errno_t pci_ide_init_io(pci_ide_channel_t *);
+static void pci_ide_fini_io(pci_ide_channel_t *);
+static errno_t pci_ide_init_irq(pci_ide_channel_t *);
+static void pci_ide_fini_irq(pci_ide_channel_t *);
+static void pci_ide_irq_handler(ipc_call_t *, void *);
+
+static void pci_ide_write_data_16(void *, uint16_t *, size_t);
+static void pci_ide_read_data_16(void *, uint16_t *, size_t);
+static void pci_ide_write_cmd_8(void *, uint16_t, uint8_t);
+static uint8_t pci_ide_read_cmd_8(void *, uint16_t);
+static void pci_ide_write_ctl_8(void *, uint16_t, uint8_t);
+static uint8_t pci_ide_read_ctl_8(void *, uint16_t);
+static errno_t pci_ide_irq_enable(void *);
+static errno_t pci_ide_irq_disable(void *);
+static void pci_ide_dma_chan_setup(void *, void *, size_t, ata_dma_dir_t);
+static void pci_ide_dma_chan_teardown(void *);
+static errno_t pci_ide_add_device(void *, unsigned, void *);
+static errno_t pci_ide_remove_device(void *, unsigned);
+static void pci_ide_msg_debug(void *, char *);
+static void pci_ide_msg_note(void *, char *);
+static void pci_ide_msg_warn(void *, char *);
+static void pci_ide_msg_error(void *, char *);
+
+static const irq_pio_range_t pci_ide_irq_ranges[] = {
+	{
+		.base = 0,
+		.size = sizeof(ata_cmd_t)
+	},
+	{
+		.base = 0,
+		.size = sizeof(pci_ide_regs_t)
+	}
+};
+
+/** IDE interrupt pseudo code. */
+static const irq_cmd_t pci_ide_irq_cmds[] = {
+	/* [0] Read BMISX */
+	{
+		.cmd = CMD_PIO_READ_8,
+		.addr = NULL,  /* will be patched in run-time */
+		.dstarg = 1
+	},
+	/* [1] Test BMISX.IDEINTS */
+	{
+		.cmd = CMD_AND,
+		.value = bmisx_ideints,
+		.srcarg = 1,
+		.dstarg = 2
+	},
+	/* [2] if (BMISX.IDEINTS != 0) { */
+	{
+		.cmd = CMD_PREDICATE,
+		.srcarg = 2,
+		.value = 3
+	},
+	/*
+	 * [3] Clear BMISX.IDEINTS by writing 1 to it. This clears bits 6,5,
+	 * but that should not matter.
+	 */
+	{
+		.cmd = CMD_PIO_WRITE_8,
+		.addr = NULL,  /* will be patched in run-time */
+		.value = bmisx_ideints
+	},
+	/* [4] Read IDE status register */
+	{
+		.cmd = CMD_PIO_READ_8,
+		.addr = NULL,  /* will be patched in run-time */
+		.dstarg = 1
+	},
+	/* [5] Accept */
+	{
+		.cmd = CMD_ACCEPT
+	},
+	/* [6] } else { Decline */
+	{
+		.cmd = CMD_DECLINE
+	}
+	/* } */
+};
+
+/** Initialize PCI IDE controller.
+ *
+ * @param ctrl PCI IDE controller
+ * @param res Hardware resources
+ *
+ * @return EOK on success or an error code
+ */
+errno_t pci_ide_ctrl_init(pci_ide_ctrl_t *ctrl, pci_ide_hwres_t *res)
+{
+	errno_t rc;
+	void *vaddr;
+
+	ddf_msg(LVL_DEBUG, "pci_ide_ctrl_init()");
+	ctrl->bmregs_physical = res->bmregs;
+
+	ddf_msg(LVL_NOTE, "Bus master IDE regs I/O address: 0x%lx",
+	    ctrl->bmregs_physical);
+
+	rc = pio_enable((void *)ctrl->bmregs_physical, sizeof(pci_ide_regs_t),
+	    &vaddr);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
+		return rc;
+	}
+
+	ctrl->bmregs = vaddr;
+
+	ddf_msg(LVL_DEBUG, "pci_ide_ctrl_init: DONE");
+	return EOK;
+}
+
+/** Finalize PCI IDE controller.
+ *
+ * @param ctrl PCI IDE controller
+ * @return EOK on success or an error code
+ */
+errno_t pci_ide_ctrl_fini(pci_ide_ctrl_t *ctrl)
+{
+	ddf_msg(LVL_DEBUG, ": pci_ide_ctrl_fini()");
+
+	// XXX TODO
+	return EOK;
+}
+
+/** Initialize PCI IDE channel. */
+errno_t pci_ide_channel_init(pci_ide_ctrl_t *ctrl, pci_ide_channel_t *chan,
+    unsigned chan_id, pci_ide_hwres_t *res)
+{
+	errno_t rc;
+	bool irq_inited = false;
+	ata_params_t params;
+	void *buffer;
+
+	ddf_msg(LVL_DEBUG, "pci_ide_channel_init()");
+
+	chan->ctrl = ctrl;
+	chan->chan_id = chan_id;
+	fibril_mutex_initialize(&chan->lock);
+	if (chan_id == 0) {
+		chan->cmd_physical = res->cmd1;
+		chan->ctl_physical = res->ctl1;
+		chan->irq = res->irq1;
+	} else {
+		chan->cmd_physical = res->cmd2;
+		chan->ctl_physical = res->ctl2;
+		chan->irq = res->irq2;
+	}
+
+	chan->dma_buf_size = 8192;
+
+	ddf_msg(LVL_NOTE, "I/O address %p/%p", (void *) chan->cmd_physical,
+	    (void *) chan->ctl_physical);
+
+	ddf_msg(LVL_DEBUG, "Init I/O");
+	rc = pci_ide_init_io(chan);
+	if (rc != EOK)
+		return rc;
+
+	ddf_msg(LVL_DEBUG, "Init IRQ");
+	rc = pci_ide_init_irq(chan);
+	if (rc != EOK) {
+		ddf_msg(LVL_NOTE, "init IRQ failed");
+		goto error;
+	}
+
+	irq_inited = true;
+
+	ddf_msg(LVL_DEBUG, "Allocate PRD table");
+
+	buffer = AS_AREA_ANY;
+	rc = dmamem_map_anonymous(sizeof (pci_ide_prd_t), DMAMEM_4GiB | 0xffff,
+	    AS_AREA_WRITE | AS_AREA_READ, 0, &chan->prdt_pa, &buffer);
+	if (rc != EOK) {
+		ddf_msg(LVL_NOTE, "Failed allocating PRD table.");
+		goto error;
+	}
+
+	chan->prdt = (pci_ide_prd_t *)buffer;
+
+	ddf_msg(LVL_DEBUG, "Allocate DMA buffer");
+
+	buffer = AS_AREA_ANY;
+	rc = dmamem_map_anonymous(chan->dma_buf_size, DMAMEM_4GiB | 0xffff,
+	    AS_AREA_WRITE | AS_AREA_READ, 0, &chan->dma_buf_pa, &buffer);
+	if (rc != EOK) {
+		ddf_msg(LVL_NOTE, "Failed allocating PRD table.");
+		goto error;
+	}
+
+	chan->dma_buf = buffer;
+
+	/* Populate PRD with information on our single DMA buffer */
+	chan->prdt->pba = host2uint32_t_le(chan->dma_buf_pa);
+	chan->prdt->bcnt = host2uint16_t_le(chan->dma_buf_size);
+	chan->prdt->eot_res = host2uint16_t_le(pci_ide_prd_eot);
+
+	/* Program PRD table pointer register */
+	if (chan_id == 0) {
+		pio_write_32(&chan->ctrl->bmregs->bmidtpp,
+		    (uint32_t)chan->prdt_pa);
+	} else {
+		pio_write_32(&chan->ctrl->bmregs->bmidtps,
+		    (uint32_t)chan->prdt_pa);
+	}
+
+	ddf_msg(LVL_DEBUG, "pci_ide_channel_init(): Initialize IDE channel");
+
+	params.arg = (void *)chan;
+	params.have_irq = (chan->irq >= 0) ? true : false;
+	params.use_dma = true;
+	params.max_dma_xfer = chan->dma_buf_size;
+	params.write_data_16 = pci_ide_write_data_16;
+	params.read_data_16 = pci_ide_read_data_16;
+	params.write_cmd_8 = pci_ide_write_cmd_8;
+	params.read_cmd_8 = pci_ide_read_cmd_8;
+	params.write_ctl_8 = pci_ide_write_ctl_8;
+	params.read_ctl_8 = pci_ide_read_ctl_8;
+	params.irq_enable = pci_ide_irq_enable;
+	params.irq_disable = pci_ide_irq_disable;
+	params.dma_chan_setup = pci_ide_dma_chan_setup;
+	params.dma_chan_teardown = pci_ide_dma_chan_teardown;
+	params.add_device = pci_ide_add_device;
+	params.remove_device = pci_ide_remove_device;
+	params.msg_debug = pci_ide_msg_debug;
+	params.msg_note = pci_ide_msg_note;
+	params.msg_warn = pci_ide_msg_warn;
+	params.msg_error = pci_ide_msg_error;
+
+	rc = ata_channel_create(&params, &chan->channel);
+	if (rc != EOK)
+		goto error;
+
+	rc = ata_channel_initialize(chan->channel);
+	if (rc != EOK)
+		goto error;
+
+	ddf_msg(LVL_DEBUG, "pci_ide_channel_init: DONE");
+	return EOK;
+error:
+	if (irq_inited)
+		pci_ide_fini_irq(chan);
+	pci_ide_fini_io(chan);
+	return rc;
+}
+
+/** Finalize PCI IDE channel. */
+errno_t pci_ide_channel_fini(pci_ide_channel_t *chan)
+{
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, ": pci_ide_channel_fini()");
+
+	fibril_mutex_lock(&chan->lock);
+
+	rc = ata_channel_destroy(chan->channel);
+	if (rc != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return rc;
+	}
+
+	pci_ide_fini_irq(chan);
+	pci_ide_fini_io(chan);
+	fibril_mutex_unlock(&chan->lock);
+
+	return EOK;
+}
+
+/** Enable device I/O. */
+static errno_t pci_ide_init_io(pci_ide_channel_t *chan)
+{
+	errno_t rc;
+	void *vaddr;
+
+	rc = pio_enable((void *) chan->cmd_physical, sizeof(ata_cmd_t), &vaddr);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
+		return rc;
+	}
+
+	chan->cmd = vaddr;
+
+	rc = pio_enable((void *) chan->ctl_physical, sizeof(ata_ctl_t), &vaddr);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
+		return rc;
+	}
+
+	chan->ctl = vaddr;
+
+	rc = pio_enable((void *) chan->cmd_physical, sizeof(ata_cmd_t), &vaddr);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
+		return rc;
+	}
+
+	chan->cmd = vaddr;
+
+	return EOK;
+}
+
+/** Clean up device I/O. */
+static void pci_ide_fini_io(pci_ide_channel_t *chan)
+{
+	(void) chan;
+	/* XXX TODO */
+}
+
+/** Initialize IRQ. */
+static errno_t pci_ide_init_irq(pci_ide_channel_t *chan)
+{
+	irq_code_t irq_code;
+	irq_pio_range_t *ranges;
+	irq_cmd_t *cmds;
+	uint8_t *bmisx;
+	errno_t rc;
+
+	if (chan->irq < 0)
+		return EOK;
+
+	ranges = malloc(sizeof(pci_ide_irq_ranges));
+	if (ranges == NULL)
+		return ENOMEM;
+
+	cmds = malloc(sizeof(pci_ide_irq_cmds));
+	if (cmds == NULL) {
+		free(cmds);
+		return ENOMEM;
+	}
+
+	/* Bus master IDE status register (primary or secondary) */
+	bmisx = chan->chan_id == 0 ?
+	    &chan->ctrl->bmregs->bmisp :
+	    &chan->ctrl->bmregs->bmiss;
+
+	memcpy(ranges, &pci_ide_irq_ranges, sizeof(pci_ide_irq_ranges));
+	ranges[0].base = chan->cmd_physical;
+	ranges[1].base = chan->ctrl->bmregs_physical;
+
+	memcpy(cmds, &pci_ide_irq_cmds, sizeof(pci_ide_irq_cmds));
+	cmds[0].addr = bmisx;
+	cmds[3].addr = bmisx;
+	cmds[4].addr = &chan->cmd->status;
+
+	irq_code.rangecount = sizeof(pci_ide_irq_ranges) / sizeof(irq_pio_range_t);
+	irq_code.ranges = ranges;
+	irq_code.cmdcount = sizeof(pci_ide_irq_cmds) / sizeof(irq_cmd_t);
+	irq_code.cmds = cmds;
+
+	ddf_msg(LVL_NOTE, "IRQ %d", chan->irq);
+	rc = register_interrupt_handler(chan->ctrl->dev, chan->irq,
+	    pci_ide_irq_handler, (void *)chan, &irq_code, &chan->ihandle);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error registering IRQ.");
+		goto error;
+	}
+
+	ddf_msg(LVL_DEBUG, "Interrupt handler registered");
+	free(ranges);
+	free(cmds);
+	return EOK;
+error:
+	free(ranges);
+	free(cmds);
+	return rc;
+}
+
+/** Clean up IRQ. */
+static void pci_ide_fini_irq(pci_ide_channel_t *chan)
+{
+	errno_t rc;
+	async_sess_t *parent_sess;
+
+	parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
+
+	rc = hw_res_disable_interrupt(parent_sess, chan->irq);
+	if (rc != EOK)
+		ddf_msg(LVL_ERROR, "Error disabling IRQ.");
+
+	(void) unregister_interrupt_handler(chan->ctrl->dev, chan->ihandle);
+}
+
+/** Interrupt handler.
+ *
+ * @param call Call data
+ * @param arg Argument (pci_ide_channel_t *)
+ */
+static void pci_ide_irq_handler(ipc_call_t *call, void *arg)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+	uint8_t status;
+	async_sess_t *parent_sess;
+
+	status = ipc_get_arg1(call);
+	ata_channel_irq(chan->channel, status);
+
+	parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
+	hw_res_clear_interrupt(parent_sess, chan->irq);
+}
+
+/** Write the data register callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param data Data
+ * @param nwords Number of words to write
+ */
+static void pci_ide_write_data_16(void *arg, uint16_t *data, size_t nwords)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+	size_t i;
+
+	for (i = 0; i < nwords; i++)
+		pio_write_16(&chan->cmd->data_port, data[i]);
+}
+
+/** Read the data register callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param buf Destination buffer
+ * @param nwords Number of words to read
+ */
+static void pci_ide_read_data_16(void *arg, uint16_t *buf, size_t nwords)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+	size_t i;
+
+	for (i = 0; i < nwords; i++)
+		buf[i] = pio_read_16(&chan->cmd->data_port);
+}
+
+/** Write command register callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param off Register offset
+ * @param value Value to write to command register
+ */
+static void pci_ide_write_cmd_8(void *arg, uint16_t off, uint8_t value)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+
+	pio_write_8(((ioport8_t *)chan->cmd) + off, value);
+}
+
+/** Read command register callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param off Register offset
+ * @return value Value read from command register
+ */
+static uint8_t pci_ide_read_cmd_8(void *arg, uint16_t off)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+
+	return pio_read_8(((ioport8_t *)chan->cmd) + off);
+}
+
+/** Write control register callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param off Register offset
+ * @param value Value to write to control register
+ */
+static void pci_ide_write_ctl_8(void *arg, uint16_t off, uint8_t value)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+
+	pio_write_8(((ioport8_t *)chan->ctl) + off, value);
+}
+
+/** Read control register callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param off Register offset
+ * @return value Value read from control register
+ */
+static uint8_t pci_ide_read_ctl_8(void *arg, uint16_t off)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+
+	return pio_read_8(((ioport8_t *)chan->ctl) + off);
+}
+
+/** Enable IRQ callback handler
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @return EOK on success or an error code
+ */
+static errno_t pci_ide_irq_enable(void *arg)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+	async_sess_t *parent_sess;
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "Enable IRQ %d for channel %u",
+	    chan->irq, chan->chan_id);
+
+	parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
+
+	rc = hw_res_enable_interrupt(parent_sess, chan->irq);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error enabling IRQ.");
+		return rc;
+	}
+
+	return EOK;
+}
+
+/** Disable IRQ callback handler
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @return EOK on success or an error code
+ */
+static errno_t pci_ide_irq_disable(void *arg)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+	async_sess_t *parent_sess;
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "Disable IRQ");
+
+	parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
+
+	rc = hw_res_disable_interrupt(parent_sess, chan->irq);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error disabling IRQ.");
+		return rc;
+	}
+
+	return EOK;
+}
+
+/** Set up DMA channel callback handler
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param buf Buffer
+ * @param buf_size Buffer size
+ * @param dir DMA transfer direction
+ */
+static void pci_ide_dma_chan_setup(void *arg, void *buf, size_t buf_size,
+    ata_dma_dir_t dir)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+	uint8_t *bmicx;
+	uint8_t val;
+
+	/* Needed later in teardown */
+	chan->cur_dir = dir;
+	chan->cur_buf = buf;
+	chan->cur_buf_size = buf_size;
+
+	if (dir == ata_dma_write) {
+		assert(buf_size <= chan->dma_buf_size);
+		memcpy(chan->dma_buf, buf, buf_size);
+	}
+
+	/* Primary or secondary channel control register */
+	bmicx = (chan->chan_id == 0) ? &chan->ctrl->bmregs->bmicp :
+	    &chan->ctrl->bmregs->bmics;
+
+	/* Set read / write */
+	val = (dir == ata_dma_write) ? bmicx_rwcon : 0;
+	pio_write_8(bmicx, val);
+
+	/* Start bus master DMA engine */
+	val = val | bmicx_ssbm;
+	pio_write_8(bmicx, val);
+}
+
+/** Tear down DMA channel callback handler
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ */
+static void pci_ide_dma_chan_teardown(void *arg)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+	uint8_t *bmicx;
+	uint8_t val;
+
+	/* Primary or secondary channel control register */
+	bmicx = (chan->chan_id == 0) ? &chan->ctrl->bmregs->bmicp :
+	    &chan->ctrl->bmregs->bmics;
+
+	/* Stop bus master DMA engine clear SSBM but keep RWCON the same */
+	val = (chan->cur_dir == ata_dma_write) ? bmicx_rwcon : 0;
+	pio_write_8(bmicx, val);
+
+	if (chan->cur_dir == ata_dma_read) {
+		assert(chan->cur_buf_size <= chan->dma_buf_size);
+		memcpy(chan->cur_buf, chan->dma_buf, chan->cur_buf_size);
+	}
+}
+
+/** Add ATA device callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param idx Device index
+ * $param charg Connection handler argument
+ * @return EOK on success or an error code
+ */
+static errno_t pci_ide_add_device(void *arg, unsigned idx, void *charg)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+	return pci_ide_fun_create(chan, idx, charg);
+}
+
+/** Remove ATA device callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param idx Device index
+ * @return EOK on success or an error code
+ */
+static errno_t pci_ide_remove_device(void *arg, unsigned idx)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+	return pci_ide_fun_remove(chan, idx);
+}
+
+/** Debug message callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param msg Message
+ */
+static void pci_ide_msg_debug(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_DEBUG, "%s", msg);
+}
+
+/** Notice message callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param msg Message
+ */
+static void pci_ide_msg_note(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_NOTE, "%s", msg);
+}
+
+/** Warning message callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param msg Message
+ */
+static void pci_ide_msg_warn(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_WARN, "%s", msg);
+}
+
+/** Error message callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param msg Message
+ */
+static void pci_ide_msg_error(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_ERROR, "%s", msg);
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/block/pci-ide/pci-ide.h
===================================================================
--- uspace/drv/block/pci-ide/pci-ide.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/drv/block/pci-ide/pci-ide.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,135 @@
+/*
+ * 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 pci-ide
+ * @{
+ */
+/** @file PCI IDE driver definitions.
+ */
+
+#ifndef PCI_IDE_H
+#define PCI_IDE_H
+
+#include <ata/ata.h>
+#include <ata/ata_hw.h>
+#include <ddf/driver.h>
+#include <fibril_synch.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include "pci-ide_hw.h"
+
+#define NAME "pci-ide"
+
+/** PCI IDE hardware resources */
+typedef struct {
+	uintptr_t bmregs; /** PCI Bus Master register block base address. */
+	uintptr_t cmd1;	/**< Primary channel command block base address. */
+	uintptr_t ctl1;	/**< Primary channel control block base address. */
+	uintptr_t cmd2;	/**< Secondary channel command block base address. */
+	uintptr_t ctl2;	/**< Secondary channel control block base address. */
+	int irq1;	/**< Primary channel IRQ */
+	int irq2;	/**< Secondary channel IRQ */
+} pci_ide_hwres_t;
+
+/** PCI IDE channel */
+typedef struct pci_ide_channel {
+	/** Parent controller */
+	struct pci_ide_ctrl *ctrl;
+	/** I/O base address of the command registers */
+	uintptr_t cmd_physical;
+	/** I/O base address of the control registers */
+	uintptr_t ctl_physical;
+
+	/** Command registers */
+	ata_cmd_t *cmd;
+	/** Control registers */
+	ata_ctl_t *ctl;
+	/** IRQ (-1 if not used) */
+	int irq;
+	/** IRQ handle */
+	cap_irq_handle_t ihandle;
+
+	/** Synchronize controller access */
+	fibril_mutex_t lock;
+	/** Value of status register read by interrupt handler */
+	uint8_t irq_status;
+
+	/** Physical region descriptor table */
+	pci_ide_prd_t *prdt;
+	/** Physical region descriptor table physical address */
+	uintptr_t prdt_pa;
+	/** DMA buffer */
+	void *dma_buf;
+	/** DMA buffer physical address */
+	uintptr_t dma_buf_pa;
+	/** DMA buffer size */
+	size_t dma_buf_size;
+	/** Current DMA transfer direction */
+	ata_dma_dir_t cur_dir;
+	/** Current data buffer */
+	void *cur_buf;
+	/** Current data buffer size */
+	size_t cur_buf_size;
+
+	/** Libata ATA channel */
+	ata_channel_t *channel;
+	struct pci_ide_fun *fun[2];
+
+	/** Channel ID */
+	unsigned chan_id;
+} pci_ide_channel_t;
+
+/** ISA IDE controller */
+typedef struct pci_ide_ctrl {
+	/** DDF device */
+	ddf_dev_t *dev;
+
+	/** I/O base address of bus master IDE registers */
+	uintptr_t bmregs_physical;
+	/** Bus master IDE registers */
+	pci_ide_regs_t *bmregs;
+	/** Primary and secondary channel */
+	pci_ide_channel_t channel[2];
+} pci_ide_ctrl_t;
+
+/** PCI IDE function */
+typedef struct pci_ide_fun {
+	ddf_fun_t *fun;
+	void *charg;
+} pci_ide_fun_t;
+
+extern errno_t pci_ide_ctrl_init(pci_ide_ctrl_t *, pci_ide_hwres_t *);
+extern errno_t pci_ide_ctrl_fini(pci_ide_ctrl_t *);
+extern errno_t pci_ide_channel_init(pci_ide_ctrl_t *, pci_ide_channel_t *,
+    unsigned, pci_ide_hwres_t *);
+extern errno_t pci_ide_channel_fini(pci_ide_channel_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/drv/block/pci-ide/pci-ide.ma
===================================================================
--- uspace/drv/block/pci-ide/pci-ide.ma	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/drv/block/pci-ide/pci-ide.ma	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,1 @@
+10 pci/ven=8086&dev=7010
Index: uspace/drv/block/pci-ide/pci-ide_hw.h
===================================================================
--- uspace/drv/block/pci-ide/pci-ide_hw.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/drv/block/pci-ide/pci-ide_hw.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,117 @@
+/*
+ * 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 pci-ide
+ * @{
+ */
+/** @file PCI IDE hardware protocol (registers, data structures).
+ *
+ * Based on Intel 82371AB PCI-TO-ISA / IDE XCELERATOR (PIIX4) document.
+ */
+
+#ifndef PCI_IDE_HW_H
+#define PCI_IDE_HW_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+/** PCI Bus Master IDE I/O Registers */
+typedef struct {
+	/** Bus Master IDE Command (primary) */
+	uint8_t bmicp;
+	uint8_t rsvd1;
+	/** Bus Master IDE Command (secondary) */
+	uint8_t bmisp;
+	uint8_t rsvd3;
+	/** Bus Master IDE Descriptor Table Pointer (primary) */
+	uint32_t bmidtpp;
+	/** Bus Master IDE Status (secondary) */
+	uint8_t bmics;
+	uint8_t rsvd9;
+	/** Bus Master IDE Status (secondary) */
+	uint8_t bmiss;
+	uint8_t rsvd11;
+	/** Bus Master IDE Descriptor Table Pointer (secondary) */
+	uint32_t bmidtps;
+} pci_ide_regs_t;
+
+enum pci_ide_bmicx_bits {
+	/** Bus Master Read/Write Control */
+	bmicx_rwcon = 0x08,
+	/** Start/Stop Bus Master */
+	bmicx_ssbm = 0x01
+};
+
+enum pci_ide_bmisx_bits {
+	/** Drive 1 DMA Capable */
+	bmisx_dma1cap = 0x40,
+	/** Drive 0 DMA Capable */
+	bmisx_dma0cap = 0x20,
+	/** IDE Interrupte Status */
+	bmisx_ideints = 0x04,
+	/** IDE DMA Error */
+	bmisx_idedmaerr = 0x02,
+	/** Bus Master IDR Active */
+	bmisx_bmidea = 0x01
+};
+
+#define PCI_IDE_CFG_IDETIM 0x40
+#define PCI_IDE_CFG_SIDETIM 0x44
+#define PCI_IDE_CFG_UDMACTL 0x48
+#define PCI_IDE_CFG_UDMATIM 0x4a
+
+/*
+ * For PIIX we need to use ATA ports at fixed legacy ISA addresses.
+ * There are no corresponding PCI I/O ranges and these adresses are
+ * fixed and cannot be reconfigured.
+ */
+enum {
+	pci_ide_ata_cmd_p = 0x01f0,
+	pci_ide_ata_ctl_p = 0x03f4,
+	pci_ide_ata_cmd_s = 0x0170,
+	pci_ide_ata_ctl_s = 0x0374
+};
+
+enum {
+	pci_ide_prd_eot = 0x8000
+};
+
+/** PIIX physical region descriptor */
+typedef struct {
+	/** Physical base address */
+	uint32_t pba;
+	/** Byte count */
+	uint16_t bcnt;
+	/** EOT / reserved */
+	uint16_t eot_res;
+} pci_ide_prd_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/drv/block/virtio-blk/virtio-blk.c
===================================================================
--- uspace/drv/block/virtio-blk/virtio-blk.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/block/virtio-blk/virtio-blk.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -73,7 +73,12 @@
 };
 
-static void virtio_blk_irq_handler(ipc_call_t *icall, ddf_dev_t *dev)
-{
-	virtio_blk_t *virtio_blk = (virtio_blk_t *) ddf_dev_data_get(dev);
+/** VirtIO block IRQ handler.
+ *
+ * @param icall IRQ event notification
+ * @param arg Argument (virtio_blk_t *)
+ */
+static void virtio_blk_irq_handler(ipc_call_t *icall, void *arg)
+{
+	virtio_blk_t *virtio_blk = (virtio_blk_t *)arg;
 	virtio_dev_t *vdev = &virtio_blk->virtio_dev;
 
@@ -145,5 +150,6 @@
 
 	return register_interrupt_handler(dev, virtio_blk->irq,
-	    virtio_blk_irq_handler, &irq_code, &virtio_blk->irq_handle);
+	    virtio_blk_irq_handler, (void *)virtio_blk, &irq_code,
+	    &virtio_blk->irq_handle);
 }
 
Index: uspace/drv/bus/isa/ebus.dev
===================================================================
--- uspace/drv/bus/isa/ebus.dev	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/bus/isa/ebus.dev	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -3,2 +3,8 @@
 	irq 2027
 	io_range 3f8 8
+
+i8042:
+	match 100 isa/i8042
+	irq 2025
+	irq 2026
+	io_range 060 5
Index: uspace/drv/bus/isa/isa.c
===================================================================
--- uspace/drv/bus/isa/isa.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/bus/isa/isa.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2018 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2010 Lenka Trochtova
  * Copyright (c) 2011 Jan Vesely
@@ -72,8 +72,9 @@
 #define EBUS_CHILD_FUN_CONF_PATH "/drv/isa/ebus.dev"
 
-#define ISA_MAX_HW_RES 5
+#define ISA_MAX_HW_RES 6
 
 typedef struct {
 	fibril_mutex_t mutex;
+	bool pci_isa_bridge;
 	uint16_t pci_vendor_id;
 	uint16_t pci_device_id;
@@ -92,4 +93,5 @@
 	hw_resource_list_t hw_resources;
 	link_t bus_link;
+	isa_bus_t *bus;
 } isa_fun_t;
 
@@ -204,4 +206,18 @@
 }
 
+static errno_t isa_fun_get_flags(ddf_fun_t *fnode, hw_res_flags_t *rflags)
+{
+	isa_fun_t *fun = isa_fun(fnode);
+	hw_res_flags_t flags;
+
+	flags = 0;
+	if (fun->bus->pci_isa_bridge)
+		flags |= hwf_isa_bridge;
+
+	ddf_msg(LVL_NOTE, "isa_fun_get_flags: returning 0x%x", flags);
+	*rflags = flags;
+	return EOK;
+}
+
 static hw_res_ops_t isa_fun_hw_res_ops = {
 	.get_resource_list = isa_fun_get_resources,
@@ -211,4 +227,5 @@
 	.dma_channel_setup = isa_fun_setup_dma,
 	.dma_channel_remain = isa_fun_remain_dma,
+	.get_flags = isa_fun_get_flags
 };
 
@@ -264,4 +281,5 @@
 	fibril_mutex_initialize(&fun->mutex);
 	fun->hw_resources.resources = fun->resources;
+	fun->bus = isa;
 
 	fun->fnode = fnode;
@@ -743,8 +761,12 @@
 	if (rc != EOK) {
 		ddf_msg(LVL_NOTE, "Cannot read PCI config. Assuming ISA classic.");
+		isa->pci_isa_bridge = false;
 		isa->pci_vendor_id = 0;
 		isa->pci_device_id = 0;
 		isa->pci_class = BASE_CLASS_BRIDGE;
 		isa->pci_subclass = SUB_CLASS_BRIDGE_ISA;
+	} else {
+		ddf_msg(LVL_NOTE, "ISA Bridge");
+		isa->pci_isa_bridge = true;
 	}
 
Index: uspace/drv/bus/isa/isa.dev
===================================================================
--- uspace/drv/bus/isa/isa.dev	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/bus/isa/isa.dev	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -51,21 +51,10 @@
 	io_range 70 2
 
-ata-c1:
-	match 100 isa/ata_bd
+ide1:
+	match 100 isa/ide
 	io_range 0x1f0 8
 	io_range 0x3f0 8
-
-ata-c2:
-	match 100 isa/ata_bd
 	io_range 0x170 8
 	io_range 0x370 8
-
-ata-c3:
-	match 100 isa/ata_bd
-	io_range 0x1e8 8
-	io_range 0x3e8 8
-
-ata-c4:
-	match 100 isa/ata_bd
-	io_range 0x168 8
-	io_range 0x368 8
+	irq 14
+	irq 15
Index: uspace/drv/bus/usb/xhci/hw_struct/common.h
===================================================================
--- uspace/drv/bus/usb/xhci/hw_struct/common.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/bus/usb/xhci/hw_struct/common.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -53,10 +53,10 @@
  * 4 bytes, little-endian.
  */
-typedef ioport32_t xhci_dword_t __attribute__((aligned(4)));
+typedef ioport32_t xhci_dword_t;
 
 /**
  * 8 bytes, little-endian.
  */
-typedef volatile uint64_t xhci_qword_t __attribute__((aligned(8)));
+typedef volatile uint64_t xhci_qword_t;
 
 #define XHCI_DWORD_EXTRACT(field, hi, lo) \
Index: uspace/drv/bus/usb/xhci/hw_struct/context.h
===================================================================
--- uspace/drv/bus/usb/xhci/hw_struct/context.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/bus/usb/xhci/hw_struct/context.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -53,4 +53,5 @@
 	xhci_dword_t data3;
 	xhci_dword_t reserved[3];
+} xhci_ep_ctx_t;
 
 #define XHCI_EP_COUNT 31
@@ -107,6 +108,4 @@
 #define XHCI_EP_MAX_ESIT_PAYLOAD_HI(ctx) XHCI_DWORD_EXTRACT((ctx).data[0], 31, 24)
 
-} __attribute__((packed)) xhci_ep_ctx_t;
-
 enum {
 	EP_STATE_DISABLED = 0,
@@ -123,4 +122,5 @@
 	xhci_dword_t data [4];
 	xhci_dword_t reserved [4];
+} xhci_slot_ctx_t;
 
 #define XHCI_SLOT_ROUTE_STRING_SET(ctx, val) \
@@ -165,6 +165,4 @@
 #define XHCI_SLOT_STATE(ctx)            XHCI_DWORD_EXTRACT((ctx).data[3], 31, 27)
 
-} __attribute__((packed)) xhci_slot_ctx_t;
-
 enum {
 	SLOT_STATE_DISABLED = 0,
@@ -213,4 +211,6 @@
 typedef struct xhci_stream_ctx {
 	uint64_t data [2];
+} xhci_stream_ctx_t;
+
 #define XHCI_STREAM_DCS(ctx)     XHCI_QWORD_EXTRACT((ctx).data[0],  0, 0)
 #define XHCI_STREAM_SCT(ctx)     XHCI_QWORD_EXTRACT((ctx).data[0],  3, 1)
@@ -222,5 +222,4 @@
 #define XHCI_STREAM_DEQ_PTR_SET(ctx, val) \
 	xhci_qword_set_bits(&(ctx).data[0], (val >> 4), 63, 4)
-} __attribute__((packed)) xhci_stream_ctx_t;
 
 /**
@@ -234,4 +233,6 @@
 typedef struct xhci_input_ctrl_ctx {
 	uint32_t data [8];
+} __attribute__((packed)) xhci_input_ctrl_ctx_t;
+
 #define XHCI_INPUT_CTRL_CTX_DROP(ctx, idx) \
     XHCI_DWORD_EXTRACT((ctx).data[0], (idx), (idx))
@@ -252,5 +253,4 @@
 #define XHCI_INPUT_CTRL_CTX_ALTER_SETTING(ctx) \
     XHCI_DWORD_EXTRACT((ctx).data[7], 23, 16)
-} __attribute__((packed)) xhci_input_ctrl_ctx_t;
 
 /**
@@ -281,5 +281,5 @@
 	uint8_t reserved;
 	uint8_t ports [];
-} __attribute__((packed)) xhci_port_bandwidth_ctx_t;
+} xhci_port_bandwidth_ctx_t;
 
 #endif
Index: uspace/drv/char/i8042/i8042.c
===================================================================
--- uspace/drv/char/i8042/i8042.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/char/i8042/i8042.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -2,5 +2,5 @@
  * Copyright (c) 2001-2004 Jakub Jermar
  * Copyright (c) 2006 Josef Cejka
- * Copyright (c) 2017 Jiri Svoboda
+ * Copyright (c) 2021 Jiri Svoboda
  * Copyright (c) 2011 Jan Vesely
  * All rights reserved.
@@ -123,11 +123,10 @@
  * Write new data to the corresponding buffer.
  *
- * @param call pointerr to call data.
- * @param dev  Device that caued the interrupt.
- *
- */
-static void i8042_irq_handler(ipc_call_t *call, ddf_dev_t *dev)
-{
-	i8042_t *controller = ddf_dev_data_get(dev);
+ * @param call Pointer to call data.
+ * @param arg  Argument (i8042_t *)
+ */
+static void i8042_irq_handler(ipc_call_t *call, void *arg)
+{
+	i8042_t *controller = (i8042_t *)arg;
 	errno_t rc;
 
@@ -146,4 +145,7 @@
 	fibril_mutex_unlock(&port->buf_lock);
 	fibril_condvar_broadcast(&port->buf_cv);
+
+	async_sess_t *parent_sess = ddf_dev_parent_sess_get(controller->dev);
+	hw_res_clear_interrupt(parent_sess, port->irq);
 }
 
@@ -175,4 +177,6 @@
 	bool aux_bound = false;
 
+	dev->dev = ddf_dev;
+
 	if (regs->size < sizeof(i8042_regs_t)) {
 		rc = EINVAL;
@@ -202,4 +206,5 @@
 	dev->kbd->cds.ops = &i8042_chardev_ops;
 	dev->kbd->cds.sarg = dev->kbd;
+	dev->kbd->irq = irq_kbd;
 	fibril_mutex_initialize(&dev->kbd->buf_lock);
 	fibril_condvar_initialize(&dev->kbd->buf_cv);
@@ -226,4 +231,5 @@
 	dev->aux->cds.ops = &i8042_chardev_ops;
 	dev->aux->cds.sarg = dev->aux;
+	dev->aux->irq = irq_mouse;
 	fibril_mutex_initialize(&dev->aux->buf_lock);
 	fibril_condvar_initialize(&dev->aux->buf_cv);
@@ -283,5 +289,5 @@
 	cap_irq_handle_t kbd_ihandle;
 	rc = register_interrupt_handler(ddf_dev, irq_kbd,
-	    i8042_irq_handler, &irq_code, &kbd_ihandle);
+	    i8042_irq_handler, (void *)dev, &irq_code, &kbd_ihandle);
 	if (rc != EOK) {
 		ddf_msg(LVL_ERROR, "Failed set handler for kbd: %s.",
@@ -292,5 +298,5 @@
 	cap_irq_handle_t mouse_ihandle;
 	rc = register_interrupt_handler(ddf_dev, irq_mouse,
-	    i8042_irq_handler, &irq_code, &mouse_ihandle);
+	    i8042_irq_handler, (void *)dev, &irq_code, &mouse_ihandle);
 	if (rc != EOK) {
 		ddf_msg(LVL_ERROR, "Failed set handler for mouse: %s.",
Index: uspace/drv/char/i8042/i8042.h
===================================================================
--- uspace/drv/char/i8042/i8042.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/char/i8042/i8042.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2006 Josef Cejka
  * Copyright (c) 2011 Jan Vesely
@@ -76,8 +76,12 @@
 	/** Signal new data in buffer */
 	fibril_condvar_t buf_cv;
+	/** Interrupt number */
+	int irq;
 } i8042_port_t;
 
 /** i8042 Controller. */
 typedef struct i8042 {
+	/** DDF device */
+	ddf_dev_t *dev;
 	/** I/O registers. */
 	i8042_regs_t *regs;
Index: uspace/drv/char/ns8250/ns8250.c
===================================================================
--- uspace/drv/char/ns8250/ns8250.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/char/ns8250/ns8250.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -783,10 +783,10 @@
  * data and reading the line status register.
  *
- * @param dev The serial port device.
- *
- */
-static inline void ns8250_interrupt_handler(ipc_call_t *icall, ddf_dev_t *dev)
-{
-	ns8250_t *ns = dev_ns8250(dev);
+ * @pram icall IRQ event notificatoin
+ * @param arg Argument (ns8250_t *)
+ */
+static inline void ns8250_interrupt_handler(ipc_call_t *icall, void *arg)
+{
+	ns8250_t *ns = (ns8250_t *)arg;
 	uint8_t iir = pio_read_8(&ns->regs->iid);
 	if ((iir & NS8250_IID_CAUSE_MASK) == NS8250_IID_CAUSE_RXSTATUS) {
@@ -809,5 +809,5 @@
 {
 	return register_interrupt_handler(ns->dev, ns->irq,
-	    ns8250_interrupt_handler, NULL, ihandle);
+	    ns8250_interrupt_handler, (void *)ns, NULL, ihandle);
 }
 
Index: uspace/drv/char/pl050/pl050.c
===================================================================
--- uspace/drv/char/pl050/pl050.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/char/pl050/pl050.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -143,7 +143,12 @@
 }
 
-static void pl050_interrupt(ipc_call_t *call, ddf_dev_t *dev)
-{
-	pl050_t *pl050 = (pl050_t *)ddf_dev_data_get(dev);
+/** PL050 interrupt handler
+ *
+ * @param call IRQ event notification
+ * @param arg Argument (pl050_t *)
+ */
+static void pl050_interrupt(ipc_call_t *call, void *arg)
+{
+	pl050_t *pl050 = (pl050_t *)arg;
 	size_t nidx;
 
@@ -220,5 +225,6 @@
 	cap_irq_handle_t ihandle;
 	rc = register_interrupt_handler(pl050->dev,
-	    res.irqs.irqs[0], pl050_interrupt, &pl050_irq_code, &ihandle);
+	    res.irqs.irqs[0], pl050_interrupt, (void *)pl050, &pl050_irq_code,
+	    &ihandle);
 	if (rc != EOK) {
 		ddf_msg(LVL_ERROR, "Failed registering interrupt handler. (%s)",
Index: uspace/drv/hid/adb-kbd/adb-kbd.c
===================================================================
--- uspace/drv/hid/adb-kbd/adb-kbd.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/hid/adb-kbd/adb-kbd.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -34,5 +34,5 @@
 #include <ddf/log.h>
 #include <errno.h>
-#include <io/console.h>
+#include <io/kbd_event.h>
 #include <ipc/adb.h>
 #include <ipc/kbdev.h>
Index: uspace/drv/hid/adb-kbd/ctl.c
===================================================================
--- uspace/drv/hid/adb-kbd/ctl.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/hid/adb-kbd/ctl.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -33,5 +33,5 @@
 
 #include <errno.h>
-#include <io/console.h>
+#include <io/kbd_event.h>
 #include <io/keycode.h>
 
Index: uspace/drv/hid/adb-kbd/meson.build
===================================================================
--- uspace/drv/hid/adb-kbd/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/hid/adb-kbd/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -27,4 +27,5 @@
 #
 
+deps = [ 'input' ]
 src = files(
 	'main.c',
Index: uspace/drv/hid/adb-mouse/meson.build
===================================================================
--- uspace/drv/hid/adb-mouse/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/hid/adb-mouse/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -27,3 +27,4 @@
 #
 
+deps = [ 'input' ]
 src = files('main.c', 'adb-mouse.c')
Index: uspace/drv/hid/atkbd/atkbd.c
===================================================================
--- uspace/drv/hid/atkbd/atkbd.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/hid/atkbd/atkbd.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -38,7 +38,7 @@
 #include <errno.h>
 #include <ddf/log.h>
+#include <io/kbd_event.h>
 #include <io/keycode.h>
 #include <io/chardev.h>
-#include <io/console.h>
 #include <ipc/kbdev.h>
 #include <abi/ipc/methods.h>
Index: uspace/drv/hid/atkbd/meson.build
===================================================================
--- uspace/drv/hid/atkbd/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/hid/atkbd/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -27,3 +27,4 @@
 #
 
+deps = [ 'input' ]
 src = files('main.c', 'atkbd.c')
Index: uspace/drv/hid/ps2mouse/meson.build
===================================================================
--- uspace/drv/hid/ps2mouse/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/hid/ps2mouse/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -27,3 +27,4 @@
 #
 
+deps = [ 'input' ]
 src = files('main.c', 'ps2mouse.c')
Index: uspace/drv/hid/ps2mouse/ps2mouse.c
===================================================================
--- uspace/drv/hid/ps2mouse/ps2mouse.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/hid/ps2mouse/ps2mouse.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -40,5 +40,4 @@
 #include <io/keycode.h>
 #include <io/chardev.h>
-#include <io/console.h>
 #include <ipc/mouseev.h>
 #include <abi/ipc/methods.h>
Index: uspace/drv/hid/usbhid/kbd/kbddev.c
===================================================================
--- uspace/drv/hid/usbhid/kbd/kbddev.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/hid/usbhid/kbd/kbddev.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -41,5 +41,5 @@
 
 #include <io/keycode.h>
-#include <io/console.h>
+#include <io/kbd_event.h>
 #include <abi/ipc/methods.h>
 #include <ipc/kbdev.h>
Index: uspace/drv/hid/usbhid/kbd/kbdrepeat.c
===================================================================
--- uspace/drv/hid/usbhid/kbd/kbdrepeat.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/hid/usbhid/kbd/kbdrepeat.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,6 +36,6 @@
 
 #include <fibril_synch.h>
+#include <io/kbd_event.h>
 #include <io/keycode.h>
-#include <io/console.h>
 #include <errno.h>
 
Index: uspace/drv/hid/usbhid/meson.build
===================================================================
--- uspace/drv/hid/usbhid/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/hid/usbhid/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -27,5 +27,5 @@
 #
 
-deps = [ 'usbhid', 'usbdev', 'usb' ]
+deps = [ 'usbhid', 'usbdev', 'usb', 'input' ]
 includes += [ include_directories('.') ]
 src = files(
Index: uspace/drv/hid/usbhid/mouse/mousedev.c
===================================================================
--- uspace/drv/hid/usbhid/mouse/mousedev.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/hid/usbhid/mouse/mousedev.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -46,5 +46,4 @@
 #include <str_error.h>
 #include <ipc/mouseev.h>
-#include <io/console.h>
 
 #include <ipc/kbdev.h>
Index: uspace/drv/hid/usbhid/multimedia/multimedia.c
===================================================================
--- uspace/drv/hid/usbhid/multimedia/multimedia.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/hid/usbhid/multimedia/multimedia.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -51,5 +51,5 @@
 
 #include <ipc/kbdev.h>
-#include <io/console.h>
+#include <io/kbd_event.h>
 
 #define NAME  "multimedia-keys"
Index: uspace/drv/hid/xtkbd/meson.build
===================================================================
--- uspace/drv/hid/xtkbd/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/hid/xtkbd/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -27,3 +27,4 @@
 #
 
+deps = [ 'input' ]
 src = files('main.c', 'xtkbd.c')
Index: uspace/drv/hid/xtkbd/xtkbd.c
===================================================================
--- uspace/drv/hid/xtkbd/xtkbd.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/hid/xtkbd/xtkbd.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,5 +1,5 @@
 /*
+ * Copyright (c) 2022 Jiri Svoboda
  * Copyright (c) 2011 Jan Vesely
- * Copyright (c) 2017 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,5 +39,5 @@
 #include <io/keycode.h>
 #include <io/chardev.h>
-#include <io/console.h>
+#include <io/kbd_event.h>
 #include <ipc/kbdev.h>
 #include <abi/ipc/methods.h>
@@ -132,4 +132,5 @@
 	[0x58] = KC_F12,
 
+	[0x54] = KC_SYSREQ,
 	[0x46] = KC_SCROLL_LOCK,
 
@@ -163,5 +164,6 @@
 	[0x1d] = KC_RCTRL,
 
-	[0x37] = KC_SYSREQ,
+	[0x37] = KC_PRTSCR,
+	[0x46] = KC_PAUSE,
 
 	[0x52] = KC_INSERT,
@@ -198,4 +200,27 @@
 }
 
+/** Get one scancode byte from the keyboard.
+ *
+ * @param kbd Keyboard
+ * @param code Place to store scancode byte
+ * @return EOK on success or an error code
+ */
+static errno_t xtkbd_get_code(xt_kbd_t *kbd, uint8_t *code)
+{
+	errno_t rc;
+	size_t nread;
+
+	*code = 0;
+	rc = chardev_read(kbd->chardev, code, 1, &nread, chardev_f_none);
+	if (rc != EOK) {
+		ddf_msg(LVL_WARN, "Error reading from keyboard device.");
+		return EIO;
+	}
+
+	ddf_msg(LVL_DEBUG, "Read scancode: 0x%02hhx", *code);
+
+	return EOK;
+}
+
 /** Get data and parse scancodes.
  *
@@ -207,14 +232,15 @@
 static errno_t polling(void *arg)
 {
+	kbd_event_type_t evtype;
+	unsigned int key;
+	size_t map_size;
 	xt_kbd_t *kbd = arg;
-	size_t nread;
+	uint8_t code;
+	uint8_t xcode1;
+	uint8_t xcode2;
 	errno_t rc;
 
 	while (true) {
-		const unsigned int *map = scanmap_simple;
-		size_t map_size = sizeof(scanmap_simple) / sizeof(unsigned int);
-
-		uint8_t code = 0;
-		rc = chardev_read(kbd->chardev, &code, 1, &nread, chardev_f_none);
+		rc = xtkbd_get_code(kbd, &code);
 		if (rc != EOK)
 			return EIO;
@@ -226,109 +252,59 @@
 		/* Extended set */
 		if (code == KBD_SCANCODE_SET_EXTENDED) {
-			map = scanmap_e0;
-			map_size = sizeof(scanmap_e0) / sizeof(unsigned int);
-
-			rc = chardev_read(kbd->chardev, &code, 1, &nread,
-			    chardev_f_none);
+			rc = xtkbd_get_code(kbd, &xcode1);
 			if (rc != EOK)
 				return EIO;
 
-			/* Handle really special keys */
-
-			if (code == 0x2a) {  /* Print Screen */
-				rc = chardev_read(kbd->chardev, &code, 1, &nread,
-				    chardev_f_none);
-				if (rc != EOK)
-					return EIO;
-
-				if (code != 0xe0)
-					continue;
-
-				rc = chardev_read(kbd->chardev, &code, 1, &nread,
-				    chardev_f_none);
-				if (rc != EOK)
-					return EIO;
-
-				if (code == 0x37)
-					push_event(kbd->client_sess, KEY_PRESS, KC_PRTSCR);
-
+			if ((xcode1 & 0x7f) == 0x2a)  /* Fake shift */
+				continue;
+
+			/* Bit 7 indicates press/release */
+			evtype = (xcode1 & 0x80) ? KEY_RELEASE : KEY_PRESS;
+
+			map_size = sizeof(scanmap_e0) / sizeof(unsigned int);
+			key = (xcode1 & 0x7f) < map_size ?
+			    scanmap_e0[xcode1 & 0x7f] : 0;
+
+			if (key != 0)
+				push_event(kbd->client_sess, evtype, key);
+			else
+				ddf_msg(LVL_WARN, "Unknown scancode: e0 %hhx", xcode1);
+
+			continue;
+		}
+
+		/* Extended special set */
+		if (code == KBD_SCANCODE_SET_EXTENDED_SPECIAL) {
+			rc = xtkbd_get_code(kbd, &xcode1);
+			if (rc != EOK)
+				return EIO;
+
+			rc = xtkbd_get_code(kbd, &xcode2);
+			if (rc != EOK)
+				return EIO;
+
+			if ((xcode1 & 0x80) == (xcode2 & 0x80) &&
+			    (xcode1 & 0x7f) == 0x1d && (xcode2 & 0x7f) == 0x45) {
+				/* Pause */
+				evtype = (xcode1 & 0x80) ? KEY_RELEASE :
+				    KEY_PRESS;
+				push_event(kbd->client_sess, evtype, KC_PAUSE);
 				continue;
 			}
 
-			if (code == 0x46) {  /* Break */
-				rc = chardev_read(kbd->chardev, &code, 1, &nread,
-				    chardev_f_none);
-				if (rc != EOK)
-					return EIO;
-
-				if (code != 0xe0)
-					continue;
-
-				rc = chardev_read(kbd->chardev, &code, 1, &nread,
-				    chardev_f_none);
-				if (rc != EOK)
-					return EIO;
-
-				if (code == 0xc6)
-					push_event(kbd->client_sess, KEY_PRESS, KC_BREAK);
-
-				continue;
-			}
-		}
-
-		/* Extended special set */
-		if (code == KBD_SCANCODE_SET_EXTENDED_SPECIAL) {
-			rc = chardev_read(kbd->chardev, &code, 1, &nread,
-			    chardev_f_none);
-			if (rc != EOK)
-				return EIO;
-
-			if (code != 0x1d)
-				continue;
-
-			rc = chardev_read(kbd->chardev, &code, 1, &nread,
-			    chardev_f_none);
-			if (rc != EOK)
-				return EIO;
-
-			if (code != 0x45)
-				continue;
-
-			rc = chardev_read(kbd->chardev, &code, 1, &nread,
-			    chardev_f_none);
-			if (rc != EOK)
-				return EIO;
-
-			if (code != 0xe1)
-				continue;
-
-			rc = chardev_read(kbd->chardev, &code, 1, &nread,
-			    chardev_f_none);
-			if (rc != EOK)
-				return EIO;
-
-			if (code != 0x9d)
-				continue;
-
-			rc = chardev_read(kbd->chardev, &code, 1, &nread,
-			    chardev_f_none);
-			if (rc != EOK)
-				return EIO;
-
-			if (code == 0xc5)
-				push_event(kbd->client_sess, KEY_PRESS, KC_PAUSE);
-
+			ddf_msg(LVL_WARN, "Unknown scancode: e1 %hhx %hhx",
+			    xcode1, xcode2);
 			continue;
 		}
 
 		/* Bit 7 indicates press/release */
-		const kbd_event_type_t type =
-		    (code & 0x80) ? KEY_RELEASE : KEY_PRESS;
-		code &= ~0x80;
-
-		const unsigned int key = (code < map_size) ? map[code] : 0;
+		evtype = (code & 0x80) ? KEY_RELEASE : KEY_PRESS;
+
+		map_size = sizeof(scanmap_simple) / sizeof(unsigned int);
+		key = (code & 0x7f) < map_size ?
+		    scanmap_simple[code & 0x7f] : 0;
 
 		if (key != 0)
-			push_event(kbd->client_sess, type, key);
+			push_event(kbd->client_sess, evtype, key);
 		else
 			ddf_msg(LVL_WARN, "Unknown scancode: %hhx", code);
Index: uspace/drv/meson.build
===================================================================
--- uspace/drv/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -33,6 +33,7 @@
 	'root/virt',
 	'block/ahci',
-	'block/ata_bd',
 	'block/ddisk',
+	'block/isa-ide',
+	'block/pci-ide',
 	'block/usbmast',
 	'block/virtio-blk',
Index: uspace/drv/nic/ar9271/wmi.c
===================================================================
--- uspace/drv/nic/ar9271/wmi.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/nic/ar9271/wmi.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -230,4 +230,8 @@
 	size_t buffer_size = header_size + command_length;
 	void *buffer = malloc(buffer_size);
+	if (buffer == NULL) {
+		usb_log_error("Failed to allocate WMI message buffer (out of memory).\n");
+		return ENOMEM;
+	}
 
 	if (command_buffer != NULL)
@@ -241,14 +245,12 @@
 	    host2uint16_t_be(++htc_device->sequence_number);
 
-	/* Send message. */
+	/* Send message (buffer will not be needed afterwards regardless of result). */
 	errno_t rc = htc_send_control_message(htc_device, buffer, buffer_size,
 	    htc_device->endpoints.wmi_endpoint);
-	if (rc != EOK) {
-		free(buffer);
+	free(buffer);
+	if (rc != EOK) {
 		usb_log_error("Failed to send WMI message. Error: %s\n", str_error_name(rc));
 		return rc;
 	}
-
-	free(buffer);
 
 	bool clean_resp_buffer = false;
@@ -267,5 +269,4 @@
 		    response_buffer_size, NULL);
 		if (rc != EOK) {
-			free(buffer);
 			usb_log_error("Failed to receive WMI message response. "
 			    "Error: %s\n", str_error_name(rc));
@@ -275,5 +276,4 @@
 		if (response_buffer_size < sizeof(htc_frame_header_t) +
 		    sizeof(wmi_command_header_t)) {
-			free(buffer);
 			usb_log_error("Corrupted response received.\n");
 			return EINVAL;
Index: uspace/drv/nic/e1k/e1k.c
===================================================================
--- uspace/drv/nic/e1k/e1k.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/nic/e1k/e1k.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1244,12 +1244,11 @@
  *
  * @param icall IPC call structure
- * @param dev   E1000 device
- *
- */
-static void e1000_interrupt_handler(ipc_call_t *icall,
-    ddf_dev_t *dev)
+ * @param arg   Argument (nic_t *)
+ *
+ */
+static void e1000_interrupt_handler(ipc_call_t *icall, void *arg)
 {
 	uint32_t icr = (uint32_t) ipc_get_arg2(icall);
-	nic_t *nic = NIC_DATA_DEV(dev);
+	nic_t *nic = (nic_t *)arg;
 	e1000_t *e1000 = DRIVER_DATA_NIC(nic);
 
@@ -1283,5 +1282,5 @@
 
 	errno_t rc = register_interrupt_handler(nic_get_ddf_dev(nic), e1000->irq,
-	    e1000_interrupt_handler, &e1000_irq_code, handle);
+	    e1000_interrupt_handler, (void *)nic, &e1000_irq_code, handle);
 
 	fibril_mutex_unlock(&irq_reg_mutex);
Index: uspace/drv/nic/ne2k/ne2k.c
===================================================================
--- uspace/drv/nic/ne2k/ne2k.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/nic/ne2k/ne2k.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -121,5 +121,5 @@
 };
 
-static void ne2k_interrupt_handler(ipc_call_t *, ddf_dev_t *);
+static void ne2k_interrupt_handler(ipc_call_t *, void *);
 
 static errno_t ne2k_register_interrupt(nic_t *nic_data,
@@ -161,5 +161,6 @@
 
 	return register_interrupt_handler(nic_get_ddf_dev(nic_data),
-	    ne2k->irq, ne2k_interrupt_handler, &ne2k->code, handle);
+	    ne2k->irq, ne2k_interrupt_handler, (void *)nic_data, &ne2k->code,
+	    handle);
 }
 
@@ -237,7 +238,12 @@
 }
 
-void ne2k_interrupt_handler(ipc_call_t *call, ddf_dev_t *dev)
-{
-	nic_t *nic_data = DRIVER_DATA(dev);
+/** NE2K interrupt handler
+ *
+ * @param call IRQ event notification
+ * @param arg Argument (nic_t *)
+ */
+void ne2k_interrupt_handler(ipc_call_t *call, void *arg)
+{
+	nic_t *nic_data = (nic_t *)arg;
 	ne2k_interrupt(nic_data, IRQ_GET_ISR(*call), IRQ_GET_TSR(*call));
 }
Index: uspace/drv/nic/rtl8139/driver.c
===================================================================
--- uspace/drv/nic/rtl8139/driver.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/nic/rtl8139/driver.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -819,14 +819,11 @@
  *
  * @param icall  The IPC call structure
- * @param dev    The rtl8139 device
- *
- */
-static void rtl8139_interrupt_handler(ipc_call_t *icall, ddf_dev_t *dev)
-{
-	assert(dev);
-	assert(icall);
-
+ * @param arg    Argument (nic_t *)
+ *
+ */
+static void rtl8139_interrupt_handler(ipc_call_t *icall, void *arg)
+{
 	uint16_t isr = (uint16_t) ipc_get_arg2(icall);
-	nic_t *nic_data = nic_get_from_ddf_dev(dev);
+	nic_t *nic_data = (nic_t *)arg;
 	rtl8139_t *rtl8139 = nic_get_specific(nic_data);
 
@@ -861,5 +858,6 @@
 	rtl8139_irq_code.cmds[3].addr = rtl8139->io_addr + IMR;
 	errno_t rc = register_interrupt_handler(nic_get_ddf_dev(nic_data),
-	    rtl8139->irq, rtl8139_interrupt_handler, &rtl8139_irq_code, handle);
+	    rtl8139->irq, rtl8139_interrupt_handler, (void *)nic_data,
+	    &rtl8139_irq_code, handle);
 
 	RTL8139_IRQ_STRUCT_UNLOCK();
Index: uspace/drv/nic/rtl8169/driver.c
===================================================================
--- uspace/drv/nic/rtl8169/driver.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/nic/rtl8169/driver.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -74,5 +74,5 @@
 static errno_t rtl8169_on_stopped(nic_t *nic_data);
 static void rtl8169_send_frame(nic_t *nic_data, void *data, size_t size);
-static void rtl8169_irq_handler(ipc_call_t *icall, ddf_dev_t *dev);
+static void rtl8169_irq_handler(ipc_call_t *icall, void *);
 static inline errno_t rtl8169_register_int_handler(nic_t *nic_data,
     cap_irq_handle_t *handle);
@@ -372,5 +372,6 @@
 	rtl8169_irq_code.cmds[3].addr = rtl8169->regs + IMR;
 	errno_t rc = register_interrupt_handler(nic_get_ddf_dev(nic_data),
-	    rtl8169->irq, rtl8169_irq_handler, &rtl8169_irq_code, handle);
+	    rtl8169->irq, rtl8169_irq_handler, (void *)rtl8169,
+	    &rtl8169_irq_code, handle);
 
 	return rc;
@@ -1033,12 +1034,13 @@
 }
 
-static void rtl8169_irq_handler(ipc_call_t *icall, ddf_dev_t *dev)
-{
-	assert(dev);
-	assert(icall);
-
+/** RTL8169 IRQ handler.
+ *
+ * @param icall IRQ event notification
+ * @param arg Argument (rtl8169_t *)
+ */
+static void rtl8169_irq_handler(ipc_call_t *icall, void *arg)
+{
 	uint16_t isr = (uint16_t) ipc_get_arg2(icall) & INT_KNOWN;
-	nic_t *nic_data = nic_get_from_ddf_dev(dev);
-	rtl8169_t *rtl8169 = nic_get_specific(nic_data);
+	rtl8169_t *rtl8169 = (rtl8169_t *)arg;
 
 	ddf_msg(LVL_DEBUG, "rtl8169_irq_handler(): isr=0x%04x", isr);
@@ -1050,5 +1052,5 @@
 		/* Packet underrun or link change */
 		if (isr & INT_PUN) {
-			rtl8169_link_change(dev);
+			rtl8169_link_change(rtl8169->dev);
 			pio_write_16(rtl8169->regs + ISR, INT_PUN);
 		}
@@ -1056,5 +1058,5 @@
 		/* Transmit notification */
 		if (isr & (INT_TER | INT_TOK | INT_TDU)) {
-			rtl8169_transmit_done(dev);
+			rtl8169_transmit_done(rtl8169->dev);
 			pio_write_16(rtl8169->regs + ISR, (INT_TER | INT_TOK | INT_TDU));
 		}
@@ -1072,5 +1074,5 @@
 
 		if (isr & (INT_RER | INT_ROK)) {
-			rtl8169_receive_done(dev);
+			rtl8169_receive_done(rtl8169->dev);
 			pio_write_16(rtl8169->regs + ISR, (INT_RER | INT_ROK));
 		}
Index: uspace/drv/nic/virtio-net/virtio-net.c
===================================================================
--- uspace/drv/nic/virtio-net/virtio-net.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/drv/nic/virtio-net/virtio-net.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -70,7 +70,12 @@
 };
 
-static void virtio_net_irq_handler(ipc_call_t *icall, ddf_dev_t *dev)
-{
-	nic_t *nic = ddf_dev_data_get(dev);
+/** VirtIO net IRQ handler.
+ *
+ * @param icall IRQ event notification
+ * @param arg Argument (nic_t *)
+ */
+static void virtio_net_irq_handler(ipc_call_t *icall, void *arg)
+{
+	nic_t *nic = (nic_t *)arg;
 	virtio_net_t *virtio_net = nic_get_specific(nic);
 	virtio_dev_t *vdev = &virtio_net->virtio_dev;
@@ -164,5 +169,6 @@
 
 	return register_interrupt_handler(dev, virtio_net->irq,
-	    virtio_net_irq_handler, &irq_code, &virtio_net->irq_handle);
+	    virtio_net_irq_handler, (void *)nic, &irq_code,
+	    &virtio_net->irq_handle);
 }
 
Index: uspace/lib/ata/include/ata/ata.h
===================================================================
--- uspace/lib/ata/include/ata/ata.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ata/include/ata/ata.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,209 @@
+/*
+ * 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 libata
+ * @{
+ */
+/** @file ATA driver library definitions.
+ */
+
+#ifndef LIBATA_ATA_H
+#define LIBATA_ATA_H
+
+#include <async.h>
+#include <bd_srv.h>
+#include <fibril_synch.h>
+#include <stdbool.h>
+#include <str.h>
+#include <stdint.h>
+#include <stddef.h>
+#include "ata_hw.h"
+
+struct ata_device;
+
+/** ATA DMA direction */
+typedef enum {
+	/** DMA read */
+	ata_dma_read,
+	/** DMA write */
+	ata_dma_write
+} ata_dma_dir_t;
+
+/** ATA channel creation parameters */
+typedef struct {
+	/** Argument to callback functions */
+	void *arg;
+	/** IRQ is available */
+	bool have_irq;
+	/** Use DMA transfers */
+	bool use_dma;
+	/** Maximum number of bytes that we can DMA in one I/O operation */
+	size_t max_dma_xfer;
+	/** Read 16 bits from the data port */
+	void (*write_data_16)(void *, uint16_t *, size_t);
+	/** Write 16 bits to the data port */
+	void (*read_data_16)(void *, uint16_t *, size_t);
+	/** Read 8 bits from an 8-bit command register */
+	void (*write_cmd_8)(void *, uint16_t, uint8_t);
+	/** Writes 8 bits to an 8-bit command register */
+	uint8_t (*read_cmd_8)(void *, uint16_t);
+	/** Read 8 bits from a control register */
+	void (*write_ctl_8)(void *, uint16_t, uint8_t);
+	/** Write 8 bits to a control register */
+	uint8_t (*read_ctl_8)(void *, uint16_t);
+	/** Enable interrupts */
+	errno_t (*irq_enable)(void *);
+	/** Disable interrupts */
+	errno_t (*irq_disable)(void *);
+	/** Set up DMA channel */
+	void (*dma_chan_setup)(void *, void *, size_t, ata_dma_dir_t);
+	/** Set up DMA channel */
+	void (*dma_chan_teardown)(void *);
+	/** Add new device */
+	errno_t (*add_device)(void *, unsigned, void *);
+	/** Remove device */
+	errno_t (*remove_device)(void *, unsigned);
+	/** Log notice message */
+	void (*msg_note)(void *, char *);
+	/** Log error message */
+	void (*msg_error)(void *, char *);
+	/** Log warning message */
+	void (*msg_warn)(void *, char *);
+	/** Log debug message */
+	void (*msg_debug)(void *, char *);
+} ata_params_t;
+
+/** Timeout definitions. Unit is 10 ms. */
+enum ata_timeout {
+	TIMEOUT_PROBE	=  100, /*  1 s */
+	TIMEOUT_BSY	=  100, /*  1 s */
+	TIMEOUT_DRDY	= 1000  /* 10 s */
+};
+
+enum ata_dev_type {
+	ata_reg_dev,	/* Register device (no packet feature set support) */
+	ata_pkt_dev	/* Packet device (supports packet feature set). */
+};
+
+/** Register device block addressing mode. */
+enum rd_addr_mode {
+	am_chs,		/**< CHS block addressing */
+	am_lba28,	/**< LBA-28 block addressing */
+	am_lba48	/**< LBA-48 block addressing */
+};
+
+/** Block coordinates */
+typedef struct {
+	enum rd_addr_mode amode;
+
+	union {
+		/** CHS coordinates */
+		struct {
+			uint8_t sector;
+			uint8_t cyl_lo;
+			uint8_t cyl_hi;
+		};
+		/** LBA coordinates */
+		struct {
+			uint8_t c0;
+			uint8_t c1;
+			uint8_t c2;
+			uint8_t c3;
+			uint8_t c4;
+			uint8_t c5;
+		};
+	};
+
+	/** Lower 4 bits for device/head register */
+	uint8_t h;
+} block_coord_t;
+
+/** ATA device state structure. */
+typedef struct ata_device {
+	bool present;
+	struct ata_channel *chan;
+
+	/** Device type */
+	enum ata_dev_type dev_type;
+
+	/** Addressing mode to use (if register device) */
+	enum rd_addr_mode amode;
+
+	/*
+	 * Geometry. Only valid if operating in CHS mode.
+	 */
+	struct {
+		unsigned heads;
+		unsigned cylinders;
+		unsigned sectors;
+	} geom;
+
+	uint64_t blocks;
+	size_t block_size;
+
+	char model[STR_BOUNDS(40) + 1];
+
+	int device_id;
+	bd_srvs_t bds;
+} ata_device_t;
+
+/** ATA channel */
+typedef struct ata_channel {
+	/** Parameters */
+	ata_params_t params;
+
+	/** Command registers */
+	ata_cmd_t *cmd;
+	/** Control registers */
+	ata_ctl_t *ctl;
+
+	/** Per-device state. */
+	ata_device_t device[MAX_DEVICES];
+
+	/** Synchronize channel access */
+	fibril_mutex_t lock;
+	/** Synchronize access to irq_fired/irq_status */
+	fibril_mutex_t irq_lock;
+	/** Signalled by IRQ handler */
+	fibril_condvar_t irq_cv;
+	/** Set to true when interrupt occurs */
+	bool irq_fired;
+	/** Value of status register read by interrupt handler */
+	uint8_t irq_status;
+} ata_channel_t;
+
+extern errno_t ata_channel_create(ata_params_t *, ata_channel_t **);
+extern errno_t ata_channel_initialize(ata_channel_t *);
+extern errno_t ata_channel_destroy(ata_channel_t *);
+extern void ata_channel_irq(ata_channel_t *, uint8_t);
+extern void ata_connection(ipc_call_t *, ata_device_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ata/include/ata/ata_hw.h
===================================================================
--- uspace/lib/ata/include/ata/ata_hw.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ata/include/ata/ata_hw.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,272 @@
+/*
+ * 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 libata
+ * @{
+ */
+/** @file ATA hardware protocol (registers, data structures).
+ */
+
+#ifndef LIBATA_ATA_HW_H
+#define LIBATA_ATA_HW_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+enum {
+	CTL_READ_START  = 0,
+	CTL_WRITE_START = 1,
+};
+
+enum {
+	STATUS_FAILURE = 0
+};
+
+enum {
+	MAX_DEVICES	= 2
+};
+
+/** ATA Command Register Block. */
+typedef union {
+	/* Read/Write */
+	struct {
+		uint16_t data_port;
+		uint8_t sector_count;
+		uint8_t sector_number;
+		uint8_t cylinder_low;
+		uint8_t cylinder_high;
+		uint8_t drive_head;
+		uint8_t pad_rw0;
+	};
+
+	/* Read Only */
+	struct {
+		uint8_t pad_ro0;
+		uint8_t error;
+		uint8_t pad_ro1[5];
+		uint8_t status;
+	};
+
+	/* Write Only */
+	struct {
+		uint8_t pad_wo0;
+		uint8_t features;
+		uint8_t pad_wo1[5];
+		uint8_t command;
+	};
+} ata_cmd_t;
+
+typedef union {
+	/* Read */
+	struct {
+		uint8_t pad0[6];
+		uint8_t alt_status;
+		uint8_t drive_address;
+	};
+
+	/* Write */
+	struct {
+		uint8_t pad1[6];
+		uint8_t device_control;
+		uint8_t pad2;
+	};
+} ata_ctl_t;
+
+#define REG_SECTOR_COUNT offsetof(ata_cmd_t, sector_count)
+#define REG_SECTOR_NUMBER offsetof(ata_cmd_t, sector_number)
+#define REG_CYLINDER_LOW offsetof(ata_cmd_t, cylinder_low)
+#define REG_CYLINDER_HIGH offsetof(ata_cmd_t, cylinder_high)
+#define REG_DRIVE_HEAD offsetof(ata_cmd_t, drive_head)
+#define REG_STATUS offsetof(ata_cmd_t, status)
+#define REG_COMMAND offsetof(ata_cmd_t, command)
+#define REG_FEATURES offsetof(ata_cmd_t, features)
+
+enum devctl_bits {
+	DCR_SRST	= 0x04, /**< Software Reset */
+	DCR_nIEN	= 0x02  /**< Interrupt Enable (negated) */
+};
+
+enum status_bits {
+	SR_BSY		= 0x80, /**< Busy */
+	SR_DRDY		= 0x40, /**< Drive Ready */
+	SR_DWF		= 0x20, /**< Drive Write Fault */
+	SR_DSC		= 0x10, /**< Drive Seek Complete */
+	SR_DRQ		= 0x08, /**< Data Request */
+	SR_CORR		= 0x04, /**< Corrected Data */
+	SR_IDX		= 0x02, /**< Index */
+	SR_ERR		= 0x01  /**< Error */
+};
+
+enum drive_head_bits {
+	DHR_LBA		= 0x40,	/**< Use LBA addressing mode */
+	DHR_DRV		= 0x10	/**< Select device 1 */
+};
+
+enum error_bits {
+	ER_BBK		= 0x80, /**< Bad Block Detected */
+	ER_UNC		= 0x40, /**< Uncorrectable Data Error */
+	ER_MC		= 0x20, /**< Media Changed */
+	ER_IDNF		= 0x10, /**< ID Not Found */
+	ER_MCR		= 0x08, /**< Media Change Request */
+	ER_ABRT		= 0x04, /**< Aborted Command */
+	ER_TK0NF	= 0x02, /**< Track 0 Not Found */
+	ER_AMNF		= 0x01  /**< Address Mark Not Found */
+};
+
+enum ata_command {
+	CMD_READ_SECTORS	= 0x20,
+	CMD_READ_SECTORS_EXT	= 0x24,
+	CMD_READ_DMA_EXT	= 0x25,
+	CMD_WRITE_SECTORS	= 0x30,
+	CMD_WRITE_SECTORS_EXT	= 0x34,
+	CMD_WRITE_DMA_EXT	= 0x35,
+	CMD_PACKET		= 0xA0,
+	CMD_IDENTIFY_PKT_DEV	= 0xA1,
+	CMD_READ_DMA		= 0xC8,
+	CMD_WRITE_DMA		= 0xCA,
+	CMD_IDENTIFY_DRIVE	= 0xEC,
+	CMD_FLUSH_CACHE		= 0xE7
+};
+
+/** Data returned from identify device and identify packet device command. */
+typedef struct {
+	uint16_t gen_conf;
+	uint16_t cylinders;
+	uint16_t _res2;
+	uint16_t heads;
+	uint16_t _vs4;
+	uint16_t _vs5;
+	uint16_t sectors;
+	uint16_t _vs7;
+	uint16_t _vs8;
+	uint16_t _vs9;
+
+	uint16_t serial_number[10];
+	uint16_t _vs20;
+	uint16_t _vs21;
+	uint16_t vs_bytes;
+	uint16_t firmware_rev[4];
+	uint16_t model_name[20];
+
+	uint16_t max_rw_multiple;
+	uint16_t _res48;
+	uint16_t caps;		/* Different meaning for packet device */
+	uint16_t _res50;
+	uint16_t pio_timing;
+	uint16_t dma_timing;
+
+	uint16_t validity;
+	uint16_t cur_cyl;
+	uint16_t cur_heads;
+	uint16_t cur_sectors;
+	uint16_t cur_capacity0;
+	uint16_t cur_capacity1;
+	uint16_t mss;
+	uint16_t total_lba28_0;
+	uint16_t total_lba28_1;
+	uint16_t sw_dma;
+	uint16_t mw_dma;
+	uint16_t pio_modes;
+	uint16_t min_mw_dma_cycle;
+	uint16_t rec_mw_dma_cycle;
+	uint16_t min_raw_pio_cycle;
+	uint16_t min_iordy_pio_cycle;
+
+	uint16_t _res69;
+	uint16_t _res70;
+	uint16_t _res71;
+	uint16_t _res72;
+	uint16_t _res73;
+	uint16_t _res74;
+
+	uint16_t queue_depth;
+	uint16_t _res76[1 + 79 - 76];
+	uint16_t version_maj;
+	uint16_t version_min;
+	uint16_t cmd_set0;
+	uint16_t cmd_set1;
+	uint16_t csf_sup_ext;
+	uint16_t csf_enabled0;
+	uint16_t csf_enabled1;
+	uint16_t csf_default;
+	uint16_t udma;
+
+	uint16_t _res89[1 + 99 - 89];
+
+	/* Total number of blocks in LBA-48 addressing */
+	uint16_t total_lba48_0;
+	uint16_t total_lba48_1;
+	uint16_t total_lba48_2;
+	uint16_t total_lba48_3;
+
+	/* Note: more fields are defined in ATA/ATAPI-7 */
+	uint16_t _res104[1 + 127 - 104];
+	uint16_t _vs128[1 + 159 - 128];
+	uint16_t _res160[1 + 255 - 160];
+} identify_data_t;
+
+/** Capability bits for register device. */
+enum ata_regdev_caps {
+	rd_cap_iordy		= 0x0800,
+	rd_cap_iordy_cbd	= 0x0400,
+	rd_cap_lba		= 0x0200,
+	rd_cap_dma		= 0x0100
+};
+
+/** Capability bits for packet device. */
+enum ata_pktdev_caps {
+	pd_cap_ildma		= 0x8000,
+	pd_cap_cmdqueue		= 0x4000,
+	pd_cap_overlap		= 0x2000,
+	pd_cap_need_softreset	= 0x1000,	/* Obsolete (ATAPI-6) */
+	pd_cap_iordy		= 0x0800,
+	pd_cap_iordy_dis	= 0x0400,
+	pd_cap_lba		= 0x0200,	/* Must be on */
+	pd_cap_dma		= 0x0100
+};
+
+/** Bits of @c identify_data_t.cmd_set1 */
+enum ata_cs1 {
+	cs1_addr48	= 0x0400	/**< 48-bit address feature set */
+};
+
+/** Extract value of device type from scsi_std_inquiry_data_t.pqual_devtype */
+#define INQUIRY_PDEV_TYPE(val) ((val) & 0x1f)
+
+enum ata_pdev_signature {
+	/**
+	 * Signature put by a packet device in byte count register
+	 * in response to Identify command.
+	 */
+	PDEV_SIGNATURE_BC	= 0xEB14
+};
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ata/meson.build
===================================================================
--- uspace/lib/ata/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ata/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,32 @@
+#
+# 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.
+#
+
+deps = [ 'device', 'scsi' ]
+src = files(
+	'src/ata.c'
+)
Index: uspace/lib/ata/src/ata.c
===================================================================
--- uspace/lib/ata/src/ata.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ata/src/ata.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,1738 @@
+/*
+ * 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 libata
+ * @{
+ */
+
+/**
+ * @file
+ * @brief ATA disk driver library
+ *
+ * This driver library implements the generic part of ATA/ATAPI. It is
+ * meant to be used by a driver which implements the actual transport
+ * (such as ISA, PCI).
+ *
+ * This driver libary supports CHS, 28-bit and 48-bit LBA addressing,
+ * as well as PACKET devices. It supports PIO transfers and IRQ.
+ *
+ * There is no support DMA, S.M.A.R.T or removable devices.
+ *
+ * This driver is based on the ATA-1, ATA-2, ATA-3 and ATA/ATAPI-4 through 7
+ * standards, as published by the ANSI, NCITS and INCITS standards bodies,
+ * which are freely available. This driver contains no vendor-specific
+ * code at this moment.
+ */
+
+#include <bd_srv.h>
+#include <byteorder.h>
+#include <errno.h>
+#include <macros.h>
+#include <scsi/mmc.h>
+#include <scsi/sbc.h>
+#include <scsi/spc.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "ata/ata.h"
+#include "ata/ata_hw.h"
+
+#define MSG_BUF_SIZE 256
+
+/**
+ * Size of data returned from Identify Device or Identify Packet Device
+ * command.
+ */
+static const size_t identify_data_size = 512;
+static void ata_msg_note(ata_channel_t *, const char *, ...);
+static void ata_msg_debug(ata_channel_t *, const char *, ...);
+static void ata_msg_warn(ata_channel_t *, const char *, ...);
+static void ata_msg_error(ata_channel_t *, const char *, ...);
+static errno_t ata_device_add(ata_device_t *);
+static errno_t ata_device_remove(ata_device_t *);
+static void ata_read_data_16(ata_channel_t *, uint16_t *, size_t);
+static void ata_write_data_16(ata_channel_t *, uint16_t *, size_t);
+static uint8_t ata_read_cmd_8(ata_channel_t *, uint16_t);
+static void ata_write_cmd_8(ata_channel_t *, uint16_t, uint8_t);
+
+static errno_t ata_bd_init_irq(ata_channel_t *);
+static void ata_bd_fini_irq(ata_channel_t *);
+
+static errno_t ata_bd_open(bd_srvs_t *, bd_srv_t *);
+static errno_t ata_bd_close(bd_srv_t *);
+static errno_t ata_bd_read_blocks(bd_srv_t *, uint64_t, size_t, void *, size_t);
+static errno_t ata_bd_read_toc(bd_srv_t *, uint8_t, void *, size_t);
+static errno_t ata_bd_write_blocks(bd_srv_t *, uint64_t, size_t, const void *,
+    size_t);
+static errno_t ata_bd_get_block_size(bd_srv_t *, size_t *);
+static errno_t ata_bd_get_num_blocks(bd_srv_t *, aoff64_t *);
+static errno_t ata_bd_sync_cache(bd_srv_t *, aoff64_t, size_t);
+
+static errno_t ata_rcmd_read(ata_device_t *, uint64_t, size_t, void *);
+static errno_t ata_rcmd_write(ata_device_t *, uint64_t, size_t,
+    const void *);
+static errno_t ata_rcmd_flush_cache(ata_device_t *);
+static errno_t ata_device_init(ata_channel_t *, ata_device_t *, int);
+static errno_t ata_identify_dev(ata_device_t *, void *);
+static errno_t ata_identify_pkt_dev(ata_device_t *, void *);
+static errno_t ata_cmd_packet(ata_device_t *, const void *, size_t, void *,
+    size_t, size_t *);
+static errno_t ata_pcmd_inquiry(ata_device_t *, void *, size_t, size_t *);
+static errno_t ata_pcmd_read_12(ata_device_t *, uint64_t, size_t, void *, size_t);
+static errno_t ata_pcmd_read_capacity(ata_device_t *, uint64_t *, size_t *);
+static errno_t ata_pcmd_read_toc(ata_device_t *, uint8_t, void *, size_t);
+static void disk_print_summary(ata_device_t *);
+static size_t ata_disk_maxnb(ata_device_t *);
+static errno_t coord_calc(ata_device_t *, uint64_t, block_coord_t *);
+static void coord_sc_program(ata_channel_t *, const block_coord_t *, uint16_t);
+static errno_t wait_status(ata_channel_t *, unsigned, unsigned, uint8_t *,
+    unsigned);
+static errno_t wait_irq(ata_channel_t *, uint8_t *, unsigned);
+static void ata_dma_chan_setup(ata_device_t *, void *, size_t, ata_dma_dir_t);
+static void ata_dma_chan_teardown(ata_device_t *);
+
+static bd_ops_t ata_bd_ops = {
+	.open = ata_bd_open,
+	.close = ata_bd_close,
+	.read_blocks = ata_bd_read_blocks,
+	.read_toc = ata_bd_read_toc,
+	.write_blocks = ata_bd_write_blocks,
+	.get_block_size = ata_bd_get_block_size,
+	.get_num_blocks = ata_bd_get_num_blocks,
+	.sync_cache = ata_bd_sync_cache
+};
+
+static ata_device_t *bd_srv_device(bd_srv_t *bd)
+{
+	return (ata_device_t *)bd->srvs->sarg;
+}
+
+static int disk_dev_idx(ata_device_t *device)
+{
+	return (device->device_id & 1);
+}
+
+/** Create ATA channel.
+ *
+ * @param params Channel creation parameters
+ * @param rchan Place to store pointer to new channel
+ * @return EOK on success or an error code
+ */
+errno_t ata_channel_create(ata_params_t *params, ata_channel_t **rchan)
+{
+	ata_channel_t *chan;
+
+	chan = calloc(1, sizeof(ata_channel_t));
+	if (chan == NULL)
+		return ENOMEM;
+
+	chan->params = *params;
+	ata_msg_debug(chan, "ata_channel_create()");
+
+	fibril_mutex_initialize(&chan->lock);
+	fibril_mutex_initialize(&chan->irq_lock);
+	fibril_condvar_initialize(&chan->irq_cv);
+
+	*rchan = chan;
+	return EOK;
+}
+
+/** Initialize ATA channel.
+ *
+ * @param params Channel creation parameters
+ * @param rchan Place to store pointer to new channel
+ * @return EOK on success or an error code
+ */
+errno_t ata_channel_initialize(ata_channel_t *chan)
+{
+	int i;
+	errno_t rc;
+	int n_disks;
+	bool irq_inited = false;
+	bool dev_added[MAX_DEVICES];
+
+	for (i = 0; i < MAX_DEVICES; i++)
+		dev_added[i] = false;
+
+	ata_msg_debug(chan, "ata_channel_initialize()");
+
+	rc = ata_bd_init_irq(chan);
+	if (rc != EOK)
+		return rc;
+
+	irq_inited = true;
+
+	for (i = 0; i < MAX_DEVICES; i++) {
+		ata_msg_debug(chan, "Identify drive %d...", i);
+
+		rc = ata_device_init(chan, &chan->device[i], i);
+
+		if (rc == EOK) {
+			disk_print_summary(&chan->device[i]);
+		} else {
+			ata_msg_debug(chan, "Not found.");
+		}
+	}
+
+	n_disks = 0;
+
+	for (i = 0; i < MAX_DEVICES; i++) {
+		/* Skip unattached devices. */
+		if (chan->device[i].present == false)
+			continue;
+
+		rc = ata_device_add(&chan->device[i]);
+		if (rc != EOK) {
+			ata_msg_error(chan, "Unable to add device %d.", i);
+			goto error;
+		}
+		dev_added[i] = true;
+		++n_disks;
+	}
+
+	if (n_disks == 0) {
+		ata_msg_warn(chan, "No devices detected.");
+		rc = ENOENT;
+		goto error;
+	}
+
+	return EOK;
+error:
+	for (i = 0; i < MAX_DEVICES; i++) {
+		if (dev_added[i]) {
+			rc = ata_device_remove(&chan->device[i]);
+			if (rc != EOK) {
+				ata_msg_error(chan,
+				    "Unable to remove device %d.", i);
+			}
+		}
+	}
+	if (irq_inited)
+		ata_bd_fini_irq(chan);
+	return rc;
+}
+
+/** Destroy ATA channel. */
+errno_t ata_channel_destroy(ata_channel_t *chan)
+{
+	int i;
+	errno_t rc;
+
+	ata_msg_debug(chan, ": ata_channel_destroy()");
+
+	fibril_mutex_lock(&chan->lock);
+
+	for (i = 0; i < MAX_DEVICES; i++) {
+		rc = ata_device_remove(&chan->device[i]);
+		if (rc != EOK) {
+			ata_msg_error(chan, "Unable to remove device %d.", i);
+			return rc;
+		}
+	}
+
+	ata_bd_fini_irq(chan);
+	fibril_mutex_unlock(&chan->lock);
+
+	return EOK;
+}
+
+/** Add ATA device.
+ *
+ * @param d Device
+ * @return EOK on success or an error code
+ */
+static errno_t ata_device_add(ata_device_t *d)
+{
+	bd_srvs_init(&d->bds);
+	d->bds.ops = &ata_bd_ops;
+	d->bds.sarg = (void *)d;
+
+	return d->chan->params.add_device(d->chan->params.arg, d->device_id,
+	    (void *)d);
+}
+
+/** Remove ATA device.
+ *
+ * @param d Device
+ * @return EOK on success or an error code
+ */
+static errno_t ata_device_remove(ata_device_t *d)
+{
+	return d->chan->params.remove_device(d->chan->params.arg, d->device_id);
+}
+
+/** Read 16 bits from data port.
+ *
+ * @param chan ATA channel
+ * @param buf Buffer to hold data
+ * @param nwords Number of words to read
+ */
+static void ata_read_data_16(ata_channel_t *chan, uint16_t *buf,
+    size_t nwords)
+{
+	chan->params.read_data_16(chan->params.arg, buf, nwords);
+}
+
+/** Write 16 bits to data port.
+ *
+ * @param chan ATA channel
+ * @param data Data
+ * @param nwords Number of words to write
+ */
+static void ata_write_data_16(ata_channel_t *chan, uint16_t *data,
+    size_t nwords)
+{
+	chan->params.write_data_16(chan->params.arg, data, nwords);
+}
+
+/** Read 8 bits from 8-bit command port.
+ *
+ * @param chan ATA channel
+ * @param port Port number
+ * @return 8-bit register value
+ */
+static uint8_t ata_read_cmd_8(ata_channel_t *chan, uint16_t port)
+{
+	return chan->params.read_cmd_8(chan->params.arg, port);
+}
+
+/** Write 8 bits to 8-bit command port.
+ *
+ * @param chan ATA channel
+ * @param port Port number
+ * @param value Register value
+ */
+static void ata_write_cmd_8(ata_channel_t *chan, uint16_t port, uint8_t value)
+{
+	return chan->params.write_cmd_8(chan->params.arg, port, value);
+}
+
+/** Log a notice message.
+ *
+ * @param chan Channel
+ * @param fmt Format
+ */
+static void ata_msg_note(ata_channel_t *chan, const char *fmt, ...)
+{
+	va_list ap;
+	char buf[MSG_BUF_SIZE];
+
+	va_start(ap, fmt);
+	vsnprintf(buf, sizeof(buf), fmt, ap);
+	va_end(ap);
+
+	chan->params.msg_note(chan->params.arg, buf);
+}
+
+/** Log a debug message.
+ *
+ * @param chan Channel
+ * @param fmt Format
+ */
+static void ata_msg_debug(ata_channel_t *chan, const char *fmt, ...)
+{
+	va_list ap;
+	char buf[MSG_BUF_SIZE];
+
+	va_start(ap, fmt);
+	vsnprintf(buf, sizeof(buf), fmt, ap);
+	va_end(ap);
+
+	chan->params.msg_debug(chan->params.arg, buf);
+}
+
+/** Log a warning message.
+ *
+ * @param chan Channel
+ * @param fmt Format
+ */
+static void ata_msg_warn(ata_channel_t *chan, const char *fmt, ...)
+{
+	va_list ap;
+	char buf[MSG_BUF_SIZE];
+
+	va_start(ap, fmt);
+	vsnprintf(buf, sizeof(buf), fmt, ap);
+	va_end(ap);
+
+	chan->params.msg_warn(chan->params.arg, buf);
+}
+
+/** Log an error message.
+ *
+ * @param chan Channel
+ * @param fmt Format
+ */
+static void ata_msg_error(ata_channel_t *chan, const char *fmt, ...)
+{
+	va_list ap;
+	char buf[MSG_BUF_SIZE];
+
+	va_start(ap, fmt);
+	vsnprintf(buf, sizeof(buf), fmt, ap);
+	va_end(ap);
+
+	chan->params.msg_error(chan->params.arg, buf);
+}
+
+/** Print one-line device summary. */
+static void disk_print_summary(ata_device_t *d)
+{
+	uint64_t mbytes;
+	char *atype = NULL;
+	char *cap = NULL;
+	int rc;
+
+	if (d->dev_type == ata_reg_dev) {
+		switch (d->amode) {
+		case am_chs:
+			rc = asprintf(&atype, "CHS %u cylinders, %u heads, "
+			    "%u sectors", d->geom.cylinders, d->geom.heads,
+			    d->geom.sectors);
+			if (rc < 0) {
+				/* Out of memory */
+				atype = NULL;
+			}
+			break;
+		case am_lba28:
+			atype = str_dup("LBA-28");
+			break;
+		case am_lba48:
+			atype = str_dup("LBA-48");
+			break;
+		}
+	} else {
+		atype = str_dup("PACKET");
+	}
+
+	if (atype == NULL)
+		return;
+
+	mbytes = d->blocks / (2 * 1024);
+	if (mbytes > 0) {
+		rc = asprintf(&cap, " %" PRIu64 " MB.", mbytes);
+		if (rc < 0) {
+			cap = NULL;
+			goto cleanup;
+		}
+	}
+
+	ata_msg_note(d->chan, "%s: %s %" PRIu64 " blocks%s", d->model, atype,
+	    d->blocks, cap);
+cleanup:
+	free(atype);
+	free(cap);
+}
+
+/** Initialize IRQ. */
+static errno_t ata_bd_init_irq(ata_channel_t *chan)
+{
+	if (!chan->params.have_irq)
+		return EOK;
+
+	return chan->params.irq_enable(chan->params.arg);
+}
+
+/** Clean up IRQ. */
+static void ata_bd_fini_irq(ata_channel_t *chan)
+{
+	if (!chan->params.have_irq)
+		return;
+
+	(void)chan->params.irq_disable(chan->params.arg);
+}
+
+/** Initialize a device.
+ *
+ * Probes for a device, determines its parameters and initializes
+ * the device structure.
+ */
+static errno_t ata_device_init(ata_channel_t *chan, ata_device_t *d,
+    int device_id)
+{
+	identify_data_t idata;
+	uint8_t model[40];
+	scsi_std_inquiry_data_t inq_data;
+	size_t isize;
+	uint16_t w;
+	uint8_t c;
+	uint16_t bc;
+	uint64_t nblocks;
+	size_t block_size;
+	size_t pos, len;
+	errno_t rc;
+	unsigned i;
+
+	d->chan = chan;
+	d->device_id = device_id;
+	d->present = false;
+
+	/* Try identify command. */
+	rc = ata_identify_dev(d, &idata);
+	if (rc == EOK) {
+		/* Success. It's a register (non-packet) device. */
+		ata_msg_debug(chan, "ATA register-only device found.");
+		d->dev_type = ata_reg_dev;
+	} else if (rc == EIO) {
+		/*
+		 * There is something, but not a register device. Check to see
+		 * whether the IDENTIFY command left the packet signature in
+		 * the registers in case this is a packet device.
+		 *
+		 * According to the ATA specification, the LBA low and
+		 * interrupt reason registers should be set to 0x01. However,
+		 * there are many devices that do not follow this and only set
+		 * the byte count registers. So, only check these.
+		 */
+		bc = ((uint16_t)ata_read_cmd_8(chan, REG_CYLINDER_HIGH) << 8) |
+		    ata_read_cmd_8(chan, REG_CYLINDER_LOW);
+
+		if (bc == PDEV_SIGNATURE_BC) {
+			rc = ata_identify_pkt_dev(d, &idata);
+			if (rc == EOK) {
+				/* We have a packet device. */
+				d->dev_type = ata_pkt_dev;
+			} else {
+				return EIO;
+			}
+		} else {
+			/* Nope. Something's there, but not recognized. */
+			return EIO;
+		}
+	} else {
+		/* Operation timed out. That means there is no device there. */
+		return EIO;
+	}
+
+	if (d->dev_type == ata_pkt_dev) {
+		/* Packet device */
+		d->amode = 0;
+
+		d->geom.cylinders = 0;
+		d->geom.heads = 0;
+		d->geom.sectors = 0;
+
+		d->blocks = 0;
+	} else if ((idata.caps & rd_cap_lba) == 0) {
+		/* Device only supports CHS addressing. */
+		d->amode = am_chs;
+
+		d->geom.cylinders = idata.cylinders;
+		d->geom.heads = idata.heads;
+		d->geom.sectors = idata.sectors;
+
+		d->blocks = d->geom.cylinders * d->geom.heads * d->geom.sectors;
+	} else if ((idata.cmd_set1 & cs1_addr48) == 0) {
+		/* Device only supports LBA-28 addressing. */
+		d->amode = am_lba28;
+
+		d->geom.cylinders = 0;
+		d->geom.heads = 0;
+		d->geom.sectors = 0;
+
+		d->blocks =
+		    (uint32_t) idata.total_lba28_0 |
+		    ((uint32_t) idata.total_lba28_1 << 16);
+	} else {
+		/* Device supports LBA-48 addressing. */
+		d->amode = am_lba48;
+
+		d->geom.cylinders = 0;
+		d->geom.heads = 0;
+		d->geom.sectors = 0;
+
+		d->blocks =
+		    (uint64_t) idata.total_lba48_0 |
+		    ((uint64_t) idata.total_lba48_1 << 16) |
+		    ((uint64_t) idata.total_lba48_2 << 32) |
+		    ((uint64_t) idata.total_lba48_3 << 48);
+	}
+
+	/*
+	 * Convert model name to string representation.
+	 */
+	for (i = 0; i < 20; i++) {
+		w = idata.model_name[i];
+		model[2 * i] = w >> 8;
+		model[2 * i + 1] = w & 0x00ff;
+	}
+
+	len = 40;
+	while (len > 0 && model[len - 1] == 0x20)
+		--len;
+
+	pos = 0;
+	for (i = 0; i < len; ++i) {
+		c = model[i];
+		if (c >= 0x80)
+			c = '?';
+
+		chr_encode(c, d->model, &pos, 40);
+	}
+	d->model[pos] = '\0';
+
+	if (d->dev_type == ata_pkt_dev) {
+		/* Send inquiry. */
+		rc = ata_pcmd_inquiry(d, &inq_data, sizeof(inq_data), &isize);
+		if (rc != EOK || isize < sizeof(inq_data)) {
+			ata_msg_error(chan, "Device inquiry failed.");
+			d->present = false;
+			return EIO;
+		}
+
+		/* Check device type. */
+		if (INQUIRY_PDEV_TYPE(inq_data.pqual_devtype) != SCSI_DEV_CD_DVD)
+			ata_msg_warn(chan, "Peripheral device type is not CD-ROM.");
+
+		rc = ata_pcmd_read_capacity(d, &nblocks, &block_size);
+		if (rc != EOK) {
+			ata_msg_error(chan, "Read capacity command failed.");
+			d->present = false;
+			return EIO;
+		}
+
+		d->blocks = nblocks;
+		d->block_size = block_size;
+	} else {
+		/* Assume register Read always uses 512-byte blocks. */
+		d->block_size = 512;
+	}
+
+	d->present = true;
+	return EOK;
+}
+
+static errno_t ata_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
+{
+	return EOK;
+}
+
+static errno_t ata_bd_close(bd_srv_t *bd)
+{
+	return EOK;
+}
+
+/** Read multiple blocks from the device. */
+static errno_t ata_bd_read_blocks(bd_srv_t *bd, uint64_t ba, size_t cnt,
+    void *buf, size_t size)
+{
+	ata_device_t *device = bd_srv_device(bd);
+	size_t maxnb;
+	size_t nb;
+	errno_t rc;
+
+	if (size < cnt * device->block_size) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	/* Maximum number of blocks to transfer at the same time */
+	maxnb = ata_disk_maxnb(device);
+	while (cnt > 0) {
+		nb = min(maxnb, cnt);
+		if (device->dev_type == ata_reg_dev) {
+			rc = ata_rcmd_read(device, ba, nb, buf);
+		} else {
+			rc = ata_pcmd_read_12(device, ba, nb, buf,
+			    device->block_size);
+		}
+
+		if (rc != EOK)
+			goto error;
+
+		ba += nb;
+		cnt -= nb;
+		buf += device->block_size * nb;
+	}
+
+	return EOK;
+error:
+	ata_msg_debug(device->chan, "ata_bd_read_blocks: rc=%d", rc);
+	return rc;
+}
+
+/** Read TOC from device. */
+static errno_t ata_bd_read_toc(bd_srv_t *bd, uint8_t session, void *buf, size_t size)
+{
+	ata_device_t *device = bd_srv_device(bd);
+
+	return ata_pcmd_read_toc(device, session, buf, size);
+}
+
+/** Write multiple blocks to the device. */
+static errno_t ata_bd_write_blocks(bd_srv_t *bd, uint64_t ba, size_t cnt,
+    const void *buf, size_t size)
+{
+	ata_device_t *device = bd_srv_device(bd);
+	size_t maxnb;
+	size_t nb;
+	errno_t rc;
+
+	if (device->dev_type != ata_reg_dev)
+		return ENOTSUP;
+
+	if (size < cnt * device->block_size)
+		return EINVAL;
+
+	/* Maximum number of blocks to transfer at the same time */
+	maxnb = ata_disk_maxnb(device);
+	while (cnt > 0) {
+		nb = min(maxnb, cnt);
+		rc = ata_rcmd_write(device, ba, nb, buf);
+		if (rc != EOK)
+			return rc;
+
+		ba += nb;
+		cnt -= nb;
+		buf += device->block_size * nb;
+	}
+
+	return EOK;
+}
+
+/** Get device block size. */
+static errno_t ata_bd_get_block_size(bd_srv_t *bd, size_t *rbsize)
+{
+	ata_device_t *device = bd_srv_device(bd);
+
+	*rbsize = device->block_size;
+	return EOK;
+}
+
+/** Get device number of blocks. */
+static errno_t ata_bd_get_num_blocks(bd_srv_t *bd, aoff64_t *rnb)
+{
+	ata_device_t *device = bd_srv_device(bd);
+
+	*rnb = device->blocks;
+	return EOK;
+}
+
+/** Flush cache. */
+static errno_t ata_bd_sync_cache(bd_srv_t *bd, uint64_t ba, size_t cnt)
+{
+	ata_device_t *device = bd_srv_device(bd);
+
+	/* ATA cannot flush just some blocks, we just flush everything. */
+	(void)ba;
+	(void)cnt;
+
+	return ata_rcmd_flush_cache(device);
+}
+
+/** PIO data-in command protocol. */
+static errno_t ata_pio_data_in(ata_device_t *device, void *obuf, size_t obuf_size,
+    size_t blk_size, size_t nblocks)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t status;
+	errno_t rc;
+
+	assert(nblocks > 0);
+	assert(blk_size % 2 == 0);
+
+	while (nblocks > 0) {
+		if (chan->params.have_irq)
+			rc = wait_irq(chan, &status, TIMEOUT_BSY);
+		else
+			rc = wait_status(chan, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+
+		if (rc != EOK) {
+			ata_msg_debug(chan, "wait_irq/wait_status failed");
+			return EIO;
+		}
+
+		if ((status & SR_DRQ) == 0) {
+			ata_msg_debug(chan, "DRQ == 0");
+			break;
+		}
+
+		/* Read data from the device buffer. */
+		ata_read_data_16(chan, (uint16_t *)obuf, blk_size / 2);
+		obuf += blk_size;
+
+		--nblocks;
+	}
+
+	if ((status & SR_ERR) != 0) {
+		ata_msg_debug(chan, "status & SR_ERR != 0");
+		return EIO;
+	}
+	if (nblocks > 0) {
+		ata_msg_debug(chan, "remaining nblocks = %zu", nblocks);
+		return EIO;
+	}
+
+	return EOK;
+}
+
+/** PIO data-out command protocol. */
+static errno_t ata_pio_data_out(ata_device_t *device, const void *buf, size_t buf_size,
+    size_t blk_size, size_t nblocks)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t status;
+	errno_t rc;
+
+	assert(nblocks > 0);
+	assert(blk_size % 2 == 0);
+
+	rc = wait_status(chan, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+	if (rc != EOK)
+		return EIO;
+
+	while (nblocks > 0) {
+		if ((status & SR_DRQ) == 0) {
+			ata_msg_debug(chan, "pio_data_out: unexpected DRQ=0");
+			break;
+		}
+
+		/* Write data to the device buffer. */
+		ata_write_data_16(chan, (uint16_t *)buf, blk_size / 2);
+		buf += blk_size;
+
+		if (chan->params.have_irq)
+			rc = wait_irq(chan, &status, TIMEOUT_BSY);
+		else
+			rc = wait_status(chan, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+		if (rc != EOK)
+			return EIO;
+
+		--nblocks;
+	}
+
+	if (status & SR_ERR)
+		return EIO;
+	if (nblocks > 0)
+		return EIO;
+
+	return EOK;
+}
+
+/** PIO non-data command protocol. */
+static errno_t ata_pio_nondata(ata_device_t *device)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t status;
+	errno_t rc;
+
+	if (chan->params.have_irq)
+		rc = wait_irq(chan, &status, TIMEOUT_BSY);
+	else
+		rc = wait_status(chan, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+
+	if (rc != EOK)
+		return EIO;
+
+	if (status & SR_ERR)
+		return EIO;
+
+	return EOK;
+}
+
+/** DMA command protocol.
+ *
+ * @param device ATA device
+ * @param cmd Command code
+ * @param buf Data buffer
+ * @param buf_size Data buffer size in bytes
+ * @param dir DMA direction
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t ata_dma_proto(ata_device_t *device, uint8_t cmd, void *buf,
+    size_t buf_size, ata_dma_dir_t dir)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t status;
+	errno_t rc;
+
+	/* Set up DMA channel */
+	ata_dma_chan_setup(device, buf, buf_size, dir);
+
+	ata_write_cmd_8(chan, REG_COMMAND, cmd);
+
+	if (chan->params.have_irq)
+		rc = wait_irq(chan, &status, TIMEOUT_BSY);
+	else
+		rc = wait_status(chan, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+
+	/* Tear down DMA channel */
+	ata_dma_chan_teardown(device);
+
+	if (rc != EOK) {
+		ata_msg_debug(chan, "wait_irq/wait_status failed");
+		return EIO;
+	}
+
+	if ((status & SR_ERR) != 0) {
+		ata_msg_debug(chan, "status & SR_ERR != 0");
+		return EIO;
+	}
+
+	return EOK;
+}
+
+/** Issue IDENTIFY DEVICE command.
+ *
+ * Reads @c identify data into the provided buffer. This is used to detect
+ * whether an ATA device is present and if so, to determine its parameters.
+ *
+ * @param device	Device
+ * @param buf		Pointer to a 512-byte buffer.
+ *
+ * @return		ETIMEOUT on timeout (this can mean the device is
+ *			not present). EIO if device responds with error.
+ */
+static errno_t ata_identify_dev(ata_device_t *device, void *buf)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t status;
+	uint8_t drv_head;
+
+	drv_head = ((disk_dev_idx(device) != 0) ? DHR_DRV : 0);
+
+	if (wait_status(chan, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
+		return ETIMEOUT;
+
+	ata_write_cmd_8(chan, REG_DRIVE_HEAD, drv_head);
+
+	/*
+	 * Do not wait for DRDY to be set in case this is a packet device.
+	 * We determine whether the device is present by waiting for DRQ to be
+	 * set after issuing the command.
+	 */
+	if (wait_status(chan, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
+		return ETIMEOUT;
+
+	ata_write_cmd_8(chan, REG_COMMAND, CMD_IDENTIFY_DRIVE);
+
+	/*
+	 * For probing purposes we need to wait for some status bit to become
+	 * active - otherwise we could be fooled just by receiving all zeroes.
+	 */
+	if (wait_status(chan, SR_DRQ, ~SR_BSY, &status, TIMEOUT_PROBE) != EOK) {
+		if ((status & SR_ERR) == 0) {
+			/* Probably no device at all */
+			return ETIMEOUT;
+		}
+	}
+
+	return ata_pio_data_in(device, buf, identify_data_size,
+	    identify_data_size, 1);
+}
+
+/** Issue Identify Packet Device command.
+ *
+ * Reads @c identify data into the provided buffer. This is used to detect
+ * whether an ATAPI device is present and if so, to determine its parameters.
+ *
+ * @param device	Device
+ * @param buf		Pointer to a 512-byte buffer.
+ */
+static errno_t ata_identify_pkt_dev(ata_device_t *device, void *buf)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t drv_head;
+
+	drv_head = ((disk_dev_idx(device) != 0) ? DHR_DRV : 0);
+
+	if (wait_status(chan, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
+		return EIO;
+
+	ata_write_cmd_8(chan, REG_DRIVE_HEAD, drv_head);
+
+	/* For ATAPI commands we do not need to wait for DRDY. */
+	if (wait_status(chan, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
+		return EIO;
+
+	ata_write_cmd_8(chan, REG_COMMAND, CMD_IDENTIFY_PKT_DEV);
+
+	return ata_pio_data_in(device, buf, identify_data_size,
+	    identify_data_size, 1);
+}
+
+/** Read data using PIO during a PACKET command.
+ *
+ * @param device Device
+ * @param obuf Output buffer
+ * @param obuf_size Output buffer size
+ * @param rcvd_size Place to store number of bytes actually transferred
+ *                  or @c NULL
+ */
+static errno_t ata_packet_pio_data_in(ata_device_t *device, void *obuf,
+    size_t obuf_size, size_t *rcvd_size)
+{
+	ata_channel_t *chan = device->chan;
+	size_t data_size;
+	size_t remain;
+	uint8_t status;
+	errno_t rc;
+
+	remain = obuf_size;
+	while (remain > 0) {
+		if (chan->params.have_irq)
+			rc = wait_irq(chan, &status, TIMEOUT_BSY);
+		else
+			rc = wait_status(chan, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+
+		if (rc != EOK) {
+			fibril_mutex_unlock(&chan->lock);
+			return EIO;
+		}
+
+		if ((status & SR_DRQ) == 0)
+			break;
+
+		/* Read byte count. */
+		data_size = (uint16_t) ata_read_cmd_8(chan, REG_CYLINDER_LOW) +
+		    ((uint16_t) ata_read_cmd_8(chan, REG_CYLINDER_HIGH) << 8);
+
+		/* Check whether data fits into output buffer. */
+		if (data_size > obuf_size) {
+			/* Output buffer is too small to store data. */
+			fibril_mutex_unlock(&chan->lock);
+			return EIO;
+		}
+
+		/* Read data from the device buffer. */
+		ata_read_data_16(chan, obuf, (data_size + 1) / 2);
+		obuf += data_size;
+
+		remain -= data_size;
+	}
+
+	if (chan->params.have_irq)
+		rc = wait_irq(chan, &status, TIMEOUT_BSY);
+	else
+		rc = wait_status(chan, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+
+	if (status & SR_ERR)
+		return EIO;
+
+	if (rcvd_size != NULL)
+		*rcvd_size = obuf_size - remain;
+
+	return EOK;
+}
+
+/** Transfer data using DMA during PACKET command.
+ *
+ * @param device Device
+ * @param buf Buffer
+ * @param buf_size Buffer size
+ * @param dir DMA direction
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t ata_packet_dma(ata_device_t *device, void *buf, size_t buf_size,
+    ata_dma_dir_t dir)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t status;
+	errno_t rc;
+
+	if (chan->params.have_irq)
+		rc = wait_irq(chan, &status, TIMEOUT_BSY);
+	else
+		rc = wait_status(chan, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+
+	if (rc != EOK) {
+		ata_msg_debug(chan, "wait_irq/wait_status failed");
+		return EIO;
+	}
+
+	if ((status & SR_ERR) != 0) {
+		ata_msg_debug(chan, "status & SR_ERR != 0");
+		return EIO;
+	}
+
+	return EOK;
+}
+
+/** Issue packet command (i. e. write a command packet to the device).
+ *
+ * Only data-in commands are supported (e.g. inquiry, read).
+ *
+ * @param device	Device
+ * @param obuf		Buffer for storing data read from device
+ * @param obuf_size	Size of obuf in bytes
+ * @param rcvd_size	Place to store number of bytes read or @c NULL
+ *
+ * @return EOK on success, EIO on error.
+ */
+static errno_t ata_cmd_packet(ata_device_t *device, const void *cpkt, size_t cpkt_size,
+    void *obuf, size_t obuf_size, size_t *rcvd_size)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t status;
+	uint8_t drv_head;
+	errno_t rc;
+
+	fibril_mutex_lock(&chan->lock);
+
+	/* New value for Drive/Head register */
+	drv_head =
+	    ((disk_dev_idx(device) != 0) ? DHR_DRV : 0);
+
+	if (wait_status(chan, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return EIO;
+	}
+
+	ata_write_cmd_8(chan, REG_DRIVE_HEAD, drv_head);
+
+	if (wait_status(chan, 0, ~(SR_BSY | SR_DRQ), NULL, TIMEOUT_BSY) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return EIO;
+	}
+
+	if (chan->params.use_dma) {
+		/* Set up DMA channel */
+		ata_dma_chan_setup(device, obuf, obuf_size, ata_dma_read);
+		ata_write_cmd_8(chan, REG_FEATURES, 0x01); // XXX
+	} else {
+		/*
+		 * Byte count <- max. number of bytes we can read in one
+		 * PIO transfer.
+		 */
+		ata_write_cmd_8(chan, REG_CYLINDER_LOW, 0xfe);
+		ata_write_cmd_8(chan, REG_CYLINDER_HIGH, 0xff);
+	}
+
+	ata_write_cmd_8(chan, REG_COMMAND, CMD_PACKET);
+
+	if (wait_status(chan, SR_DRQ, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
+		if (chan->params.use_dma)
+			ata_dma_chan_teardown(device);
+		fibril_mutex_unlock(&chan->lock);
+		return EIO;
+	}
+
+	/* Write command packet. */
+	ata_write_data_16(chan, ((uint16_t *) cpkt), (cpkt_size + 1) / 2);
+
+	if (chan->params.use_dma) {
+		/* Read data using DMA */
+		rc = ata_packet_dma(device, obuf, obuf_size, ata_dma_read);
+		if (rc == EOK && rcvd_size != NULL)
+			*rcvd_size = obuf_size;
+		ata_dma_chan_teardown(device);
+	} else {
+		/* Read data using PIO */
+		rc = ata_packet_pio_data_in(device, obuf, obuf_size, rcvd_size);
+	}
+
+	fibril_mutex_unlock(&chan->lock);
+
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Issue ATAPI Inquiry.
+ *
+ * @param device	Device
+ * @param obuf		Buffer for storing inquiry data read from device
+ * @param obuf_size	Size of obuf in bytes
+ *
+ * @return EOK on success, EIO on error.
+ */
+static errno_t ata_pcmd_inquiry(ata_device_t *device, void *obuf, size_t obuf_size,
+    size_t *rcvd_size)
+{
+	uint8_t cpb[12];
+	scsi_cdb_inquiry_t *cp = (scsi_cdb_inquiry_t *)cpb;
+	errno_t rc;
+
+	memset(cpb, 0, sizeof(cpb));
+
+	/*
+	 * For SFF 8020 compliance the inquiry must be padded to 12 bytes
+	 * and allocation length must fit in one byte.
+	 */
+	cp->op_code = SCSI_CMD_INQUIRY;
+
+	/* Allocation length */
+	cp->alloc_len = host2uint16_t_be(min(obuf_size, 0xff));
+
+	rc = ata_cmd_packet(device, cpb, sizeof(cpb), obuf, obuf_size, rcvd_size);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Issue ATAPI read capacity(10) command.
+ *
+ * @param device	Device
+ * @param nblocks	Place to store number of blocks
+ * @param block_size	Place to store block size
+ *
+ * @return EOK on success, EIO on error.
+ */
+static errno_t ata_pcmd_read_capacity(ata_device_t *device, uint64_t *nblocks,
+    size_t *block_size)
+{
+	scsi_cdb_read_capacity_10_t cdb;
+	scsi_read_capacity_10_data_t data;
+	size_t rsize;
+	errno_t rc;
+
+	memset(&cdb, 0, sizeof(cdb));
+	cdb.op_code = SCSI_CMD_READ_CAPACITY_10;
+
+	rc = ata_cmd_packet(device, &cdb, sizeof(cdb), &data, sizeof(data), &rsize);
+	if (rc != EOK)
+		return rc;
+
+	if (rsize != sizeof(data))
+		return EIO;
+
+	*nblocks = uint32_t_be2host(data.last_lba) + 1;
+	*block_size = uint32_t_be2host(data.block_size);
+
+	return EOK;
+}
+
+/** Issue ATAPI read(12) command.
+ *
+ * Output buffer must be large enough to hold the data, otherwise the
+ * function will fail.
+ *
+ * @param device	Device
+ * @param ba		Starting block address
+ * @param cnt		Number of blocks to read
+ * @param obuf		Buffer for storing inquiry data read from device
+ * @param obuf_size	Size of obuf in bytes
+ *
+ * @return EOK on success, EIO on error.
+ */
+static errno_t ata_pcmd_read_12(ata_device_t *device, uint64_t ba, size_t cnt,
+    void *obuf, size_t obuf_size)
+{
+	scsi_cdb_read_12_t cp;
+	errno_t rc;
+
+	if (ba > UINT32_MAX)
+		return EINVAL;
+
+	memset(&cp, 0, sizeof(cp));
+
+	cp.op_code = SCSI_CMD_READ_12;
+	cp.lba = host2uint32_t_be(ba);
+	cp.xfer_len = host2uint32_t_be(cnt);
+
+	rc = ata_cmd_packet(device, &cp, sizeof(cp), obuf, obuf_size, NULL);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Issue ATAPI read TOC command.
+ *
+ * Read TOC in 'multi-session' format (first and last session number
+ * with last session LBA).
+ *
+ * http://suif.stanford.edu/~csapuntz/specs/INF-8020.PDF page 171
+ *
+ * Output buffer must be large enough to hold the data, otherwise the
+ * function will fail.
+ *
+ * @param device	Device
+ * @param session	Starting session
+ * @param obuf		Buffer for storing inquiry data read from device
+ * @param obuf_size	Size of obuf in bytes
+ *
+ * @return EOK on success, EIO on error.
+ */
+static errno_t ata_pcmd_read_toc(ata_device_t *device, uint8_t session,
+    void *obuf, size_t obuf_size)
+{
+	uint8_t cpb[12];
+	scsi_cdb_read_toc_t *cp = (scsi_cdb_read_toc_t *)cpb;
+	errno_t rc;
+
+	memset(cpb, 0, sizeof(cpb));
+
+	cp->op_code = SCSI_CMD_READ_TOC;
+	cp->msf = 0;
+	cp->format = 0x01; /* 0x01 = multi-session mode */
+	cp->track_sess_no = session;
+	cp->alloc_len = host2uint16_t_be(obuf_size);
+	cp->control = 0x40; /* 0x01 = multi-session mode (shifted to MSB) */
+
+	rc = ata_cmd_packet(device, cpb, sizeof(cpb), obuf, obuf_size, NULL);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Read a physical block from the device.
+ *
+ * @param device	Device
+ * @param ba		Address the first block.
+ * @param cnt		Number of blocks to transfer.
+ * @param buf		Buffer for holding the data.
+ *
+ * @return EOK on success, EIO on error.
+ */
+static errno_t ata_rcmd_read(ata_device_t *device, uint64_t ba, size_t blk_cnt,
+    void *buf)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t drv_head;
+	block_coord_t bc;
+	uint8_t cmd;
+	errno_t rc;
+
+	/* Silence warning. */
+	memset(&bc, 0, sizeof(bc));
+
+	/* Compute block coordinates. */
+	if (coord_calc(device, ba, &bc) != EOK) {
+		ata_msg_note(chan, "ata_rcmd_read() -> coord_calc failed");
+		return EINVAL;
+	}
+
+	/* New value for Drive/Head register */
+	drv_head =
+	    ((disk_dev_idx(device) != 0) ? DHR_DRV : 0) |
+	    ((device->amode != am_chs) ? DHR_LBA : 0) |
+	    (bc.h & 0x0f);
+
+	fibril_mutex_lock(&chan->lock);
+
+	/* Program a Read Sectors operation. */
+
+	if (wait_status(chan, 0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		ata_msg_note(chan, "ata_rcmd_read() -> wait_status failed");
+		return EIO;
+	}
+
+	ata_write_cmd_8(chan, REG_DRIVE_HEAD, drv_head);
+
+	if (wait_status(chan, SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		ata_msg_note(chan, "ata_rcmd_read() -> wait_status 2 failed");
+		return EIO;
+	}
+
+	/* Program block coordinates into the device. */
+	coord_sc_program(chan, &bc, blk_cnt);
+
+	if (chan->params.use_dma) {
+		cmd = (device->amode == am_lba48) ? CMD_READ_DMA_EXT :
+		    CMD_READ_DMA;
+
+		rc = ata_dma_proto(device, cmd, buf,
+		    blk_cnt * device->block_size, ata_dma_read);
+		if (rc != EOK) {
+			ata_msg_note(chan, "ata_rcmd_read() -> dma_proto->%d",
+			    rc);
+		}
+	} else {
+		ata_write_cmd_8(chan, REG_COMMAND, device->amode == am_lba48 ?
+		    CMD_READ_SECTORS_EXT : CMD_READ_SECTORS);
+
+		rc = ata_pio_data_in(device, buf, blk_cnt * device->block_size,
+		    device->block_size, blk_cnt);
+		if (rc != EOK) {
+			ata_msg_note(chan, "ata_rcmd_read() -> pio_data_in->%d",
+			    rc);
+		}
+	}
+
+	fibril_mutex_unlock(&chan->lock);
+
+	return rc;
+}
+
+/** Write a physical block to the device.
+ *
+ * @param device	Device
+ * @param ba		Address of the first block.
+ * @param cnt		Number of blocks to transfer.
+ * @param buf		Buffer holding the data to write.
+ *
+ * @return EOK on success, EIO on error.
+ */
+static errno_t ata_rcmd_write(ata_device_t *device, uint64_t ba, size_t cnt,
+    const void *buf)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t drv_head;
+	block_coord_t bc;
+	uint8_t cmd;
+	errno_t rc;
+
+	/* Silence warning. */
+	memset(&bc, 0, sizeof(bc));
+
+	/* Compute block coordinates. */
+	if (coord_calc(device, ba, &bc) != EOK)
+		return EINVAL;
+
+	/* New value for Drive/Head register */
+	drv_head =
+	    ((disk_dev_idx(device) != 0) ? DHR_DRV : 0) |
+	    ((device->amode != am_chs) ? DHR_LBA : 0) |
+	    (bc.h & 0x0f);
+
+	fibril_mutex_lock(&chan->lock);
+
+	/* Program a Write Sectors operation. */
+
+	if (wait_status(chan, 0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return EIO;
+	}
+
+	ata_write_cmd_8(chan, REG_DRIVE_HEAD, drv_head);
+
+	if (wait_status(chan, SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return EIO;
+	}
+
+	/* Program block coordinates into the device. */
+	coord_sc_program(chan, &bc, cnt);
+
+	if (chan->params.use_dma) {
+		cmd = (device->amode == am_lba48) ? CMD_WRITE_DMA_EXT :
+		    CMD_WRITE_DMA;
+
+		rc = ata_dma_proto(device, cmd, (void *)buf,
+		    cnt * device->block_size, ata_dma_write);
+		if (rc != EOK) {
+			ata_msg_note(chan, "ata_rcmd_write() -> dma_proto->%d",
+			    rc);
+		}
+	} else {
+		ata_write_cmd_8(chan, REG_COMMAND, device->amode == am_lba48 ?
+		    CMD_WRITE_SECTORS_EXT : CMD_WRITE_SECTORS);
+
+		rc = ata_pio_data_out(device, buf, cnt * device->block_size,
+		    device->block_size, cnt);
+		if (rc != EOK) {
+			ata_msg_note(chan,
+			    "ata_rcmd_read() -> pio_data_out->%d", rc);
+		}
+	}
+
+	fibril_mutex_unlock(&chan->lock);
+	return rc;
+}
+
+/** Flush cached data to nonvolatile storage.
+ *
+ * @param device	Device
+ *
+ * @return EOK on success, EIO on error.
+ */
+static errno_t ata_rcmd_flush_cache(ata_device_t *device)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t drv_head;
+	errno_t rc;
+
+	/* New value for Drive/Head register */
+	drv_head =
+	    (disk_dev_idx(device) != 0) ? DHR_DRV : 0;
+
+	fibril_mutex_lock(&chan->lock);
+
+	/* Program a Flush Cache operation. */
+
+	if (wait_status(chan, 0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return EIO;
+	}
+
+	ata_write_cmd_8(chan, REG_DRIVE_HEAD, drv_head);
+
+	if (wait_status(chan, SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return EIO;
+	}
+
+	ata_write_cmd_8(chan, REG_COMMAND, CMD_FLUSH_CACHE);
+
+	rc = ata_pio_nondata(device);
+
+	fibril_mutex_unlock(&chan->lock);
+	return rc;
+}
+
+/** Get the maximum number of blocks to be transferred in one I/O
+ *
+ * @param d Device
+ * @return Maximum number of blocks
+ */
+static size_t ata_disk_maxnb(ata_device_t *d)
+{
+	size_t maxnb;
+	size_t dma_maxnb;
+
+	maxnb = 0;
+
+	if (d->dev_type == ata_pkt_dev) {
+		/* Could be more depending on SCSI command support */
+		maxnb = 0x100;
+	} else {
+		switch (d->amode) {
+		case am_chs:
+		case am_lba28:
+			maxnb = 0x100;
+			break;
+		case am_lba48:
+			maxnb = 0x10000;
+			break;
+		}
+	}
+
+	/*
+	 * If using DMA, this needs to be further restricted not to
+	 * exceed DMA buffer size.
+	 */
+	dma_maxnb = d->chan->params.max_dma_xfer / d->block_size;
+	if (dma_maxnb < maxnb)
+		maxnb = dma_maxnb;
+
+	return maxnb;
+}
+
+/** Calculate block coordinates.
+ *
+ * Calculates block coordinates in the best coordinate system supported
+ * by the device. These can be later programmed into the device using
+ * @c coord_sc_program().
+ *
+ * @return EOK on success or EINVAL if block index is past end of device.
+ */
+static errno_t coord_calc(ata_device_t *d, uint64_t ba, block_coord_t *bc)
+{
+	uint64_t c;
+	uint64_t idx;
+
+	/* Check device bounds. */
+	if (ba >= d->blocks)
+		return EINVAL;
+
+	bc->amode = d->amode;
+
+	switch (d->amode) {
+	case am_chs:
+		/* Compute CHS coordinates. */
+		c = ba / (d->geom.heads * d->geom.sectors);
+		idx = ba % (d->geom.heads * d->geom.sectors);
+
+		bc->cyl_lo = c & 0xff;
+		bc->cyl_hi = (c >> 8) & 0xff;
+		bc->h      = (idx / d->geom.sectors) & 0x0f;
+		bc->sector = (1 + (idx % d->geom.sectors)) & 0xff;
+		break;
+
+	case am_lba28:
+		/* Compute LBA-28 coordinates. */
+		bc->c0 = ba & 0xff;		/* bits 0-7 */
+		bc->c1 = (ba >> 8) & 0xff;	/* bits 8-15 */
+		bc->c2 = (ba >> 16) & 0xff;	/* bits 16-23 */
+		bc->h  = (ba >> 24) & 0x0f;	/* bits 24-27 */
+		break;
+
+	case am_lba48:
+		/* Compute LBA-48 coordinates. */
+		bc->c0 = ba & 0xff;		/* bits 0-7 */
+		bc->c1 = (ba >> 8) & 0xff;	/* bits 8-15 */
+		bc->c2 = (ba >> 16) & 0xff;	/* bits 16-23 */
+		bc->c3 = (ba >> 24) & 0xff;	/* bits 24-31 */
+		bc->c4 = (ba >> 32) & 0xff;	/* bits 32-39 */
+		bc->c5 = (ba >> 40) & 0xff;	/* bits 40-47 */
+		bc->h  = 0;
+		break;
+	}
+
+	return EOK;
+}
+
+/** Program block coordinates and sector count into ATA registers.
+ *
+ * Note that bc->h must be programmed separately into the device/head register.
+ *
+ * @param chan		Channel
+ * @param bc		Block coordinates
+ * @param scnt		Sector count
+ */
+static void coord_sc_program(ata_channel_t *chan, const block_coord_t *bc,
+    uint16_t scnt)
+{
+	if (bc->amode == am_lba48) {
+		/* Write high-order bits. */
+		ata_write_cmd_8(chan, REG_SECTOR_COUNT, scnt >> 8);
+		ata_write_cmd_8(chan, REG_SECTOR_NUMBER, bc->c3);
+		ata_write_cmd_8(chan, REG_CYLINDER_LOW, bc->c4);
+		ata_write_cmd_8(chan, REG_CYLINDER_HIGH, bc->c5);
+	}
+
+	/* Write low-order bits. */
+	ata_write_cmd_8(chan, REG_SECTOR_COUNT, scnt & 0x00ff);
+	ata_write_cmd_8(chan, REG_SECTOR_NUMBER, bc->c0);
+	ata_write_cmd_8(chan, REG_CYLINDER_LOW, bc->c1);
+	ata_write_cmd_8(chan, REG_CYLINDER_HIGH, bc->c2);
+}
+
+/** Wait until some status bits are set and some are reset.
+ *
+ * Example: wait_status(chan, SR_DRDY, ~SR_BSY, ...) waits for SR_DRDY to become
+ * set and SR_BSY to become reset.
+ *
+ * @param chan		Channel
+ * @param set		Combination if bits which must be all set.
+ * @param n_reset	Negated combination of bits which must be all reset.
+ * @param pstatus	Pointer where to store last read status or NULL.
+ * @param timeout	Timeout in 10ms units.
+ *
+ * @return		EOK on success, EIO on timeout.
+ */
+static errno_t wait_status(ata_channel_t *chan, unsigned set, unsigned n_reset,
+    uint8_t *pstatus, unsigned timeout)
+{
+	uint8_t status;
+	int cnt;
+
+	status = ata_read_cmd_8(chan, REG_STATUS);
+
+	/*
+	 * This is crude, yet simple. First try with 1us delays
+	 * (most likely the device will respond very fast). If not,
+	 * start trying every 10 ms.
+	 */
+
+	cnt = 100;
+	while ((status & ~n_reset) != 0 || (status & set) != set) {
+		--cnt;
+		if (cnt <= 0)
+			break;
+
+		status = ata_read_cmd_8(chan, REG_STATUS);
+	}
+
+	cnt = timeout;
+	while ((status & ~n_reset) != 0 || (status & set) != set) {
+		fibril_usleep(10000);
+		--cnt;
+		if (cnt <= 0)
+			break;
+
+		status = ata_read_cmd_8(chan, REG_STATUS);
+	}
+
+	if (pstatus)
+		*pstatus = status;
+
+	if (cnt == 0)
+		return EIO;
+
+	return EOK;
+}
+
+/** Wait for IRQ and return status.
+ *
+ * @param chan		Channel
+ * @param pstatus	Pointer where to store last read status or NULL.
+ * @param timeout	Timeout in 10ms units.
+ *
+ * @return		EOK on success, EIO on timeout.
+ */
+static errno_t wait_irq(ata_channel_t *chan, uint8_t *pstatus, unsigned timeout)
+{
+	fibril_mutex_lock(&chan->irq_lock);
+	while (!chan->irq_fired)
+		fibril_condvar_wait(&chan->irq_cv, &chan->irq_lock);
+
+	chan->irq_fired = false;
+	*pstatus = chan->irq_status;
+	fibril_mutex_unlock(&chan->irq_lock);
+	return EOK;
+}
+
+/** Set up DMA channel.
+ *
+ * @param device ATA device
+ * @param buf Data buffer
+ * @param buf_size Data buffer size in bytes
+ * @param dir DMA direction
+ */
+static void ata_dma_chan_setup(ata_device_t *device, void *buf,
+    size_t buf_size, ata_dma_dir_t dir)
+{
+	device->chan->params.dma_chan_setup(device->chan->params.arg,
+	    buf, buf_size, dir);
+}
+
+/** Tear down DMA channel.
+ *
+ * @param device ATA device
+ */
+static void ata_dma_chan_teardown(ata_device_t *device)
+{
+	device->chan->params.dma_chan_teardown(device->chan->params.arg);
+}
+
+/** Interrupt handler.
+ *
+ * @param chan ATA channel
+ * @param status Status read by interrupt handler
+ */
+void ata_channel_irq(ata_channel_t *chan, uint8_t status)
+{
+	fibril_mutex_lock(&chan->irq_lock);
+	chan->irq_fired = true;
+	chan->irq_status = status;
+	fibril_mutex_unlock(&chan->irq_lock);
+	fibril_condvar_broadcast(&chan->irq_cv);
+}
+
+/** Block device connection handler */
+void ata_connection(ipc_call_t *icall, ata_device_t *device)
+{
+	bd_conn(icall, &device->bds);
+}
+
+/**
+ * @}
+ */
Index: uspace/lib/bithenge/src/helenos/block.c
===================================================================
--- uspace/lib/bithenge/src/helenos/block.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/bithenge/src/helenos/block.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2012 Sean Bartell
  * All rights reserved.
@@ -103,5 +104,5 @@
 	// Initialize libblock
 	errno_t rc;
-	rc = block_init(service_id, 2048);
+	rc = block_init(service_id);
 	if (rc != EOK)
 		return rc;
Index: uspace/lib/block/block.c
===================================================================
--- uspace/lib/block/block.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/block/block.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2008 Jakub Jermar
  * Copyright (c) 2008 Martin Decky
@@ -145,5 +146,5 @@
 }
 
-errno_t block_init(service_id_t service_id, size_t comm_size)
+errno_t block_init(service_id_t service_id)
 {
 	bd_t *bd;
@@ -260,5 +261,5 @@
 }
 
-static hash_table_ops_t cache_ops = {
+static const hash_table_ops_t cache_ops = {
 	.hash = cache_hash,
 	.key_hash = cache_key_hash,
Index: uspace/lib/block/block.h
===================================================================
--- uspace/lib/block/block.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/block/block.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2008 Jakub Jermar
  * Copyright (c) 2008 Martin Decky
@@ -99,5 +100,5 @@
 };
 
-extern errno_t block_init(service_id_t, size_t);
+extern errno_t block_init(service_id_t);
 extern void block_fini(service_id_t);
 
Index: pace/lib/c/arch/amd64/include/libarch/stackarg.h
===================================================================
--- uspace/lib/c/arch/amd64/include/libarch/stackarg.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,41 +1,0 @@
-/*
- * Copyright (c) 2006 Josef Cejka
- * 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 libcamd64
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_STACKARG_H_
-#define _LIBC_STACKARG_H_
-
-#endif
-
-/** @}
- */
Index: uspace/lib/c/arch/arm32/meson.build
===================================================================
--- uspace/lib/c/arch/arm32/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/arch/arm32/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -30,5 +30,5 @@
 arch_src += files(
 	'src/atomic.c',
-	'src/entryjmp.S',
+	'src/entryjmp.c',
 	'src/thread_entry.S',
 	'src/syscall.c',
Index: uspace/lib/c/arch/arm32/src/atomic.c
===================================================================
--- uspace/lib/c/arch/arm32/src/atomic.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/arch/arm32/src/atomic.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -34,9 +34,14 @@
 
 #include <stdbool.h>
+#include "ras_page.h"
 
 volatile unsigned *ras_page;
 
-bool __atomic_compare_exchange_4(volatile unsigned *mem, unsigned *expected, unsigned desired, bool weak, int success, int failure)
+bool __atomic_compare_exchange_4(volatile void *mem0, void *expected0,
+    unsigned desired, bool weak, int success, int failure)
 {
+	volatile unsigned *mem = mem0;
+	unsigned *expected = expected0;
+
 	(void) success;
 	(void) failure;
@@ -81,6 +86,9 @@
 }
 
-unsigned short __atomic_fetch_add_2(volatile unsigned short *mem, unsigned short val, int model)
+unsigned short __atomic_fetch_add_2(volatile void *mem0, unsigned short val,
+    int model)
 {
+	volatile unsigned short *mem = mem0;
+
 	(void) model;
 
@@ -115,6 +123,8 @@
 }
 
-unsigned __atomic_fetch_add_4(volatile unsigned *mem, unsigned val, int model)
+unsigned __atomic_fetch_add_4(volatile void *mem0, unsigned val, int model)
 {
+	volatile unsigned *mem = mem0;
+
 	(void) model;
 
@@ -149,5 +159,5 @@
 }
 
-unsigned __atomic_fetch_sub_4(volatile unsigned *mem, unsigned val, int model)
+unsigned __atomic_fetch_sub_4(volatile void *mem, unsigned val, int model)
 {
 	return __atomic_fetch_add_4(mem, -val, model);
Index: uspace/lib/c/arch/arm32/src/crt0.S
===================================================================
--- uspace/lib/c/arch/arm32/src/crt0.S	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/arch/arm32/src/crt0.S	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -40,4 +40,5 @@
 SYMBOL(_start)
 	# Store the RAS page address into the ras_page variable
+	// FIXME: This only works for not-PIC executables.
 	ldr r0, =ras_page
 	str r2, [r0]
Index: pace/lib/c/arch/arm32/src/entryjmp.S
===================================================================
--- uspace/lib/c/arch/arm32/src/entryjmp.S	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,42 +1,0 @@
-#
-# Copyright (c) 2008 Jiri Svoboda
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# - Redistributions of source code must retain the above copyright
-#   notice, this list of conditions and the following disclaimer.
-# - Redistributions in binary form must reproduce the above copyright
-#   notice, this list of conditions and the following disclaimer in the
-#   documentation and/or other materials provided with the distribution.
-# - The name of the author may not be used to endorse or promote products
-#   derived from this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
-# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
-# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
-# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#
-
-#include <abi/asmtool.h>
-
-## void entry_point_jmp(void *entry_point, void *pcb);
-#
-# r0	contains entry_point
-# r1	contains pcb
-#
-# Jump to program entry point
-SYMBOL(entry_point_jmp)
-	# load ras_page address to r2
-	ldr r2, =ras_page
-	ldr r2, [r2]
-	# pcb is passed to the entry point in r1 (where it already is)
-	mov r15, r0
Index: uspace/lib/c/arch/arm32/src/entryjmp.c
===================================================================
--- uspace/lib/c/arch/arm32/src/entryjmp.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/c/arch/arm32/src/entryjmp.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2008 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <entry_point.h>
+#include "ras_page.h"
+
+void entry_point_jmp(void *entry_point, void *pcb)
+{
+	asm volatile (
+	    "mov r1, %0 \n"
+	    "mov r2, %1 \n"
+	    "mov r15, %2 \n"
+	    :
+	    : "r" (pcb), "r" (ras_page), "r" (entry_point)
+	    : "r1", "r2"
+	);
+
+	__builtin_unreachable();
+}
Index: uspace/lib/c/arch/arm32/src/ras_page.h
===================================================================
--- uspace/lib/c/arch/arm32/src/ras_page.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/c/arch/arm32/src/ras_page.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,1 @@
+extern volatile unsigned *ras_page;
Index: uspace/lib/c/arch/arm32/src/rtld/reloc.c
===================================================================
--- uspace/lib/c/arch/arm32/src/rtld/reloc.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/arch/arm32/src/rtld/reloc.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -127,14 +127,4 @@
 
 		switch (rel_type) {
-		case R_ARM_ABS32:
-			DPRINTF("ignore R_ARM_ABS32\n");
-			/*
-			 * Not sure why we get these static relocations, but
-			 * attempting to process them will crash the
-			 * loader. If we ignore them, everything
-			 * seems to work.
-			 */
-			break;
-
 		case R_ARM_TLS_DTPMOD32:
 			DPRINTF("fixup R_ARM_TLS_DTPMOD32\n");
@@ -189,5 +179,6 @@
 		case R_ARM_GLOB_DAT:
 		case R_ARM_JUMP_SLOT:
-			DPRINTF("fixup R_ARM_GLOB_DAT/JUMP_SLOT (S)\n");
+		case R_ARM_ABS32:
+			DPRINTF("fixup R_ARM_GLOB_DAT/JUMP_SLOT/ABS32 (S)\n");
 			*r_ptr = sym_addr;
 			break;
Index: uspace/lib/c/common
===================================================================
--- uspace/lib/c/common	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/c/common	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,1 @@
+../../../common
Index: pace/lib/c/generic/adt/checksum.c
===================================================================
--- uspace/lib/c/generic/adt/checksum.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,146 +1,0 @@
-/*
- * Copyright (c) 2012 Dominik Taborsky
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include <adt/checksum.h>
-
-/**
- * 256-value table of precomputed polynomials for CRC32. Note
- * the values depend on the selected divisor polynomial (currently
- * 0xedb88320) and whether the CRC computation is reflected or not.
- * See http://www.repairfaq.org/filipg/LINK/F_crc_v3.html for a perfect
- * source of info about this.
- */
-uint32_t poly_table[256] = {
-	0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
-	0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
-	0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
-	0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
-	0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
-	0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
-	0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
-	0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
-	0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
-	0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
-	0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
-	0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
-	0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
-	0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
-	0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
-	0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
-	0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
-	0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
-	0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
-	0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
-	0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
-	0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
-	0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
-	0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
-	0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
-	0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
-	0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
-	0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
-	0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
-	0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
-	0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
-	0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
-	0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
-	0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
-	0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
-	0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
-	0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
-	0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
-	0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
-	0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
-	0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
-	0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
-	0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
-	0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
-	0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
-	0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
-	0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
-	0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
-	0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
-	0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
-	0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
-	0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
-	0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
-	0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
-	0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
-	0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
-	0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
-	0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
-	0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
-	0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
-	0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
-	0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
-	0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
-	0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
-};
-
-/** Compute CRC32 value.
- *
- * See wiki.osdev.org/CRC32 for reference.
- *
- * @param[in] data   Data to process.
- * @param[in] length Length of the data in bytes.
- *
- * @return Computed CRC32 of the data.
- *
- */
-uint32_t compute_crc32(uint8_t *data, size_t length)
-{
-	return compute_crc32_seed(data, length, 0);
-}
-
-/** Compute CRC32 value with initial seed.
- *
- * Use this when checksumming non-continous data (linked lists, trees,
- * etc.). On the first data block call compute_crc32() and use the result
- * as the seed for the following call of compute_crc32_seed() on the next
- * block. Then use the result of that call as the seed of the following
- * call, etc.
- *
- * @param[in] data   Fata to process.
- * @param[in] length Length of the data in bytes.
- * @param[in] seed   The starting value of the CRC.
- *
- * @return Computed CRC32 of the data of all the previous blocks.
- *
- */
-uint32_t compute_crc32_seed(uint8_t *data, size_t length, uint32_t seed)
-{
-	uint32_t crc;
-
-	for (crc = ~seed; length > 0; length--)
-		crc = poly_table[((uint8_t) crc ^ *(data++))] ^ (crc >> 8);
-
-	return (~crc);
-}
-
-/** @}
- */
Index: pace/lib/c/generic/adt/circ_buf.c
===================================================================
--- uspace/lib/c/generic/adt/circ_buf.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,113 +1,0 @@
-/*
- * Copyright (c) 2017 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-
-/** @file Circular buffer
- */
-
-#include <adt/circ_buf.h>
-#include <errno.h>
-#include <mem.h>
-#include <stddef.h>
-
-/** Initialize circular buffer.
- *
- * @param cbuf Circular buffer
- * @param buf Buffer for storing data
- * @param nmemb Number of entries in @a buf
- * @param size Size of individual buffer entry
- */
-void circ_buf_init(circ_buf_t *cbuf, void *buf, size_t nmemb, size_t size)
-{
-	cbuf->buf = buf;
-	cbuf->nmemb = nmemb;
-	cbuf->size = size;
-	cbuf->rp = 0;
-	cbuf->wp = 0;
-	cbuf->nused = 0;
-}
-
-/** Return number of free buffer entries.
- *
- * @param cbuf Circular buffer
- * @return Number of free buffer entries
- */
-size_t circ_buf_nfree(circ_buf_t *cbuf)
-{
-	return cbuf->nmemb - cbuf->nused;
-}
-
-/** Return number of used buffer entries.
- *
- * @param cbuf Circular buffer
- * @return Number of used buffer entries
- */
-size_t circ_buf_nused(circ_buf_t *cbuf)
-{
-	return cbuf->nused;
-}
-
-/** Push new entry into circular buffer.
- *
- * @param cbuf Circular buffer
- * @param data Pointer to entry data
- * @return EOK on success, EAGAIN if circular buffer is full
- */
-errno_t circ_buf_push(circ_buf_t *cbuf, const void *data)
-{
-	if (circ_buf_nfree(cbuf) == 0)
-		return EAGAIN;
-
-	memcpy(cbuf->buf + cbuf->size * cbuf->wp, data, cbuf->size);
-	cbuf->wp = (cbuf->wp + 1) % cbuf->nmemb;
-	cbuf->nused++;
-	return EOK;
-}
-
-/** Pop entry from circular buffer.
- *
- * @param cbuf Circular buffer
- * @param datab Pointer to data buffer for storing entry
- * @return EOK on success, EAGAIN if circular buffer is full
- */
-errno_t circ_buf_pop(circ_buf_t *cbuf, void *datab)
-{
-	if (cbuf->nused == 0)
-		return EAGAIN;
-
-	memcpy(datab, cbuf->buf + cbuf->size * cbuf->rp, cbuf->size);
-	cbuf->rp = (cbuf->rp + 1) % cbuf->nmemb;
-	cbuf->nused--;
-	return EOK;
-}
-
-/** @}
- */
Index: pace/lib/c/generic/adt/hash_table.c
===================================================================
--- uspace/lib/c/generic/adt/hash_table.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,472 +1,0 @@
-/*
- * Copyright (c) 2008 Jakub Jermar
- * Copyright (c) 2012 Adam Hraska
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-/*
- * This is an implementation of a generic resizable chained hash table.
- *
- * The table grows to 2*n+1 buckets each time, starting at n == 89,
- * per Thomas Wang's recommendation:
- * http://www.concentric.net/~Ttwang/tech/hashsize.htm
- *
- * This policy produces prime table sizes for the first five resizes
- * and generally produces table sizes which are either prime or
- * have fairly large (prime/odd) divisors. Having a prime table size
- * mitigates the use of suboptimal hash functions and distributes
- * items over the whole table.
- */
-
-#include <adt/hash_table.h>
-#include <adt/list.h>
-#include <assert.h>
-#include <member.h>
-#include <stdlib.h>
-#include <str.h>
-
-/* Optimal initial bucket count. See comment above. */
-#define HT_MIN_BUCKETS  89
-/* The table is resized when the average load per bucket exceeds this number. */
-#define HT_MAX_LOAD     2
-
-static size_t round_up_size(size_t);
-static bool alloc_table(size_t, list_t **);
-static void clear_items(hash_table_t *);
-static void resize(hash_table_t *, size_t);
-static void grow_if_needed(hash_table_t *);
-static void shrink_if_needed(hash_table_t *);
-
-/* Dummy do nothing callback to invoke in place of remove_callback == NULL. */
-static void nop_remove_callback(ht_link_t *item)
-{
-	/* no-op */
-}
-
-/** Create chained hash table.
- *
- * @param h        Hash table structure. Will be initialized by this call.
- * @param init_size Initial desired number of hash table buckets. Pass zero
- *                 if you want the default initial size.
- * @param max_load The table is resized when the average load per bucket
- *                 exceeds this number. Pass zero if you want the default.
- * @param op       Hash table operations structure. remove_callback()
- *                 is optional and can be NULL if no action is to be taken
- *                 upon removal. equal() is optional if and only if
- *                 hash_table_insert_unique() will never be invoked.
- *                 All other operations are mandatory.
- *
- * @return True on success
- *
- */
-bool hash_table_create(hash_table_t *h, size_t init_size, size_t max_load,
-    hash_table_ops_t *op)
-{
-	assert(h);
-	assert(op && op->hash && op->key_hash && op->key_equal);
-
-	/* Check for compulsory ops. */
-	if (!op || !op->hash || !op->key_hash || !op->key_equal)
-		return false;
-
-	h->bucket_cnt = round_up_size(init_size);
-
-	if (!alloc_table(h->bucket_cnt, &h->bucket))
-		return false;
-
-	h->max_load = (max_load == 0) ? HT_MAX_LOAD : max_load;
-	h->item_cnt = 0;
-	h->op = op;
-	h->full_item_cnt = h->max_load * h->bucket_cnt;
-	h->apply_ongoing = false;
-
-	if (h->op->remove_callback == NULL) {
-		h->op->remove_callback = nop_remove_callback;
-	}
-
-	return true;
-}
-
-/** Destroy a hash table instance.
- *
- * @param h Hash table to be destroyed.
- *
- */
-void hash_table_destroy(hash_table_t *h)
-{
-	assert(h && h->bucket);
-	assert(!h->apply_ongoing);
-
-	clear_items(h);
-
-	free(h->bucket);
-
-	h->bucket = NULL;
-	h->bucket_cnt = 0;
-}
-
-/** Returns true if there are no items in the table. */
-bool hash_table_empty(hash_table_t *h)
-{
-	assert(h && h->bucket);
-	return h->item_cnt == 0;
-}
-
-/** Returns the number of items in the table. */
-size_t hash_table_size(hash_table_t *h)
-{
-	assert(h && h->bucket);
-	return h->item_cnt;
-}
-
-/** Remove all elements from the hash table
- *
- * @param h Hash table to be cleared
- */
-void hash_table_clear(hash_table_t *h)
-{
-	assert(h && h->bucket);
-	assert(!h->apply_ongoing);
-
-	clear_items(h);
-
-	/* Shrink the table to its minimum size if possible. */
-	if (HT_MIN_BUCKETS < h->bucket_cnt) {
-		resize(h, HT_MIN_BUCKETS);
-	}
-}
-
-/** Unlinks and removes all items but does not resize. */
-static void clear_items(hash_table_t *h)
-{
-	if (h->item_cnt == 0)
-		return;
-
-	for (size_t idx = 0; idx < h->bucket_cnt; ++idx) {
-		list_foreach_safe(h->bucket[idx], cur, next) {
-			assert(cur);
-			ht_link_t *cur_link = member_to_inst(cur, ht_link_t, link);
-
-			list_remove(cur);
-			h->op->remove_callback(cur_link);
-		}
-	}
-
-	h->item_cnt = 0;
-}
-
-/** Insert item into a hash table.
- *
- * @param h    Hash table.
- * @param item Item to be inserted into the hash table.
- */
-void hash_table_insert(hash_table_t *h, ht_link_t *item)
-{
-	assert(item);
-	assert(h && h->bucket);
-	assert(!h->apply_ongoing);
-
-	size_t idx = h->op->hash(item) % h->bucket_cnt;
-
-	list_append(&item->link, &h->bucket[idx]);
-	++h->item_cnt;
-	grow_if_needed(h);
-}
-
-/** Insert item into a hash table if not already present.
- *
- * @param h    Hash table.
- * @param item Item to be inserted into the hash table.
- *
- * @return False if such an item had already been inserted.
- * @return True if the inserted item was the only item with such a lookup key.
- */
-bool hash_table_insert_unique(hash_table_t *h, ht_link_t *item)
-{
-	assert(item);
-	assert(h && h->bucket && h->bucket_cnt);
-	assert(h->op && h->op->hash && h->op->equal);
-	assert(!h->apply_ongoing);
-
-	size_t idx = h->op->hash(item) % h->bucket_cnt;
-
-	/* Check for duplicates. */
-	list_foreach(h->bucket[idx], link, ht_link_t, cur_link) {
-		/*
-		 * We could filter out items using their hashes first, but
-		 * calling equal() might very well be just as fast.
-		 */
-		if (h->op->equal(cur_link, item))
-			return false;
-	}
-
-	list_append(&item->link, &h->bucket[idx]);
-	++h->item_cnt;
-	grow_if_needed(h);
-
-	return true;
-}
-
-/** Search hash table for an item matching keys.
- *
- * @param h   Hash table.
- * @param key Array of all keys needed to compute hash index.
- *
- * @return Matching item on success, NULL if there is no such item.
- *
- */
-ht_link_t *hash_table_find(const hash_table_t *h, const void *key)
-{
-	assert(h && h->bucket);
-
-	size_t idx = h->op->key_hash(key) % h->bucket_cnt;
-
-	list_foreach(h->bucket[idx], link, ht_link_t, cur_link) {
-		/*
-		 * Is this is the item we are looking for? We could have first
-		 * checked if the hashes match but op->key_equal() may very well be
-		 * just as fast as op->hash().
-		 */
-		if (h->op->key_equal(key, cur_link)) {
-			return cur_link;
-		}
-	}
-
-	return NULL;
-}
-
-/** Find the next item equal to item. */
-ht_link_t *
-hash_table_find_next(const hash_table_t *h, ht_link_t *first, ht_link_t *item)
-{
-	assert(item);
-	assert(h && h->bucket);
-
-	size_t idx = h->op->hash(item) % h->bucket_cnt;
-
-	/* Traverse the circular list until we reach the starting item again. */
-	for (link_t *cur = item->link.next; cur != &first->link;
-	    cur = cur->next) {
-		assert(cur);
-
-		if (cur == &h->bucket[idx].head)
-			continue;
-
-		ht_link_t *cur_link = member_to_inst(cur, ht_link_t, link);
-		/*
-		 * Is this is the item we are looking for? We could have first
-		 * checked if the hashes match but op->equal() may very well be
-		 * just as fast as op->hash().
-		 */
-		if (h->op->equal(cur_link, item)) {
-			return cur_link;
-		}
-	}
-
-	return NULL;
-}
-
-/** Remove all matching items from hash table.
- *
- * For each removed item, h->remove_callback() is called.
- *
- * @param h    Hash table.
- * @param key  Array of keys that will be compared against items of
- *             the hash table.
- *
- * @return Returns the number of removed items.
- */
-size_t hash_table_remove(hash_table_t *h, const void *key)
-{
-	assert(h && h->bucket);
-	assert(!h->apply_ongoing);
-
-	size_t idx = h->op->key_hash(key) % h->bucket_cnt;
-
-	size_t removed = 0;
-
-	list_foreach_safe(h->bucket[idx], cur, next) {
-		ht_link_t *cur_link = member_to_inst(cur, ht_link_t, link);
-
-		if (h->op->key_equal(key, cur_link)) {
-			++removed;
-			list_remove(cur);
-			h->op->remove_callback(cur_link);
-		}
-	}
-
-	h->item_cnt -= removed;
-	shrink_if_needed(h);
-
-	return removed;
-}
-
-/** Removes an item already present in the table. The item must be in the table. */
-void hash_table_remove_item(hash_table_t *h, ht_link_t *item)
-{
-	assert(item);
-	assert(h && h->bucket);
-	assert(link_in_use(&item->link));
-
-	list_remove(&item->link);
-	--h->item_cnt;
-	h->op->remove_callback(item);
-	shrink_if_needed(h);
-}
-
-/** Apply function to all items in hash table.
- *
- * @param h   Hash table.
- * @param f   Function to be applied. Return false if no more items
- *            should be visited. The functor may only delete the supplied
- *            item. It must not delete the successor of the item passed
- *            in the first argument.
- * @param arg Argument to be passed to the function.
- */
-void hash_table_apply(hash_table_t *h, bool (*f)(ht_link_t *, void *), void *arg)
-{
-	assert(f);
-	assert(h && h->bucket);
-
-	if (h->item_cnt == 0)
-		return;
-
-	h->apply_ongoing = true;
-
-	for (size_t idx = 0; idx < h->bucket_cnt; ++idx) {
-		list_foreach_safe(h->bucket[idx], cur, next) {
-			ht_link_t *cur_link = member_to_inst(cur, ht_link_t, link);
-			/*
-			 * The next pointer had already been saved. f() may safely
-			 * delete cur (but not next!).
-			 */
-			if (!f(cur_link, arg))
-				goto out;
-		}
-	}
-out:
-	h->apply_ongoing = false;
-
-	shrink_if_needed(h);
-	grow_if_needed(h);
-}
-
-/** Rounds up size to the nearest suitable table size. */
-static size_t round_up_size(size_t size)
-{
-	size_t rounded_size = HT_MIN_BUCKETS;
-
-	while (rounded_size < size) {
-		rounded_size = 2 * rounded_size + 1;
-	}
-
-	return rounded_size;
-}
-
-/** Allocates and initializes the desired number of buckets. True if successful. */
-static bool alloc_table(size_t bucket_cnt, list_t **pbuckets)
-{
-	assert(pbuckets && HT_MIN_BUCKETS <= bucket_cnt);
-
-	list_t *buckets = malloc(bucket_cnt * sizeof(list_t));
-	if (!buckets)
-		return false;
-
-	for (size_t i = 0; i < bucket_cnt; i++)
-		list_initialize(&buckets[i]);
-
-	*pbuckets = buckets;
-	return true;
-}
-
-/** Shrinks the table if the table is only sparely populated. */
-static inline void shrink_if_needed(hash_table_t *h)
-{
-	if (h->item_cnt <= h->full_item_cnt / 4 && HT_MIN_BUCKETS < h->bucket_cnt) {
-		/*
-		 * Keep the bucket_cnt odd (possibly also prime).
-		 * Shrink from 2n + 1 to n. Integer division discards the +1.
-		 */
-		size_t new_bucket_cnt = h->bucket_cnt / 2;
-		resize(h, new_bucket_cnt);
-	}
-}
-
-/** Grows the table if table load exceeds the maximum allowed. */
-static inline void grow_if_needed(hash_table_t *h)
-{
-	/* Grow the table if the average bucket load exceeds the maximum. */
-	if (h->full_item_cnt < h->item_cnt) {
-		/* Keep the bucket_cnt odd (possibly also prime). */
-		size_t new_bucket_cnt = 2 * h->bucket_cnt + 1;
-		resize(h, new_bucket_cnt);
-	}
-}
-
-/** Allocates and rehashes items to a new table. Frees the old table. */
-static void resize(hash_table_t *h, size_t new_bucket_cnt)
-{
-	assert(h && h->bucket);
-	assert(HT_MIN_BUCKETS <= new_bucket_cnt);
-
-	/* We are traversing the table and resizing would mess up the buckets. */
-	if (h->apply_ongoing)
-		return;
-
-	list_t *new_buckets;
-
-	/* Leave the table as is if we cannot resize. */
-	if (!alloc_table(new_bucket_cnt, &new_buckets))
-		return;
-
-	if (0 < h->item_cnt) {
-		/* Rehash all the items to the new table. */
-		for (size_t old_idx = 0; old_idx < h->bucket_cnt; ++old_idx) {
-			list_foreach_safe(h->bucket[old_idx], cur, next) {
-				ht_link_t *cur_link = member_to_inst(cur, ht_link_t, link);
-
-				size_t new_idx = h->op->hash(cur_link) % new_bucket_cnt;
-				list_remove(cur);
-				list_append(cur, &new_buckets[new_idx]);
-			}
-		}
-	}
-
-	free(h->bucket);
-	h->bucket = new_buckets;
-	h->bucket_cnt = new_bucket_cnt;
-	h->full_item_cnt = h->max_load * h->bucket_cnt;
-}
-
-/** @}
- */
Index: pace/lib/c/generic/adt/list.c
===================================================================
--- uspace/lib/c/generic/adt/list.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,117 +1,0 @@
-/*
- * Copyright (c) 2004 Jakub Jermar
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-
-/**
- * @file
- * @brief	Functions completing doubly linked circular list implementation.
- *
- * This file contains some of the functions implementing doubly linked circular lists.
- * However, this ADT is mostly implemented in @ref list.h.
- */
-
-#include <adt/list.h>
-#include <stdbool.h>
-#include <stdint.h>
-
-/** Check for membership
- *
- * Check whether link is contained in a list.
- * Membership is defined as pointer equivalence.
- *
- * @param link	Item to look for.
- * @param list	List to look in.
- *
- * @return true if link is contained in list, false otherwise.
- *
- */
-bool list_member(const link_t *link, const list_t *list)
-{
-	bool found = false;
-	link_t *hlp = list->head.next;
-
-	while (hlp != &list->head) {
-		if (hlp == link) {
-			found = true;
-			break;
-		}
-		hlp = hlp->next;
-	}
-
-	return found;
-}
-
-/** Moves items of one list into another after the specified item.
- *
- * Inserts all items of @a list after item at @a pos in another list.
- * Both lists may be empty.
- *
- * @param list Source list to move after pos. Empty afterwards.
- * @param pos Source items will be placed after this item.
- */
-void list_splice(list_t *list, link_t *pos)
-{
-	if (list_empty(list))
-		return;
-
-	/* Attach list to destination. */
-	list->head.next->prev = pos;
-	list->head.prev->next = pos->next;
-
-	/* Link destination list to the added list. */
-	pos->next->prev = list->head.prev;
-	pos->next = list->head.next;
-
-	list_initialize(list);
-}
-
-/** Count list items
- *
- * Return the number of items in the list.
- *
- * @param list		List to count.
- * @return		Number of items in the list.
- */
-size_t list_count(const list_t *list)
-{
-	size_t count = 0;
-
-	link_t *link = list_first(list);
-	while (link != NULL) {
-		count++;
-		link = list_next(link, list);
-	}
-
-	return count;
-}
-
-/** @}
- */
Index: pace/lib/c/generic/adt/odict.c
===================================================================
--- uspace/lib/c/generic/adt/odict.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,1 +1,0 @@
-../../../../../kernel/generic/src/adt/odict.c
Index: uspace/lib/c/generic/assert.c
===================================================================
--- uspace/lib/c/generic/assert.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/generic/assert.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -46,6 +46,6 @@
 	 * Send the message safely to kio. Nested asserts should not occur.
 	 */
-	kio_printf("Assertion failed (%s) in task %ld, file \"%s\", line %u.\n",
-	    cond, (long) task_get_id(), file, line);
+	kio_printf("Assertion failed (%s) in task %" PRIu64 ", file \"%s\", line %u.\n",
+	    cond, task_get_id(), file, line);
 
 	stacktrace_kio_print();
@@ -60,6 +60,6 @@
 	 * Send the message safely to kio. Nested asserts should not occur.
 	 */
-	kio_printf("Assertion failed (%s) in task %ld, file \"%s\", line %u.\n",
-	    cond, (long) task_get_id(), file, line);
+	kio_printf("Assertion failed (%s) in task %" PRIu64 ", file \"%s\", line %u.\n",
+	    cond, task_get_id(), file, line);
 
 	stacktrace_kio_print();
@@ -77,6 +77,6 @@
 	 * assertions.
 	 */
-	kio_printf("Assertion failed (%s) in task %ld, file \"%s\", line %u.\n",
-	    cond, (long) task_get_id(), file, line);
+	kio_printf("Assertion failed (%s) in task %" PRIu64 ", file \"%s\", line %u.\n",
+	    cond, task_get_id(), file, line);
 	stacktrace_print();
 
Index: uspace/lib/c/generic/async/ports.c
===================================================================
--- uspace/lib/c/generic/async/ports.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/generic/async/ports.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -123,5 +123,5 @@
 
 /** Operations for the port hash table. */
-static hash_table_ops_t interface_hash_table_ops = {
+static const hash_table_ops_t interface_hash_table_ops = {
 	.hash = interface_hash,
 	.key_hash = interface_key_hash,
@@ -151,5 +151,5 @@
 
 /** Operations for the port hash table. */
-static hash_table_ops_t port_hash_table_ops = {
+static const hash_table_ops_t port_hash_table_ops = {
 	.hash = port_hash,
 	.key_hash = port_key_hash,
Index: uspace/lib/c/generic/async/server.c
===================================================================
--- uspace/lib/c/generic/async/server.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/generic/async/server.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -250,5 +250,5 @@
 
 /** Operations for the client hash table. */
-static hash_table_ops_t client_hash_table_ops = {
+static const hash_table_ops_t client_hash_table_ops = {
 	.hash = client_hash,
 	.key_hash = client_key_hash,
@@ -511,5 +511,5 @@
 
 /** Operations for the notification hash table. */
-static hash_table_ops_t notification_hash_table_ops = {
+static const hash_table_ops_t notification_hash_table_ops = {
 	.hash = notification_hash,
 	.key_hash = notification_key_hash,
Index: pace/lib/c/generic/bsearch.c
===================================================================
--- uspace/lib/c/generic/bsearch.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,78 +1,0 @@
-/*
- * Copyright (c) 2018 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-
-/**
- * @file
- * @brief Binary search.
- */
-
-#include <bsearch.h>
-#include <stddef.h>
-
-/** Binary search.
- *
- * @param key Key to search for
- * @param base Array of objects
- * @param nmemb Number of objects in array
- * @param size Size of each object
- * @param compar Comparison function
- */
-void *bsearch(const void *key, const void *base, size_t nmemb, size_t size,
-    int (*compar)(const void *, const void *))
-{
-	size_t pividx;
-	const void *pivot;
-	int r;
-
-	while (nmemb != 0) {
-		pividx = nmemb / 2;
-		pivot = base + size * pividx;
-
-		r = compar(key, pivot);
-		if (r == 0)
-			return (void *)pivot;
-
-		if (r < 0) {
-			/* Now only look at members preceding pivot */
-			nmemb = pividx;
-		} else {
-			/* Now only look at members following pivot */
-			nmemb = nmemb - pividx - 1;
-			base += size * (pividx + 1);
-		}
-	}
-
-	return NULL;
-}
-
-/** @}
- */
Index: pace/lib/c/generic/clipboard.c
===================================================================
--- uspace/lib/c/generic/clipboard.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,210 +1,0 @@
-/*
- * Copyright (c) 2009 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- * @brief System clipboard API.
- *
- * The clipboard data is managed by the clipboard service and it is shared by
- * the entire system.
- *
- */
-
-#include <async.h>
-#include <clipboard.h>
-#include <errno.h>
-#include <fibril_synch.h>
-#include <ipc/clipboard.h>
-#include <ipc/services.h>
-#include <loc.h>
-#include <stdlib.h>
-#include <str.h>
-
-static FIBRIL_MUTEX_INITIALIZE(clip_mutex);
-static async_sess_t *clip_sess = NULL;
-
-/** Start an async exchange on the clipboard session.
- *
- * @return New exchange.
- *
- */
-static async_exch_t *clip_exchange_begin(void)
-{
-	service_id_t sid;
-	errno_t rc;
-
-	fibril_mutex_lock(&clip_mutex);
-
-	while (clip_sess == NULL) {
-		rc = loc_service_get_id(SERVICE_NAME_CLIPBOARD, &sid,
-		    IPC_FLAG_BLOCKING);
-		if (rc != EOK)
-			continue;
-
-		clip_sess = loc_service_connect(sid, INTERFACE_CLIPBOARD,
-		    IPC_FLAG_BLOCKING);
-	}
-
-	fibril_mutex_unlock(&clip_mutex);
-
-	return async_exchange_begin(clip_sess);
-}
-
-/** Finish an async exchange on the clipboard session.
- *
- * @param exch Exchange to be finished.
- *
- */
-static void clip_exchange_end(async_exch_t *exch)
-{
-	async_exchange_end(exch);
-}
-
-/** Copy string to clipboard.
- *
- * Sets the clipboard contents to @a str. Passing an empty string or NULL
- * makes the clipboard empty.
- *
- * @param str String to put to clipboard or NULL.
- *
- * @return Zero on success or an error code.
- *
- */
-errno_t clipboard_put_str(const char *str)
-{
-	size_t size = str_size(str);
-
-	if (size == 0) {
-		async_exch_t *exch = clip_exchange_begin();
-		errno_t rc = async_req_1_0(exch, CLIPBOARD_PUT_DATA,
-		    CLIPBOARD_TAG_NONE);
-		clip_exchange_end(exch);
-
-		return (errno_t) rc;
-	} else {
-		async_exch_t *exch = clip_exchange_begin();
-		aid_t req = async_send_1(exch, CLIPBOARD_PUT_DATA, CLIPBOARD_TAG_DATA,
-		    NULL);
-		errno_t rc = async_data_write_start(exch, (void *) str, size);
-		clip_exchange_end(exch);
-
-		if (rc != EOK) {
-			errno_t rc_orig;
-			async_wait_for(req, &rc_orig);
-			if (rc_orig == EOK)
-				return (errno_t) rc;
-			else
-				return (errno_t) rc_orig;
-		}
-
-		async_wait_for(req, &rc);
-
-		return (errno_t) rc;
-	}
-}
-
-/** Get a copy of clipboard contents.
- *
- * Returns a new string that can be deallocated with free().
- *
- * @param str Here pointer to the newly allocated string is stored.
- *
- * @return Zero on success or an error code.
- *
- */
-errno_t clipboard_get_str(char **str)
-{
-	/* Loop until clipboard read succesful */
-	while (true) {
-		async_exch_t *exch = clip_exchange_begin();
-
-		sysarg_t size;
-		sysarg_t tag;
-		errno_t rc = async_req_0_2(exch, CLIPBOARD_CONTENT, &size, &tag);
-
-		clip_exchange_end(exch);
-
-		if (rc != EOK)
-			return (errno_t) rc;
-
-		char *sbuf;
-
-		switch (tag) {
-		case CLIPBOARD_TAG_NONE:
-			sbuf = malloc(1);
-			if (sbuf == NULL)
-				return ENOMEM;
-
-			sbuf[0] = 0;
-			*str = sbuf;
-			return EOK;
-		case CLIPBOARD_TAG_DATA:
-			sbuf = malloc(size + 1);
-			if (sbuf == NULL)
-				return ENOMEM;
-
-			exch = clip_exchange_begin();
-			aid_t req = async_send_1(exch, CLIPBOARD_GET_DATA, tag, NULL);
-			rc = async_data_read_start(exch, (void *) sbuf, size);
-			clip_exchange_end(exch);
-
-			if ((errno_t) rc == EOVERFLOW) {
-				/*
-				 * The data in the clipboard has changed since
-				 * the last call of CLIPBOARD_CONTENT
-				 */
-				break;
-			}
-
-			if (rc != EOK) {
-				errno_t rc_orig;
-				async_wait_for(req, &rc_orig);
-				if (rc_orig == EOK)
-					return (errno_t) rc;
-				else
-					return (errno_t) rc_orig;
-			}
-
-			async_wait_for(req, &rc);
-
-			if (rc == EOK) {
-				sbuf[size] = 0;
-				*str = sbuf;
-			}
-
-			return rc;
-		default:
-			return EINVAL;
-		}
-	}
-}
-
-/** @}
- */
Index: pace/lib/c/generic/corecfg.c
===================================================================
--- uspace/lib/c/generic/corecfg.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,92 +1,0 @@
-/*
- * Copyright (c) 2013 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include <async.h>
-#include <assert.h>
-#include <corecfg.h>
-#include <errno.h>
-#include <ipc/corecfg.h>
-#include <ipc/services.h>
-#include <loc.h>
-#include <stdlib.h>
-
-static async_sess_t *corecfg_sess = NULL;
-
-errno_t corecfg_init(void)
-{
-	service_id_t corecfg_svc;
-	errno_t rc;
-
-	assert(corecfg_sess == NULL);
-
-	rc = loc_service_get_id(SERVICE_NAME_CORECFG, &corecfg_svc,
-	    IPC_FLAG_BLOCKING);
-	if (rc != EOK)
-		return ENOENT;
-
-	corecfg_sess = loc_service_connect(corecfg_svc, INTERFACE_CORECFG,
-	    IPC_FLAG_BLOCKING);
-	if (corecfg_sess == NULL)
-		return ENOENT;
-
-	return EOK;
-}
-
-/** Get core dump enable status. */
-errno_t corecfg_get_enable(bool *renable)
-{
-	async_exch_t *exch = async_exchange_begin(corecfg_sess);
-	sysarg_t enable;
-
-	errno_t rc = async_req_0_1(exch, CORECFG_GET_ENABLE, &enable);
-
-	async_exchange_end(exch);
-
-	if (rc != EOK)
-		return rc;
-
-	*renable = enable;
-	return EOK;
-}
-
-/** Enable or disable core dumps. */
-errno_t corecfg_set_enable(bool enable)
-{
-	async_exch_t *exch = async_exchange_begin(corecfg_sess);
-	errno_t rc = async_req_1_0(exch, CORECFG_SET_ENABLE, (sysarg_t) enable);
-
-	async_exchange_end(exch);
-
-	if (rc != EOK)
-		return rc;
-
-	return EOK;
-}
-
-/** @}
- */
Index: pace/lib/c/generic/ctype.c
===================================================================
--- uspace/lib/c/generic/ctype.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,126 +1,0 @@
-/*
- * Copyright (c) 2006 Josef Cejka
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#include <ctype.h>
-
-int islower(int c)
-{
-	return ((c >= 'a') && (c <= 'z'));
-}
-
-int isupper(int c)
-{
-	return ((c >= 'A') && (c <= 'Z'));
-}
-
-int isalpha(int c)
-{
-	return (islower(c) || isupper(c));
-}
-
-int isdigit(int c)
-{
-	return ((c >= '0') && (c <= '9'));
-}
-
-int isalnum(int c)
-{
-	return (isalpha(c) || isdigit(c));
-}
-
-int isblank(int c)
-{
-	return c == ' ' || c == '\t';
-}
-
-int iscntrl(int c)
-{
-	return (c >= 0 && c < 0x20) || c == 0x7E;
-}
-
-int isprint(int c)
-{
-	return c >= 0 && c < 0x80 && !iscntrl(c);
-}
-
-int isgraph(int c)
-{
-	return isprint(c) && c != ' ';
-}
-
-int isspace(int c)
-{
-	switch (c) {
-	case ' ':
-	case '\n':
-	case '\t':
-	case '\f':
-	case '\r':
-	case '\v':
-		return 1;
-	default:
-		return 0;
-	}
-}
-
-int ispunct(int c)
-{
-	return !isspace(c) && !isalnum(c) && isprint(c);
-}
-
-int isxdigit(int c)
-{
-	return isdigit(c) ||
-	    (c >= 'a' && c <= 'f') ||
-	    (c >= 'A' && c <= 'F');
-}
-
-int tolower(int c)
-{
-	if (isupper(c))
-		return (c + ('a' - 'A'));
-	else
-		return c;
-}
-
-int toupper(int c)
-{
-	if (islower(c))
-		return (c + ('A' - 'a'));
-	else
-		return c;
-}
-
-/** @}
- */
Index: uspace/lib/c/generic/dbgcon.c
===================================================================
--- uspace/lib/c/generic/dbgcon.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/c/generic/dbgcon.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,45 @@
+/*
+ * 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 libc
+ * @{
+ */
+/** @file
+ */
+
+#include <dbgcon.h>
+#include <libc.h>
+#include <stdbool.h>
+
+bool dbgcon_enable(void)
+{
+	return __SYSCALL0(SYS_DEBUG_CONSOLE);
+}
+
+/** @}
+ */
Index: uspace/lib/c/generic/device/hw_res.c
===================================================================
--- uspace/lib/c/generic/device/hw_res.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/generic/device/hw_res.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2010 Lenka Trochtova
  * All rights reserved.
@@ -161,4 +162,28 @@
 }
 
+/** Get bus flags.
+ *
+ * @param sess HW res session
+ * @param rflags Place to store the flags
+ *
+ * @return Error code.
+ *
+ */
+errno_t hw_res_get_flags(async_sess_t *sess, hw_res_flags_t *rflags)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+
+	sysarg_t flags;
+	const errno_t ret = async_req_1_1(exch, DEV_IFACE_ID(HW_RES_DEV_IFACE),
+	    HW_RES_GET_FLAGS, &flags);
+
+	async_exchange_end(exch);
+
+	if (ret == EOK)
+		*rflags = flags;
+
+	return ret;
+}
+
 /** @}
  */
Index: pace/lib/c/generic/gsort.c
===================================================================
--- uspace/lib/c/generic/gsort.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,128 +1,0 @@
-/*
- * Copyright (c) 2005 Sergey Bondari
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-
-/**
- * @file
- * @brief Gnome Sort.
- *
- * This file contains an implementation of gnome sort
- *
- */
-
-#include <gsort.h>
-#include <inttypes.h>
-#include <mem.h>
-#include <stdlib.h>
-
-/** Immediate buffer size.
- *
- * For small buffer sizes avoid doing malloc()
- * and use the stack.
- *
- */
-#define IBUF_SIZE  32
-
-/** Array accessor.
- *
- */
-#define INDEX(buf, i, elem_size)  ((buf) + (i) * (elem_size))
-
-/** Gnome sort
- *
- * Apply generic gnome sort algorithm on supplied data,
- * using pre-allocated buffer.
- *
- * @param data      Pointer to data to be sorted.
- * @param cnt       Number of elements to be sorted.
- * @param elem_size Size of one element.
- * @param cmp       Comparator function.
- * @param arg       3rd argument passed to cmp.
- * @param slot      Pointer to scratch memory buffer
- *                  elem_size bytes long.
- *
- */
-static void _gsort(void *data, size_t cnt, size_t elem_size, sort_cmp_t cmp,
-    void *arg, void *slot)
-{
-	size_t i = 0;
-
-	while (i < cnt) {
-		if ((i != 0) &&
-		    (cmp(INDEX(data, i, elem_size),
-		    INDEX(data, i - 1, elem_size), arg) < 0)) {
-			memcpy(slot, INDEX(data, i, elem_size), elem_size);
-			memcpy(INDEX(data, i, elem_size), INDEX(data, i - 1, elem_size),
-			    elem_size);
-			memcpy(INDEX(data, i - 1, elem_size), slot, elem_size);
-			i--;
-		} else
-			i++;
-	}
-}
-
-/** Gnome sort wrapper
- *
- * This is only a wrapper that takes care of memory
- * allocations for storing the slot element for generic
- * gnome sort algorithm.
- *
- * @param data      Pointer to data to be sorted.
- * @param cnt       Number of elements to be sorted.
- * @param elem_size Size of one element.
- * @param cmp       Comparator function.
- * @param arg       3rd argument passed to cmp.
- *
- * @return True if sorting succeeded.
- *
- */
-bool gsort(void *data, size_t cnt, size_t elem_size, sort_cmp_t cmp, void *arg)
-{
-	uint8_t ibuf_slot[IBUF_SIZE];
-	void *slot;
-
-	if (elem_size > IBUF_SIZE) {
-		slot = (void *) malloc(elem_size);
-		if (!slot)
-			return false;
-	} else
-		slot = (void *) ibuf_slot;
-
-	_gsort(data, cnt, elem_size, cmp, arg, slot);
-
-	if (elem_size > IBUF_SIZE)
-		free(slot);
-
-	return true;
-}
-
-/** @}
- */
Index: uspace/lib/c/generic/io/asprintf.c
===================================================================
--- uspace/lib/c/generic/io/asprintf.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/generic/io/asprintf.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -39,5 +39,5 @@
 #include <stddef.h>
 #include <str.h>
-#include <io/printf_core.h>
+#include <printf_core.h>
 
 static int asprintf_str_write(const char *str, size_t count, void *unused)
Index: pace/lib/c/generic/io/chargrid.c
===================================================================
--- uspace/lib/c/generic/io/chargrid.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,362 +1,0 @@
-/*
- * Copyright (c) 2006 Josef Cejka
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#include <io/style.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <as.h>
-#include <io/chargrid.h>
-
-/** Create a chargrid.
- *
- * @param[in] cols  Number of columns.
- * @param[in] rows  Number of rows.
- * @param[in] flags Chargrid flags.
- *
- * @return New chargrid.
- * @return NULL on failure.
- *
- */
-chargrid_t *chargrid_create(sysarg_t cols, sysarg_t rows,
-    chargrid_flag_t flags)
-{
-	size_t size =
-	    sizeof(chargrid_t) + cols * rows * sizeof(charfield_t);
-	chargrid_t *scrbuf;
-
-	if ((flags & CHARGRID_FLAG_SHARED) == CHARGRID_FLAG_SHARED) {
-		scrbuf = (chargrid_t *) as_area_create(AS_AREA_ANY, size,
-		    AS_AREA_READ | AS_AREA_WRITE | AS_AREA_CACHEABLE,
-		    AS_AREA_UNPAGED);
-		if (scrbuf == AS_MAP_FAILED)
-			return NULL;
-	} else {
-		scrbuf = (chargrid_t *) malloc(size);
-		if (scrbuf == NULL)
-			return NULL;
-	}
-
-	scrbuf->size = size;
-	scrbuf->flags = flags;
-	scrbuf->cols = cols;
-	scrbuf->rows = rows;
-	scrbuf->cursor_visible = false;
-
-	scrbuf->attrs.type = CHAR_ATTR_STYLE;
-	scrbuf->attrs.val.style = STYLE_NORMAL;
-
-	scrbuf->top_row = 0;
-	chargrid_clear(scrbuf);
-
-	return scrbuf;
-}
-
-void chargrid_destroy(chargrid_t *srcbuf)
-{
-	// TODO
-}
-
-bool chargrid_cursor_at(chargrid_t *scrbuf, sysarg_t col, sysarg_t row)
-{
-	return ((scrbuf->cursor_visible) && (scrbuf->col == col) &&
-	    (scrbuf->row == row));
-}
-
-sysarg_t chargrid_get_top_row(chargrid_t *scrbuf)
-{
-	return scrbuf->top_row;
-}
-
-static sysarg_t chargrid_update_rows(chargrid_t *scrbuf)
-{
-	if (scrbuf->row == scrbuf->rows) {
-		scrbuf->row = scrbuf->rows - 1;
-		scrbuf->top_row = (scrbuf->top_row + 1) % scrbuf->rows;
-		chargrid_clear_row(scrbuf, scrbuf->row);
-
-		return scrbuf->rows;
-	}
-
-	return 2;
-}
-
-static sysarg_t chargrid_update_cols(chargrid_t *scrbuf)
-{
-	/* Column overflow */
-	if (scrbuf->col == scrbuf->cols) {
-		scrbuf->col = 0;
-		scrbuf->row++;
-		return chargrid_update_rows(scrbuf);
-	}
-
-	return 1;
-}
-
-/** Store one character to chargrid.
- *
- * Its position is determined by scrbuf->col
- * and scrbuf->row.
- *
- * @param scrbuf Chargrid.
- * @param ch     Character to store.
- * @param update Update coordinates.
- *
- * @return Number of rows which have been affected. In usual
- *         situations this is 1. If the current position was
- *         updated to a new row, this value is 2.
- *
- */
-sysarg_t chargrid_putuchar(chargrid_t *scrbuf, char32_t ch, bool update)
-{
-	assert(scrbuf->col < scrbuf->cols);
-	assert(scrbuf->row < scrbuf->rows);
-
-	charfield_t *field =
-	    chargrid_charfield_at(scrbuf, scrbuf->col, scrbuf->row);
-
-	field->ch = ch;
-	field->attrs = scrbuf->attrs;
-	field->flags |= CHAR_FLAG_DIRTY;
-
-	if (update) {
-		scrbuf->col++;
-		return chargrid_update_cols(scrbuf);
-	}
-
-	return 1;
-}
-
-/** Jump to a new row in chargrid.
- *
- * @param scrbuf Chargrid.
- *
- * @return Number of rows which have been affected. In usual
- *         situations this is 2 (the original row and the new
- *         row).
- *
- */
-sysarg_t chargrid_newline(chargrid_t *scrbuf)
-{
-	assert(scrbuf->col < scrbuf->cols);
-	assert(scrbuf->row < scrbuf->rows);
-
-	scrbuf->col = 0;
-	scrbuf->row++;
-
-	return chargrid_update_rows(scrbuf);
-}
-
-/** Jump to a new row in chargrid.
- *
- * @param scrbuf   Chargrid.
- * @param tab_size Tab size.
- *
- * @return Number of rows which have been affected. In usual
- *         situations this is 1. If the current position was
- *         updated to a new row, this value is 2.
- *
- */
-sysarg_t chargrid_tabstop(chargrid_t *scrbuf, sysarg_t tab_size)
-{
-	assert(scrbuf->col < scrbuf->cols);
-	assert(scrbuf->row < scrbuf->rows);
-
-	sysarg_t spaces = tab_size - scrbuf->cols % tab_size;
-	sysarg_t flush = 1;
-
-	for (sysarg_t i = 0; i < spaces; i++)
-		flush += chargrid_putuchar(scrbuf, ' ', true) - 1;
-
-	return flush;
-}
-
-/** Jump to the previous character in chargrid.
- *
- * Currently no scrollback is supported.
- *
- * @param scrbuf Chargrid.
- *
- * @return Number of rows which have been affected. In usual
- *         situations this is 1. If the current position was
- *         updated to the previous row, this value is 2.
- * @return 0 if no backspace is possible.
- *
- */
-sysarg_t chargrid_backspace(chargrid_t *scrbuf)
-{
-	assert(scrbuf->col < scrbuf->cols);
-	assert(scrbuf->row < scrbuf->rows);
-
-	if ((scrbuf->col == 0) && (scrbuf->row == 0))
-		return 0;
-
-	if (scrbuf->col == 0) {
-		scrbuf->col = scrbuf->cols - 1;
-		scrbuf->row--;
-
-		chargrid_putuchar(scrbuf, ' ', false);
-		return 2;
-	}
-
-	scrbuf->col--;
-	chargrid_putuchar(scrbuf, ' ', false);
-	return 1;
-}
-
-/** Clear the chargrid.
- *
- * @param scrbuf Chargrid.
- *
- */
-void chargrid_clear(chargrid_t *scrbuf)
-{
-	for (size_t pos = 0; pos < (scrbuf->cols * scrbuf->rows); pos++) {
-		scrbuf->data[pos].ch = 0;
-		scrbuf->data[pos].attrs = scrbuf->attrs;
-		scrbuf->data[pos].flags = CHAR_FLAG_DIRTY;
-	}
-
-	scrbuf->col = 0;
-	scrbuf->row = 0;
-}
-
-/** Update current chargrid coordinates
- *
- * @param scrbuf Chargrid.
- * @param col    New column.
- * @param row    New row.
- *
- */
-void chargrid_set_cursor(chargrid_t *scrbuf, sysarg_t col, sysarg_t row)
-{
-	if (col >= scrbuf->cols || row >= scrbuf->rows)
-		return;
-
-	scrbuf->col = col;
-	scrbuf->row = row;
-}
-
-void chargrid_set_cursor_visibility(chargrid_t *scrbuf, bool visible)
-{
-	scrbuf->cursor_visible = visible;
-}
-
-/** Get current chargrid coordinates
- *
- * @param scrbuf Chargrid.
- * @param col    Column.
- * @param row    Row.
- *
- */
-void chargrid_get_cursor(chargrid_t *scrbuf, sysarg_t *col,
-    sysarg_t *row)
-{
-	assert(col);
-	assert(row);
-
-	*col = scrbuf->col;
-	*row = scrbuf->row;
-}
-
-bool chargrid_get_cursor_visibility(chargrid_t *scrbuf)
-{
-	return scrbuf->cursor_visible;
-}
-
-/** Clear one buffer row.
- *
- * @param scrbuf Chargrid.
- * @param row    Row to clear.
- *
- */
-void chargrid_clear_row(chargrid_t *scrbuf, sysarg_t row)
-{
-	for (sysarg_t col = 0; col < scrbuf->cols; col++) {
-		charfield_t *field =
-		    chargrid_charfield_at(scrbuf, col, row);
-
-		field->ch = 0;
-		field->attrs = scrbuf->attrs;
-		field->flags |= CHAR_FLAG_DIRTY;
-	}
-}
-
-/** Set chargrid style.
- *
- * @param scrbuf Chargrid.
- * @param style  Style.
- *
- */
-void chargrid_set_style(chargrid_t *scrbuf, console_style_t style)
-{
-	scrbuf->attrs.type = CHAR_ATTR_STYLE;
-	scrbuf->attrs.val.style = style;
-}
-
-/** Set chargrid color.
- *
- * @param scrbuf  Chargrid.
- * @param bgcolor Background color.
- * @param fgcolor Foreground color.
- * @param attr    Color attribute.
- *
- */
-void chargrid_set_color(chargrid_t *scrbuf, console_color_t bgcolor,
-    console_color_t fgcolor, console_color_attr_t attr)
-{
-	scrbuf->attrs.type = CHAR_ATTR_INDEX;
-	scrbuf->attrs.val.index.bgcolor = bgcolor;
-	scrbuf->attrs.val.index.fgcolor = fgcolor;
-	scrbuf->attrs.val.index.attr = attr;
-}
-
-/** Set chargrid RGB color.
- *
- * @param scrbuf  Chargrid.
- * @param bgcolor Background color.
- * @param fgcolor Foreground color.
- *
- */
-void chargrid_set_rgb_color(chargrid_t *scrbuf, pixel_t bgcolor,
-    pixel_t fgcolor)
-{
-	scrbuf->attrs.type = CHAR_ATTR_RGB;
-	scrbuf->attrs.val.rgb.bgcolor = bgcolor;
-	scrbuf->attrs.val.rgb.fgcolor = fgcolor;
-}
-
-/** @}
- */
Index: pace/lib/c/generic/io/con_srv.c
===================================================================
--- uspace/lib/c/generic/io/con_srv.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,514 +1,0 @@
-/*
- * Copyright (c) 2021 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/**
- * @file
- * @brief Console protocol server stub
- */
-#include <as.h>
-#include <errno.h>
-#include <io/cons_event.h>
-#include <ipc/console.h>
-#include <stdlib.h>
-#include <stddef.h>
-
-#include <io/con_srv.h>
-
-static errno_t console_ev_encode(cons_event_t *event, ipc_call_t *icall)
-{
-	ipc_set_arg1(icall, event->type);
-
-	switch (event->type) {
-	case CEV_KEY:
-		ipc_set_arg2(icall, event->ev.key.type);
-		ipc_set_arg3(icall, event->ev.key.key);
-		ipc_set_arg4(icall, event->ev.key.mods);
-		ipc_set_arg5(icall, event->ev.key.c);
-		break;
-	case CEV_POS:
-		ipc_set_arg2(icall, (event->ev.pos.pos_id << 16) | (event->ev.pos.type & 0xffff));
-		ipc_set_arg3(icall, event->ev.pos.btn_num);
-		ipc_set_arg4(icall, event->ev.pos.hpos);
-		ipc_set_arg5(icall, event->ev.pos.vpos);
-		break;
-	default:
-		return EIO;
-	}
-
-	return EOK;
-}
-
-static void con_read_srv(con_srv_t *srv, ipc_call_t *icall)
-{
-	void *buf;
-	size_t size;
-	errno_t rc;
-
-	ipc_call_t call;
-	if (!async_data_read_receive(&call, &size)) {
-		async_answer_0(icall, EINVAL);
-		return;
-	}
-
-	buf = malloc(size);
-	if (buf == NULL) {
-		async_answer_0(&call, ENOMEM);
-		async_answer_0(icall, ENOMEM);
-		return;
-	}
-
-	if (srv->srvs->ops->read == NULL) {
-		async_answer_0(&call, ENOTSUP);
-		async_answer_0(icall, ENOTSUP);
-		free(buf);
-		return;
-	}
-
-	size_t nread;
-	rc = srv->srvs->ops->read(srv, buf, size, &nread);
-	if (rc != EOK) {
-		async_answer_0(&call, rc);
-		async_answer_0(icall, rc);
-		free(buf);
-		return;
-	}
-
-	async_data_read_finalize(&call, buf, nread);
-	free(buf);
-
-	async_answer_1(icall, EOK, nread);
-}
-
-static void con_write_srv(con_srv_t *srv, ipc_call_t *icall)
-{
-	void *data;
-	size_t size;
-	errno_t rc;
-
-	rc = async_data_write_accept(&data, false, 0, 0, 0, &size);
-	if (rc != EOK) {
-		async_answer_0(icall, rc);
-		return;
-	}
-
-	if (srv->srvs->ops->write == NULL) {
-		async_answer_0(icall, ENOTSUP);
-		return;
-	}
-
-	size_t nwritten = 0;
-	rc = srv->srvs->ops->write(srv, data, size, &nwritten);
-	free(data);
-
-	async_answer_1(icall, rc, nwritten);
-}
-
-static void con_sync_srv(con_srv_t *srv, ipc_call_t *icall)
-{
-	if (srv->srvs->ops->sync == NULL) {
-		async_answer_0(icall, ENOTSUP);
-		return;
-	}
-
-	srv->srvs->ops->sync(srv);
-	async_answer_0(icall, EOK);
-}
-
-static void con_clear_srv(con_srv_t *srv, ipc_call_t *icall)
-{
-	if (srv->srvs->ops->clear == NULL) {
-		async_answer_0(icall, ENOTSUP);
-		return;
-	}
-
-	srv->srvs->ops->clear(srv);
-	async_answer_0(icall, EOK);
-}
-
-static void con_set_pos_srv(con_srv_t *srv, ipc_call_t *icall)
-{
-	sysarg_t col;
-	sysarg_t row;
-
-	col = ipc_get_arg1(icall);
-	row = ipc_get_arg2(icall);
-
-	if (srv->srvs->ops->set_pos == NULL) {
-		async_answer_0(icall, ENOTSUP);
-		return;
-	}
-
-	srv->srvs->ops->set_pos(srv, col, row);
-	async_answer_0(icall, EOK);
-}
-
-static void con_get_pos_srv(con_srv_t *srv, ipc_call_t *icall)
-{
-	errno_t rc;
-	sysarg_t col;
-	sysarg_t row;
-
-	if (srv->srvs->ops->get_pos == NULL) {
-		async_answer_0(icall, ENOTSUP);
-		return;
-	}
-
-	rc = srv->srvs->ops->get_pos(srv, &col, &row);
-	async_answer_2(icall, rc, col, row);
-}
-
-static void con_get_size_srv(con_srv_t *srv, ipc_call_t *icall)
-{
-	errno_t rc;
-	sysarg_t cols;
-	sysarg_t rows;
-
-	if (srv->srvs->ops->get_size == NULL) {
-		async_answer_0(icall, ENOTSUP);
-		return;
-	}
-
-	rc = srv->srvs->ops->get_size(srv, &cols, &rows);
-	async_answer_2(icall, rc, cols, rows);
-}
-
-static void con_get_color_cap_srv(con_srv_t *srv, ipc_call_t *icall)
-{
-	errno_t rc;
-	console_caps_t ccap;
-
-	if (srv->srvs->ops->get_color_cap == NULL) {
-		async_answer_0(icall, ENOTSUP);
-		return;
-	}
-
-	rc = srv->srvs->ops->get_color_cap(srv, &ccap);
-	async_answer_1(icall, rc, (sysarg_t)ccap);
-}
-
-static void con_set_style_srv(con_srv_t *srv, ipc_call_t *icall)
-{
-	console_style_t style;
-
-	style = ipc_get_arg1(icall);
-
-	if (srv->srvs->ops->set_style == NULL) {
-		async_answer_0(icall, ENOTSUP);
-		return;
-	}
-
-	srv->srvs->ops->set_style(srv, style);
-	async_answer_0(icall, EOK);
-}
-
-static void con_set_color_srv(con_srv_t *srv, ipc_call_t *icall)
-{
-	console_color_t bgcolor;
-	console_color_t fgcolor;
-	console_color_attr_t flags;
-
-	bgcolor = ipc_get_arg1(icall);
-	fgcolor = ipc_get_arg2(icall);
-	flags = ipc_get_arg3(icall);
-
-	if (srv->srvs->ops->set_color == NULL) {
-		async_answer_0(icall, ENOTSUP);
-		return;
-	}
-
-	srv->srvs->ops->set_color(srv, bgcolor, fgcolor, flags);
-	async_answer_0(icall, EOK);
-}
-
-static void con_set_rgb_color_srv(con_srv_t *srv, ipc_call_t *icall)
-{
-	pixel_t bgcolor;
-	pixel_t fgcolor;
-
-	bgcolor = ipc_get_arg1(icall);
-	fgcolor = ipc_get_arg2(icall);
-
-	if (srv->srvs->ops->set_rgb_color == NULL) {
-		async_answer_0(icall, ENOTSUP);
-		return;
-	}
-
-	srv->srvs->ops->set_rgb_color(srv, bgcolor, fgcolor);
-	async_answer_0(icall, EOK);
-}
-
-static void con_set_cursor_visibility_srv(con_srv_t *srv, ipc_call_t *icall)
-{
-	bool show;
-
-	show = ipc_get_arg1(icall);
-
-	if (srv->srvs->ops->set_cursor_visibility == NULL) {
-		async_answer_0(icall, ENOTSUP);
-		return;
-	}
-
-	srv->srvs->ops->set_cursor_visibility(srv, show);
-	async_answer_0(icall, EOK);
-}
-
-static void con_get_event_srv(con_srv_t *srv, ipc_call_t *icall)
-{
-	errno_t rc;
-	cons_event_t event;
-	ipc_call_t result;
-
-	if (srv->srvs->ops->get_event == NULL) {
-		async_answer_0(icall, ENOTSUP);
-		return;
-	}
-
-	rc = srv->srvs->ops->get_event(srv, &event);
-	if (rc != EOK) {
-		async_answer_0(icall, rc);
-		return;
-	}
-
-	rc = console_ev_encode(&event, &result);
-	if (rc != EOK) {
-		async_answer_0(icall, rc);
-		return;
-	}
-
-	async_answer_5(icall, rc, ipc_get_arg1(&result), ipc_get_arg2(&result),
-	    ipc_get_arg3(&result), ipc_get_arg4(&result), ipc_get_arg5(&result));
-}
-
-/** Create shared buffer for efficient rendering */
-static void con_map_srv(con_srv_t *srv, ipc_call_t *icall)
-{
-	errno_t rc;
-	charfield_t *buf;
-	sysarg_t cols, rows;
-	ipc_call_t call;
-	size_t size;
-
-	if (srv->srvs->ops->map == NULL || srv->srvs->ops->unmap == NULL) {
-		async_answer_0(icall, ENOTSUP);
-		return;
-	}
-
-	cols = ipc_get_arg1(icall);
-	rows = ipc_get_arg2(icall);
-
-	if (!async_share_in_receive(&call, &size)) {
-		async_answer_0(icall, EINVAL);
-		return;
-	}
-
-	/* Check size */
-	if (size != PAGES2SIZE(SIZE2PAGES(cols * rows * sizeof(charfield_t)))) {
-		async_answer_0(&call, EINVAL);
-		async_answer_0(icall, EINVAL);
-		return;
-	}
-
-	rc = srv->srvs->ops->map(srv, cols, rows, &buf);
-	if (rc != EOK) {
-		async_answer_0(&call, rc);
-		async_answer_0(icall, rc);
-		return;
-	}
-
-	rc = async_share_in_finalize(&call, buf, AS_AREA_READ |
-	    AS_AREA_WRITE | AS_AREA_CACHEABLE);
-	if (rc != EOK) {
-		srv->srvs->ops->unmap(srv);
-		async_answer_0(icall, EIO);
-		return;
-	}
-
-	async_answer_0(icall, EOK);
-}
-
-/** Delete shared buffer */
-static void con_unmap_srv(con_srv_t *srv, ipc_call_t *icall)
-{
-	if (srv->srvs->ops->unmap == NULL) {
-		async_answer_0(icall, ENOTSUP);
-		return;
-	}
-
-	srv->srvs->ops->unmap(srv);
-	async_answer_0(icall, EOK);
-}
-
-/** Update console area from shared buffer */
-static void con_update_srv(con_srv_t *srv, ipc_call_t *icall)
-{
-	sysarg_t c0, r0, c1, r1;
-
-	c0 = ipc_get_arg1(icall);
-	r0 = ipc_get_arg2(icall);
-	c1 = ipc_get_arg3(icall);
-	r1 = ipc_get_arg4(icall);
-
-	if (srv->srvs->ops->update == NULL) {
-		async_answer_0(icall, ENOTSUP);
-		return;
-	}
-
-	srv->srvs->ops->update(srv, c0, r0, c1, r1);
-	async_answer_0(icall, EOK);
-}
-
-static con_srv_t *con_srv_create(con_srvs_t *srvs)
-{
-	con_srv_t *srv;
-
-	srv = calloc(1, sizeof(*srv));
-	if (srv == NULL)
-		return NULL;
-
-	srv->srvs = srvs;
-	return srv;
-}
-
-void con_srvs_init(con_srvs_t *srvs)
-{
-	srvs->ops = NULL;
-	srvs->sarg = NULL;
-	srvs->abort_timeout = 0;
-	srvs->aborted = false;
-}
-
-errno_t con_conn(ipc_call_t *icall, con_srvs_t *srvs)
-{
-	con_srv_t *srv;
-	errno_t rc;
-
-	/* Accept the connection */
-	async_accept_0(icall);
-
-	srv = con_srv_create(srvs);
-	if (srv == NULL)
-		return ENOMEM;
-
-	srv->client_sess = NULL;
-
-	rc = srvs->ops->open(srvs, srv);
-	if (rc != EOK)
-		return rc;
-
-	while (true) {
-		ipc_call_t call;
-		bool received = false;
-
-		while (!received) {
-			/* XXX Need to be able to abort immediately */
-			received = async_get_call_timeout(&call,
-			    srvs->abort_timeout);
-
-			if (srv->srvs->aborted) {
-				if (received)
-					async_answer_0(&call, EINTR);
-				break;
-			}
-		}
-
-		if (!received)
-			break;
-
-		sysarg_t method = ipc_get_imethod(&call);
-
-		if (!method) {
-			/* The other side has hung up */
-			async_answer_0(&call, EOK);
-			break;
-		}
-
-		switch (method) {
-		case VFS_OUT_READ:
-			con_read_srv(srv, &call);
-			break;
-		case VFS_OUT_WRITE:
-			con_write_srv(srv, &call);
-			break;
-		case VFS_OUT_SYNC:
-			con_sync_srv(srv, &call);
-			break;
-		case CONSOLE_CLEAR:
-			con_clear_srv(srv, &call);
-			break;
-		case CONSOLE_SET_POS:
-			con_set_pos_srv(srv, &call);
-			break;
-		case CONSOLE_GET_POS:
-			con_get_pos_srv(srv, &call);
-			break;
-		case CONSOLE_GET_SIZE:
-			con_get_size_srv(srv, &call);
-			break;
-		case CONSOLE_GET_COLOR_CAP:
-			con_get_color_cap_srv(srv, &call);
-			break;
-		case CONSOLE_SET_STYLE:
-			con_set_style_srv(srv, &call);
-			break;
-		case CONSOLE_SET_COLOR:
-			con_set_color_srv(srv, &call);
-			break;
-		case CONSOLE_SET_RGB_COLOR:
-			con_set_rgb_color_srv(srv, &call);
-			break;
-		case CONSOLE_SET_CURSOR_VISIBILITY:
-			con_set_cursor_visibility_srv(srv, &call);
-			break;
-		case CONSOLE_GET_EVENT:
-			con_get_event_srv(srv, &call);
-			break;
-		case CONSOLE_MAP:
-			con_map_srv(srv, &call);
-			break;
-		case CONSOLE_UNMAP:
-			con_unmap_srv(srv, &call);
-			break;
-		case CONSOLE_UPDATE:
-			con_update_srv(srv, &call);
-			break;
-		default:
-			async_answer_0(&call, ENOTSUP);
-		}
-	}
-
-	rc = srvs->ops->close(srv);
-	free(srv);
-
-	return rc;
-}
-
-/** @}
- */
Index: pace/lib/c/generic/io/console.c
===================================================================
--- uspace/lib/c/generic/io/console.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,351 +1,0 @@
-/*
- * Copyright (c) 2021 Jiri Svoboda
- * Copyright (c) 2006 Josef Cejka
- * Copyright (c) 2006 Jakub Vana
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#include <as.h>
-#include <libc.h>
-#include <async.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <vfs/vfs_sess.h>
-#include <io/console.h>
-#include <ipc/console.h>
-
-console_ctrl_t *console_init(FILE *ifile, FILE *ofile)
-{
-	console_ctrl_t *ctrl = malloc(sizeof(console_ctrl_t));
-	if (!ctrl)
-		return NULL;
-
-	ctrl->input_sess = vfs_fsession(ifile, INTERFACE_CONSOLE);
-	if (!ctrl->input_sess) {
-		free(ctrl);
-		return NULL;
-	}
-
-	ctrl->output_sess = vfs_fsession(ofile, INTERFACE_CONSOLE);
-	if (!ctrl->output_sess) {
-		free(ctrl);
-		return NULL;
-	}
-
-	ctrl->input = ifile;
-	ctrl->output = ofile;
-	ctrl->input_aid = 0;
-
-	return ctrl;
-}
-
-void console_done(console_ctrl_t *ctrl)
-{
-	free(ctrl);
-}
-
-bool console_kcon(void)
-{
-	return __SYSCALL0(SYS_DEBUG_CONSOLE);
-}
-
-void console_flush(console_ctrl_t *ctrl)
-{
-	fflush(ctrl->output);
-}
-
-void console_clear(console_ctrl_t *ctrl)
-{
-	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
-	async_req_0_0(exch, CONSOLE_CLEAR);
-	async_exchange_end(exch);
-}
-
-errno_t console_get_size(console_ctrl_t *ctrl, sysarg_t *cols, sysarg_t *rows)
-{
-	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
-	errno_t rc = async_req_0_2(exch, CONSOLE_GET_SIZE, cols, rows);
-	async_exchange_end(exch);
-
-	return rc;
-}
-
-void console_set_style(console_ctrl_t *ctrl, uint8_t style)
-{
-	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
-	async_req_1_0(exch, CONSOLE_SET_STYLE, style);
-	async_exchange_end(exch);
-}
-
-void console_set_color(console_ctrl_t *ctrl, uint8_t bgcolor, uint8_t fgcolor,
-    uint8_t flags)
-{
-	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
-	async_req_3_0(exch, CONSOLE_SET_COLOR, bgcolor, fgcolor, flags);
-	async_exchange_end(exch);
-}
-
-void console_set_rgb_color(console_ctrl_t *ctrl, uint32_t bgcolor,
-    uint32_t fgcolor)
-{
-	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
-	async_req_2_0(exch, CONSOLE_SET_RGB_COLOR, bgcolor, fgcolor);
-	async_exchange_end(exch);
-}
-
-void console_cursor_visibility(console_ctrl_t *ctrl, bool show)
-{
-	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
-	async_req_1_0(exch, CONSOLE_SET_CURSOR_VISIBILITY, (show != false));
-	async_exchange_end(exch);
-}
-
-errno_t console_get_color_cap(console_ctrl_t *ctrl, sysarg_t *ccap)
-{
-	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
-	errno_t rc = async_req_0_1(exch, CONSOLE_GET_COLOR_CAP, ccap);
-	async_exchange_end(exch);
-
-	return rc;
-}
-
-errno_t console_get_pos(console_ctrl_t *ctrl, sysarg_t *col, sysarg_t *row)
-{
-	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
-	errno_t rc = async_req_0_2(exch, CONSOLE_GET_POS, col, row);
-	async_exchange_end(exch);
-
-	return rc;
-}
-
-void console_set_pos(console_ctrl_t *ctrl, sysarg_t col, sysarg_t row)
-{
-	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
-	async_req_2_0(exch, CONSOLE_SET_POS, col, row);
-	async_exchange_end(exch);
-}
-
-static errno_t console_ev_decode(ipc_call_t *call, cons_event_t *event)
-{
-	event->type = ipc_get_arg1(call);
-
-	switch (event->type) {
-	case CEV_KEY:
-		event->ev.key.type = ipc_get_arg2(call);
-		event->ev.key.key = ipc_get_arg3(call);
-		event->ev.key.mods = ipc_get_arg4(call);
-		event->ev.key.c = ipc_get_arg5(call);
-		break;
-	case CEV_POS:
-		event->ev.pos.pos_id = ipc_get_arg2(call) >> 16;
-		event->ev.pos.type = ipc_get_arg2(call) & 0xffff;
-		event->ev.pos.btn_num = ipc_get_arg3(call);
-		event->ev.pos.hpos = ipc_get_arg4(call);
-		event->ev.pos.vpos = ipc_get_arg5(call);
-		break;
-	default:
-		return EIO;
-	}
-
-	return EOK;
-}
-
-/** Get console event.
- *
- * @param ctrl Console
- * @param event Place to store event
- * @return EOK on success, EIO on failure
- */
-errno_t console_get_event(console_ctrl_t *ctrl, cons_event_t *event)
-{
-	if (ctrl->input_aid == 0) {
-		ipc_call_t result;
-
-		async_exch_t *exch = async_exchange_begin(ctrl->input_sess);
-		aid_t aid = async_send_0(exch, CONSOLE_GET_EVENT, &result);
-		async_exchange_end(exch);
-
-		errno_t rc;
-		async_wait_for(aid, &rc);
-
-		if (rc != EOK)
-			return EIO;
-
-		rc = console_ev_decode(&result, event);
-		if (rc != EOK)
-			return EIO;
-	} else {
-		errno_t retval;
-		async_wait_for(ctrl->input_aid, &retval);
-
-		ctrl->input_aid = 0;
-
-		if (retval != EOK)
-			return EIO;
-
-		errno_t rc = console_ev_decode(&ctrl->input_call, event);
-		if (rc != EOK)
-			return EIO;
-	}
-
-	return EOK;
-}
-
-/** Get console event with timeout.
- *
- * @param ctrl Console
- * @param event Place to store event
- * @param timeout Pointer to timeout. This will be updated to reflect
- *                the remaining time in case of timeout.
- * @return EOK on success (event received), ETIMEOUT on time out,
- *         EIO on I/O error (e.g. lost console connection), ENOMEM
- *         if out of memory
- */
-errno_t console_get_event_timeout(console_ctrl_t *ctrl, cons_event_t *event,
-    usec_t *timeout)
-{
-	struct timespec t0;
-	getuptime(&t0);
-
-	if (ctrl->input_aid == 0) {
-		async_exch_t *exch = async_exchange_begin(ctrl->input_sess);
-		ctrl->input_aid = async_send_0(exch, CONSOLE_GET_EVENT,
-		    &ctrl->input_call);
-		async_exchange_end(exch);
-	}
-
-	errno_t retval;
-	errno_t rc = async_wait_timeout(ctrl->input_aid, &retval, *timeout);
-	if (rc != EOK) {
-		if (rc == ENOMEM)
-			return ENOMEM;
-		*timeout = 0;
-		return ETIMEOUT;
-	}
-
-	ctrl->input_aid = 0;
-
-	if (retval != EOK)
-		return EIO;
-
-	rc = console_ev_decode(&ctrl->input_call, event);
-	if (rc != EOK)
-		return EIO;
-
-	/* Update timeout */
-	struct timespec t1;
-	getuptime(&t1);
-	*timeout -= NSEC2USEC(ts_sub_diff(&t1, &t0));
-
-	return EOK;
-}
-
-/** Create a shared buffer for fast rendering to the console.
- *
- * @param ctrl Console
- * @param cols Number of columns
- * @param rows Number of rows
- * @param rbuf Place to store pointer to the shared buffer
- * @return EOK on success or an error code
- */
-errno_t console_map(console_ctrl_t *ctrl, sysarg_t cols, sysarg_t rows,
-    charfield_t **rbuf)
-{
-	async_exch_t *exch = NULL;
-	void *buf;
-	aid_t req;
-	ipc_call_t answer;
-	size_t asize;
-	errno_t rc;
-
-	exch = async_exchange_begin(ctrl->output_sess);
-	req = async_send_2(exch, CONSOLE_MAP, cols, rows, &answer);
-
-	asize = PAGES2SIZE(SIZE2PAGES(cols * rows * sizeof(charfield_t)));
-
-	rc = async_share_in_start_0_0(exch, asize, &buf);
-	if (rc != EOK) {
-		async_forget(req);
-		goto error;
-	}
-
-	async_exchange_end(exch);
-	exch = NULL;
-
-	async_wait_for(req, &rc);
-	if (rc != EOK)
-		goto error;
-
-	*rbuf = (charfield_t *)buf;
-	return EOK;
-error:
-	if (exch != NULL)
-		async_exchange_end(exch);
-	return rc;
-}
-
-/** Unmap console shared buffer.
- *
- * @param ctrl Console
- * @param buf Buffer
- */
-void console_unmap(console_ctrl_t *ctrl, charfield_t *buf)
-{
-	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
-	(void) async_req_0_0(exch, CONSOLE_UNMAP);
-	async_exchange_end(exch);
-
-	as_area_destroy(buf);
-}
-
-/** Update console rectangle from shared buffer.
- *
- * @param ctrl Console
- * @param c0 Column coordinate of top-left corner (inclusive)
- * @param r0 Row coordinate of top-left corner (inclusive)
- * @param c1 Column coordinate of bottom-right corner (exclusive)
- * @param r1 Row coordinate of bottom-right corner (exclusive)
- *
- * @return EOK on sucess or an error code
- */
-errno_t console_update(console_ctrl_t *ctrl, sysarg_t c0, sysarg_t r0,
-    sysarg_t c1, sysarg_t r1)
-{
-	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
-	errno_t rc = async_req_4_0(exch, CONSOLE_UPDATE, c0, r0, c1, r1);
-	async_exchange_end(exch);
-
-	return rc;
-}
-
-/** @}
- */
Index: pace/lib/c/generic/io/input.c
===================================================================
--- uspace/lib/c/generic/io/input.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,205 +1,0 @@
-/*
- * Copyright (c) 2012 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/**
- * @file
- * @brief Input protocol client stub
- */
-
-#include <async.h>
-#include <assert.h>
-#include <errno.h>
-#include <io/kbd_event.h>
-#include <io/input.h>
-#include <ipc/input.h>
-#include <stdlib.h>
-
-static void input_cb_conn(ipc_call_t *icall, void *arg);
-
-errno_t input_open(async_sess_t *sess, input_ev_ops_t *ev_ops,
-    void *arg, input_t **rinput)
-{
-	input_t *input = calloc(1, sizeof(input_t));
-	if (input == NULL)
-		return ENOMEM;
-
-	input->sess = sess;
-	input->ev_ops = ev_ops;
-	input->user = arg;
-
-	async_exch_t *exch = async_exchange_begin(sess);
-
-	port_id_t port;
-	errno_t rc = async_create_callback_port(exch, INTERFACE_INPUT_CB, 0, 0,
-	    input_cb_conn, input, &port);
-
-	async_exchange_end(exch);
-
-	if (rc != EOK)
-		goto error;
-
-	*rinput = input;
-	return EOK;
-
-error:
-	if (input != NULL)
-		free(input);
-
-	return rc;
-}
-
-void input_close(input_t *input)
-{
-	/* XXX Synchronize with input_cb_conn */
-	free(input);
-}
-
-errno_t input_activate(input_t *input)
-{
-	async_exch_t *exch = async_exchange_begin(input->sess);
-	errno_t rc = async_req_0_0(exch, INPUT_ACTIVATE);
-	async_exchange_end(exch);
-
-	return rc;
-}
-
-static void input_ev_active(input_t *input, ipc_call_t *call)
-{
-	errno_t rc = input->ev_ops->active(input);
-	async_answer_0(call, rc);
-}
-
-static void input_ev_deactive(input_t *input, ipc_call_t *call)
-{
-	errno_t rc = input->ev_ops->deactive(input);
-	async_answer_0(call, rc);
-}
-
-static void input_ev_key(input_t *input, ipc_call_t *call)
-{
-	kbd_event_type_t type;
-	keycode_t key;
-	keymod_t mods;
-	char32_t c;
-	errno_t rc;
-
-	type = ipc_get_arg1(call);
-	key = ipc_get_arg2(call);
-	mods = ipc_get_arg3(call);
-	c = ipc_get_arg4(call);
-
-	rc = input->ev_ops->key(input, type, key, mods, c);
-	async_answer_0(call, rc);
-}
-
-static void input_ev_move(input_t *input, ipc_call_t *call)
-{
-	int dx;
-	int dy;
-	errno_t rc;
-
-	dx = ipc_get_arg1(call);
-	dy = ipc_get_arg2(call);
-
-	rc = input->ev_ops->move(input, dx, dy);
-	async_answer_0(call, rc);
-}
-
-static void input_ev_abs_move(input_t *input, ipc_call_t *call)
-{
-	unsigned x;
-	unsigned y;
-	unsigned max_x;
-	unsigned max_y;
-	errno_t rc;
-
-	x = ipc_get_arg1(call);
-	y = ipc_get_arg2(call);
-	max_x = ipc_get_arg3(call);
-	max_y = ipc_get_arg4(call);
-
-	rc = input->ev_ops->abs_move(input, x, y, max_x, max_y);
-	async_answer_0(call, rc);
-}
-
-static void input_ev_button(input_t *input, ipc_call_t *call)
-{
-	int bnum;
-	int press;
-	errno_t rc;
-
-	bnum = ipc_get_arg1(call);
-	press = ipc_get_arg2(call);
-
-	rc = input->ev_ops->button(input, bnum, press);
-	async_answer_0(call, rc);
-}
-
-static void input_cb_conn(ipc_call_t *icall, void *arg)
-{
-	input_t *input = (input_t *) arg;
-
-	while (true) {
-		ipc_call_t call;
-		async_get_call(&call);
-
-		if (!ipc_get_imethod(&call)) {
-			async_answer_0(&call, EOK);
-			return;
-		}
-
-		switch (ipc_get_imethod(&call)) {
-		case INPUT_EVENT_ACTIVE:
-			input_ev_active(input, &call);
-			break;
-		case INPUT_EVENT_DEACTIVE:
-			input_ev_deactive(input, &call);
-			break;
-		case INPUT_EVENT_KEY:
-			input_ev_key(input, &call);
-			break;
-		case INPUT_EVENT_MOVE:
-			input_ev_move(input, &call);
-			break;
-		case INPUT_EVENT_ABS_MOVE:
-			input_ev_abs_move(input, &call);
-			break;
-		case INPUT_EVENT_BUTTON:
-			input_ev_button(input, &call);
-			break;
-		default:
-			async_answer_0(&call, ENOTSUP);
-		}
-	}
-}
-
-/** @}
- */
Index: uspace/lib/c/generic/io/kio.c
===================================================================
--- uspace/lib/c/generic/io/kio.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/generic/io/kio.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -42,5 +42,5 @@
 #include <abi/kio.h>
 #include <io/kio.h>
-#include <io/printf_core.h>
+#include <printf_core.h>
 #include <macros.h>
 #include <libarch/config.h>
Index: pace/lib/c/generic/io/output.c
===================================================================
--- uspace/lib/c/generic/io/output.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,144 +1,0 @@
-/*
- * Copyright (c) 2011 Martin Decky
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#include <async.h>
-#include <errno.h>
-#include <as.h>
-#include <ipc/output.h>
-#include <io/concaps.h>
-#include <io/output.h>
-
-errno_t output_yield(async_sess_t *sess)
-{
-	async_exch_t *exch = async_exchange_begin(sess);
-	errno_t ret = async_req_0_0(exch, OUTPUT_YIELD);
-	async_exchange_end(exch);
-
-	return ret;
-}
-
-errno_t output_claim(async_sess_t *sess)
-{
-	async_exch_t *exch = async_exchange_begin(sess);
-	errno_t ret = async_req_0_0(exch, OUTPUT_CLAIM);
-	async_exchange_end(exch);
-
-	return ret;
-}
-
-errno_t output_get_dimensions(async_sess_t *sess, sysarg_t *maxx, sysarg_t *maxy)
-{
-	async_exch_t *exch = async_exchange_begin(sess);
-	errno_t ret = async_req_0_2(exch, OUTPUT_GET_DIMENSIONS, maxx, maxy);
-	async_exchange_end(exch);
-
-	return ret;
-}
-
-errno_t output_get_caps(async_sess_t *sess, console_caps_t *ccaps)
-{
-	async_exch_t *exch = async_exchange_begin(sess);
-
-	sysarg_t rv;
-	errno_t ret = async_req_0_1(exch, OUTPUT_GET_CAPS, &rv);
-
-	async_exchange_end(exch);
-
-	if (ret == EOK)
-		*ccaps = (console_caps_t) rv;
-
-	return ret;
-}
-
-frontbuf_handle_t output_frontbuf_create(async_sess_t *sess,
-    chargrid_t *frontbuf)
-{
-	async_exch_t *exch = async_exchange_begin(sess);
-
-	ipc_call_t answer;
-	aid_t req = async_send_0(exch, OUTPUT_FRONTBUF_CREATE, &answer);
-	errno_t rc = async_share_out_start(exch, frontbuf, AS_AREA_READ |
-	    AS_AREA_WRITE | AS_AREA_CACHEABLE);
-
-	async_exchange_end(exch);
-
-	errno_t ret;
-	async_wait_for(req, &ret);
-
-	if ((rc != EOK) || (ret != EOK))
-		return 0;
-
-	return (frontbuf_handle_t) ipc_get_arg1(&answer);
-}
-
-errno_t output_set_style(async_sess_t *sess, console_style_t style)
-{
-	async_exch_t *exch = async_exchange_begin(sess);
-	errno_t ret = async_req_1_0(exch, OUTPUT_SET_STYLE, style);
-	async_exchange_end(exch);
-
-	return ret;
-}
-
-errno_t output_cursor_update(async_sess_t *sess, frontbuf_handle_t frontbuf)
-{
-	async_exch_t *exch = async_exchange_begin(sess);
-	errno_t ret = async_req_1_0(exch, OUTPUT_CURSOR_UPDATE, frontbuf);
-	async_exchange_end(exch);
-
-	return ret;
-}
-
-errno_t output_update(async_sess_t *sess, frontbuf_handle_t frontbuf)
-{
-	async_exch_t *exch = async_exchange_begin(sess);
-	errno_t ret = async_req_1_0(exch, OUTPUT_UPDATE, frontbuf);
-	async_exchange_end(exch);
-
-	return ret;
-}
-
-errno_t output_damage(async_sess_t *sess, frontbuf_handle_t frontbuf, sysarg_t col,
-    sysarg_t row, sysarg_t cols, sysarg_t rows)
-{
-	async_exch_t *exch = async_exchange_begin(sess);
-	errno_t ret = async_req_5_0(exch, OUTPUT_DAMAGE, frontbuf, col, row,
-	    cols, rows);
-	async_exchange_end(exch);
-
-	return ret;
-}
-
-/** @}
- */
Index: uspace/lib/c/generic/io/printf.c
===================================================================
--- uspace/lib/c/generic/io/printf.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/generic/io/printf.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -33,5 +33,5 @@
  */
 
-#include <io/printf_core.h>
+#include <printf_core.h>
 #include <stdio.h>
 
Index: pace/lib/c/generic/io/printf_core.c
===================================================================
--- uspace/lib/c/generic/io/printf_core.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,1660 +1,0 @@
-/*
- * Copyright (c) 2001-2004 Jakub Jermar
- * Copyright (c) 2006 Josef Cejka
- * Copyright (c) 2009 Martin Decky
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/**
- * @file
- * @brief Printing functions.
- */
-
-#include <stdio.h>
-#include <stddef.h>
-#include <stdlib.h>
-#include <io/printf_core.h>
-#include <ctype.h>
-#include <str.h>
-#include <double_to_str.h>
-#include <ieee_double.h>
-#include <assert.h>
-#include <macros.h>
-#include <uchar.h>
-
-/** show prefixes 0x or 0 */
-#define __PRINTF_FLAG_PREFIX       0x00000001
-
-/** show the decimal point even if no fractional digits present */
-#define __PRINTF_FLAG_DECIMALPT    0x00000001
-
-/** signed / unsigned number */
-#define __PRINTF_FLAG_SIGNED       0x00000002
-
-/** print leading zeroes */
-#define __PRINTF_FLAG_ZEROPADDED   0x00000004
-
-/** align to left */
-#define __PRINTF_FLAG_LEFTALIGNED  0x00000010
-
-/** always show + sign */
-#define __PRINTF_FLAG_SHOWPLUS     0x00000020
-
-/** print space instead of plus */
-#define __PRINTF_FLAG_SPACESIGN    0x00000040
-
-/** show big characters */
-#define __PRINTF_FLAG_BIGCHARS     0x00000080
-
-/** number has - sign */
-#define __PRINTF_FLAG_NEGATIVE     0x00000100
-
-/** don't print trailing zeros in the fractional part */
-#define __PRINTF_FLAG_NOFRACZEROS  0x00000200
-
-/**
- * Buffer big enough for 64-bit number printed in base 2, sign, prefix and 0
- * to terminate string... (last one is only for better testing end of buffer by
- * zero-filling subroutine)
- */
-#define PRINT_NUMBER_BUFFER_SIZE  (64 + 5)
-
-/** Get signed or unsigned integer argument */
-#define PRINTF_GET_INT_ARGUMENT(type, ap, flags) \
-	({ \
-		unsigned type res; \
-		\
-		if ((flags) & __PRINTF_FLAG_SIGNED) { \
-			signed type arg = va_arg((ap), signed type); \
-			\
-			if (arg < 0) { \
-				res = -arg; \
-				(flags) |= __PRINTF_FLAG_NEGATIVE; \
-			} else \
-				res = arg; \
-		} else \
-			res = va_arg((ap), unsigned type); \
-		\
-		res; \
-	})
-
-/** Enumeration of possible arguments types.
- */
-typedef enum {
-	PrintfQualifierByte = 0,
-	PrintfQualifierShort,
-	PrintfQualifierInt,
-	PrintfQualifierLong,
-	PrintfQualifierLongLong,
-	PrintfQualifierPointer,
-	PrintfQualifierSize,
-	PrintfQualifierMax
-} qualifier_t;
-
-static const char *nullstr = "(NULL)";
-static const char *digits_small = "0123456789abcdef";
-static const char *digits_big = "0123456789ABCDEF";
-static const char invalch = U_SPECIAL;
-
-/** Unformatted double number string representation. */
-typedef struct {
-	/** Buffer with len digits, no sign or leading zeros. */
-	char *str;
-	/** Number of digits in str. */
-	int len;
-	/** Decimal exponent, ie number = str * 10^dec_exp */
-	int dec_exp;
-	/** True if negative. */
-	bool neg;
-} double_str_t;
-
-/** Returns the sign character or 0 if no sign should be printed. */
-static int get_sign_char(bool negative, uint32_t flags)
-{
-	if (negative) {
-		return '-';
-	} else if (flags & __PRINTF_FLAG_SHOWPLUS) {
-		return '+';
-	} else if (flags & __PRINTF_FLAG_SPACESIGN) {
-		return ' ';
-	} else {
-		return 0;
-	}
-}
-
-/** Prints count times character ch. */
-static int print_padding(char ch, int count, printf_spec_t *ps)
-{
-	for (int i = 0; i < count; ++i) {
-		if (ps->str_write(&ch, 1, ps->data) < 0) {
-			return -1;
-		}
-	}
-
-	return count;
-}
-
-/** Print one or more characters without adding newline.
- *
- * @param buf  Buffer holding characters with size of
- *             at least size bytes. NULL is not allowed!
- * @param size Size of the buffer in bytes.
- * @param ps   Output method and its data.
- *
- * @return Number of characters printed.
- *
- */
-static int printf_putnchars(const char *buf, size_t size,
-    printf_spec_t *ps)
-{
-	return ps->str_write((void *) buf, size, ps->data);
-}
-
-/** Print one or more wide characters without adding newline.
- *
- * @param buf  Buffer holding wide characters with size of
- *             at least size bytes. NULL is not allowed!
- * @param size Size of the buffer in bytes.
- * @param ps   Output method and its data.
- *
- * @return Number of wide characters printed.
- *
- */
-static int printf_wputnchars(const char32_t *buf, size_t size,
-    printf_spec_t *ps)
-{
-	return ps->wstr_write((void *) buf, size, ps->data);
-}
-
-/** Print string without adding a newline.
- *
- * @param str String to print.
- * @param ps  Write function specification and support data.
- *
- * @return Number of characters printed.
- *
- */
-static int printf_putstr(const char *str, printf_spec_t *ps)
-{
-	if (str == NULL)
-		return printf_putnchars(nullstr, str_size(nullstr), ps);
-
-	return ps->str_write((void *) str, str_size(str), ps->data);
-}
-
-/** Print one ASCII character.
- *
- * @param c  ASCII character to be printed.
- * @param ps Output method.
- *
- * @return Number of characters printed.
- *
- */
-static int printf_putchar(const char ch, printf_spec_t *ps)
-{
-	if (!ascii_check(ch))
-		return ps->str_write((void *) &invalch, 1, ps->data);
-
-	return ps->str_write(&ch, 1, ps->data);
-}
-
-/** Print one wide character.
- *
- * @param c  Wide character to be printed.
- * @param ps Output method.
- *
- * @return Number of characters printed.
- *
- */
-static int printf_putuchar(const char32_t ch, printf_spec_t *ps)
-{
-	if (!chr_check(ch))
-		return ps->str_write((void *) &invalch, 1, ps->data);
-
-	return ps->wstr_write(&ch, sizeof(char32_t), ps->data);
-}
-
-/** Print one formatted ASCII character.
- *
- * @param ch    Character to print.
- * @param width Width modifier.
- * @param flags Flags that change the way the character is printed.
- *
- * @return Number of characters printed, negative value on failure.
- *
- */
-static int print_char(const char ch, int width, uint32_t flags, printf_spec_t *ps)
-{
-	size_t counter = 0;
-	if (!(flags & __PRINTF_FLAG_LEFTALIGNED)) {
-		while (--width > 0) {
-			/*
-			 * One space is consumed by the character itself, hence
-			 * the predecrement.
-			 */
-			if (printf_putchar(' ', ps) > 0)
-				counter++;
-		}
-	}
-
-	if (printf_putchar(ch, ps) > 0)
-		counter++;
-
-	while (--width > 0) {
-		/*
-		 * One space is consumed by the character itself, hence
-		 * the predecrement.
-		 */
-		if (printf_putchar(' ', ps) > 0)
-			counter++;
-	}
-
-	return (int) (counter);
-}
-
-/** Print one formatted wide character.
- *
- * @param ch    Character to print.
- * @param width Width modifier.
- * @param flags Flags that change the way the character is printed.
- *
- * @return Number of characters printed, negative value on failure.
- *
- */
-static int print_wchar(const char32_t ch, int width, uint32_t flags, printf_spec_t *ps)
-{
-	size_t counter = 0;
-	if (!(flags & __PRINTF_FLAG_LEFTALIGNED)) {
-		while (--width > 0) {
-			/*
-			 * One space is consumed by the character itself, hence
-			 * the predecrement.
-			 */
-			if (printf_putchar(' ', ps) > 0)
-				counter++;
-		}
-	}
-
-	if (printf_putuchar(ch, ps) > 0)
-		counter++;
-
-	while (--width > 0) {
-		/*
-		 * One space is consumed by the character itself, hence
-		 * the predecrement.
-		 */
-		if (printf_putchar(' ', ps) > 0)
-			counter++;
-	}
-
-	return (int) (counter);
-}
-
-/** Print string.
- *
- * @param str       String to be printed.
- * @param width     Width modifier.
- * @param precision Precision modifier.
- * @param flags     Flags that modify the way the string is printed.
- *
- * @return Number of characters printed, negative value on failure.
- */
-static int print_str(char *str, int width, unsigned int precision,
-    uint32_t flags, printf_spec_t *ps)
-{
-	if (str == NULL)
-		return printf_putstr(nullstr, ps);
-
-	size_t strw = str_length(str);
-
-	/* Precision unspecified - print everything. */
-	if ((precision == 0) || (precision > strw))
-		precision = strw;
-
-	/* Left padding */
-	size_t counter = 0;
-	width -= precision;
-	if (!(flags & __PRINTF_FLAG_LEFTALIGNED)) {
-		while (width-- > 0) {
-			if (printf_putchar(' ', ps) == 1)
-				counter++;
-		}
-	}
-
-	/* Part of @a str fitting into the alloted space. */
-	int retval;
-	size_t size = str_lsize(str, precision);
-	if ((retval = printf_putnchars(str, size, ps)) < 0)
-		return -counter;
-
-	counter += retval;
-
-	/* Right padding */
-	while (width-- > 0) {
-		if (printf_putchar(' ', ps) == 1)
-			counter++;
-	}
-
-	return ((int) counter);
-
-}
-
-/** Print wide string.
- *
- * @param str       Wide string to be printed.
- * @param width     Width modifier.
- * @param precision Precision modifier.
- * @param flags     Flags that modify the way the string is printed.
- *
- * @return Number of wide characters printed, negative value on failure.
- */
-static int print_wstr(char32_t *str, int width, unsigned int precision,
-    uint32_t flags, printf_spec_t *ps)
-{
-	if (str == NULL)
-		return printf_putstr(nullstr, ps);
-
-	size_t strw = wstr_length(str);
-
-	/* Precision not specified - print everything. */
-	if ((precision == 0) || (precision > strw))
-		precision = strw;
-
-	/* Left padding */
-	size_t counter = 0;
-	width -= precision;
-	if (!(flags & __PRINTF_FLAG_LEFTALIGNED)) {
-		while (width-- > 0) {
-			if (printf_putchar(' ', ps) == 1)
-				counter++;
-		}
-	}
-
-	/* Part of @a wstr fitting into the alloted space. */
-	int retval;
-	size_t size = wstr_lsize(str, precision);
-	if ((retval = printf_wputnchars(str, size, ps)) < 0)
-		return -counter;
-
-	counter += retval;
-
-	/* Right padding */
-	while (width-- > 0) {
-		if (printf_putchar(' ', ps) == 1)
-			counter++;
-	}
-
-	return ((int) counter);
-}
-
-/** Print a number in a given base.
- *
- * Print significant digits of a number in given base.
- *
- * @param num       Number to print.
- * @param width     Width modifier.
- * @param precision Precision modifier.
- * @param base      Base to print the number in (must be between 2 and 16).
- * @param flags     Flags that modify the way the number is printed.
- *
- * @return Number of characters printed.
- *
- */
-static int print_number(uint64_t num, int width, int precision, int base,
-    uint32_t flags, printf_spec_t *ps)
-{
-	/* Precision not specified. */
-	if (precision < 0) {
-		precision = 0;
-	}
-
-	const char *digits;
-	if (flags & __PRINTF_FLAG_BIGCHARS)
-		digits = digits_big;
-	else
-		digits = digits_small;
-
-	char data[PRINT_NUMBER_BUFFER_SIZE];
-	char *ptr = &data[PRINT_NUMBER_BUFFER_SIZE - 1];
-
-	/* Size of number with all prefixes and signs */
-	int size = 0;
-
-	/* Put zero at end of string */
-	*ptr-- = 0;
-
-	if (num == 0) {
-		*ptr-- = '0';
-		size++;
-	} else {
-		do {
-			*ptr-- = digits[num % base];
-			size++;
-		} while (num /= base);
-	}
-
-	/* Size of plain number */
-	int number_size = size;
-
-	/*
-	 * Collect the sum of all prefixes/signs/etc. to calculate padding and
-	 * leading zeroes.
-	 */
-	if (flags & __PRINTF_FLAG_PREFIX) {
-		switch (base) {
-		case 2:
-			/* Binary formating is not standard, but usefull */
-			size += 2;
-			break;
-		case 8:
-			size++;
-			break;
-		case 16:
-			size += 2;
-			break;
-		}
-	}
-
-	char sgn = 0;
-	if (flags & __PRINTF_FLAG_SIGNED) {
-		if (flags & __PRINTF_FLAG_NEGATIVE) {
-			sgn = '-';
-			size++;
-		} else if (flags & __PRINTF_FLAG_SHOWPLUS) {
-			sgn = '+';
-			size++;
-		} else if (flags & __PRINTF_FLAG_SPACESIGN) {
-			sgn = ' ';
-			size++;
-		}
-	}
-
-	if (flags & __PRINTF_FLAG_LEFTALIGNED)
-		flags &= ~__PRINTF_FLAG_ZEROPADDED;
-
-	/*
-	 * If the number is left-aligned or precision is specified then
-	 * padding with zeros is ignored.
-	 */
-	if (flags & __PRINTF_FLAG_ZEROPADDED) {
-		if ((precision == 0) && (width > size))
-			precision = width - size + number_size;
-	}
-
-	/* Print leading spaces */
-	if (number_size > precision) {
-		/* Print the whole number, not only a part */
-		precision = number_size;
-	}
-
-	width -= precision + size - number_size;
-	size_t counter = 0;
-
-	if (!(flags & __PRINTF_FLAG_LEFTALIGNED)) {
-		while (width-- > 0) {
-			if (printf_putchar(' ', ps) == 1)
-				counter++;
-		}
-	}
-
-	/* Print sign */
-	if (sgn) {
-		if (printf_putchar(sgn, ps) == 1)
-			counter++;
-	}
-
-	/* Print prefix */
-	if (flags & __PRINTF_FLAG_PREFIX) {
-		switch (base) {
-		case 2:
-			/* Binary formating is not standard, but usefull */
-			if (printf_putchar('0', ps) == 1)
-				counter++;
-			if (flags & __PRINTF_FLAG_BIGCHARS) {
-				if (printf_putchar('B', ps) == 1)
-					counter++;
-			} else {
-				if (printf_putchar('b', ps) == 1)
-					counter++;
-			}
-			break;
-		case 8:
-			if (printf_putchar('o', ps) == 1)
-				counter++;
-			break;
-		case 16:
-			if (printf_putchar('0', ps) == 1)
-				counter++;
-			if (flags & __PRINTF_FLAG_BIGCHARS) {
-				if (printf_putchar('X', ps) == 1)
-					counter++;
-			} else {
-				if (printf_putchar('x', ps) == 1)
-					counter++;
-			}
-			break;
-		}
-	}
-
-	/* Print leading zeroes */
-	precision -= number_size;
-	while (precision-- > 0) {
-		if (printf_putchar('0', ps) == 1)
-			counter++;
-	}
-
-	/* Print the number itself */
-	int retval;
-	if ((retval = printf_putstr(++ptr, ps)) > 0)
-		counter += retval;
-
-	/* Print trailing spaces */
-
-	while (width-- > 0) {
-		if (printf_putchar(' ', ps) == 1)
-			counter++;
-	}
-
-	return ((int) counter);
-}
-
-/** Prints a special double (ie NaN, infinity) padded to width characters. */
-static int print_special(ieee_double_t val, int width, uint32_t flags,
-    printf_spec_t *ps)
-{
-	assert(val.is_special);
-
-	char sign = get_sign_char(val.is_negative, flags);
-
-	const int str_len = 3;
-	const char *str;
-
-	if (flags & __PRINTF_FLAG_BIGCHARS) {
-		str = val.is_infinity ? "INF" : "NAN";
-	} else {
-		str = val.is_infinity ? "inf" : "nan";
-	}
-
-	int padding_len = max(0, width - ((sign ? 1 : 0) + str_len));
-
-	int counter = 0;
-	int ret;
-
-	/* Leading padding. */
-	if (!(flags & __PRINTF_FLAG_LEFTALIGNED)) {
-		if ((ret = print_padding(' ', padding_len, ps)) < 0)
-			return -1;
-
-		counter += ret;
-	}
-
-	if (sign) {
-		if ((ret = ps->str_write(&sign, 1, ps->data)) < 0)
-			return -1;
-
-		counter += ret;
-	}
-
-	if ((ret = ps->str_write(str, str_len, ps->data)) < 0)
-		return -1;
-
-	counter += ret;
-
-	/* Trailing padding. */
-	if (flags & __PRINTF_FLAG_LEFTALIGNED) {
-		if ((ret = print_padding(' ', padding_len, ps)) < 0)
-			return -1;
-
-		counter += ret;
-	}
-
-	return counter;
-}
-
-/** Trims trailing zeros but leaves a single "0" intact. */
-static void fp_trim_trailing_zeros(char *buf, int *len, int *dec_exp)
-{
-	/* Cut the zero off by adjusting the exponent. */
-	while (2 <= *len && '0' == buf[*len - 1]) {
-		--*len;
-		++*dec_exp;
-	}
-}
-
-/** Textually round up the last digit thereby eliminating it. */
-static void fp_round_up(char *buf, int *len, int *dec_exp)
-{
-	assert(1 <= *len);
-
-	char *last_digit = &buf[*len - 1];
-
-	int carry = ('5' <= *last_digit);
-
-	/* Cut the digit off by adjusting the exponent. */
-	--*len;
-	++*dec_exp;
-	--last_digit;
-
-	if (carry) {
-		/* Skip all the digits to cut off/round to zero. */
-		while (buf <= last_digit && '9' == *last_digit) {
-			--last_digit;
-		}
-
-		/* last_digit points to the last digit to round but not '9' */
-		if (buf <= last_digit) {
-			*last_digit += 1;
-			int new_len = last_digit - buf + 1;
-			*dec_exp += *len - new_len;
-			*len = new_len;
-		} else {
-			/* All len digits rounded to 0. */
-			buf[0] = '1';
-			*dec_exp += *len;
-			*len = 1;
-		}
-	} else {
-		/* The only digit was rounded to 0. */
-		if (last_digit < buf) {
-			buf[0] = '0';
-			*dec_exp = 0;
-			*len = 1;
-		}
-	}
-}
-
-/** Format and print the double string repressentation according
- *  to the %f specifier.
- */
-static int print_double_str_fixed(double_str_t *val_str, int precision, int width,
-    uint32_t flags, printf_spec_t *ps)
-{
-	int len = val_str->len;
-	char *buf = val_str->str;
-	int dec_exp = val_str->dec_exp;
-
-	assert(0 < len);
-	assert(0 <= precision);
-	assert(0 <= dec_exp || -dec_exp <= precision);
-
-	/* Number of integral digits to print (at least leading zero). */
-	int int_len = max(1, len + dec_exp);
-
-	char sign = get_sign_char(val_str->neg, flags);
-
-	/* Fractional portion lengths. */
-	int last_frac_signif_pos = max(0, -dec_exp);
-	int leading_frac_zeros = max(0, last_frac_signif_pos - len);
-	int signif_frac_figs = min(last_frac_signif_pos, len);
-	int trailing_frac_zeros = precision - last_frac_signif_pos;
-	char *buf_frac = buf + len - signif_frac_figs;
-
-	if (flags & __PRINTF_FLAG_NOFRACZEROS) {
-		trailing_frac_zeros = 0;
-	}
-
-	int frac_len = leading_frac_zeros + signif_frac_figs + trailing_frac_zeros;
-
-	bool has_decimal_pt = (0 < frac_len) || (flags & __PRINTF_FLAG_DECIMALPT);
-
-	/* Number of non-padding chars to print. */
-	int num_len = (sign ? 1 : 0) + int_len + (has_decimal_pt ? 1 : 0) + frac_len;
-
-	int padding_len = max(0, width - num_len);
-	int ret = 0;
-	int counter = 0;
-
-	/* Leading padding and sign. */
-
-	if (!(flags & (__PRINTF_FLAG_LEFTALIGNED | __PRINTF_FLAG_ZEROPADDED))) {
-		if ((ret = print_padding(' ', padding_len, ps)) < 0)
-			return -1;
-
-		counter += ret;
-	}
-
-	if (sign) {
-		if ((ret = ps->str_write(&sign, 1, ps->data)) < 0)
-			return -1;
-
-		counter += ret;
-	}
-
-	if (flags & __PRINTF_FLAG_ZEROPADDED) {
-		if ((ret = print_padding('0', padding_len, ps)) < 0)
-			return -1;
-
-		counter += ret;
-	}
-
-	/* Print the intergral part of the buffer. */
-
-	int buf_int_len = min(len, len + dec_exp);
-
-	if (0 < buf_int_len) {
-		if ((ret = ps->str_write(buf, buf_int_len, ps->data)) < 0)
-			return -1;
-
-		counter += ret;
-
-		/* Print trailing zeros of the integral part of the number. */
-		if ((ret = print_padding('0', int_len - buf_int_len, ps)) < 0)
-			return -1;
-	} else {
-		/* Single leading integer 0. */
-		char ch = '0';
-		if ((ret = ps->str_write(&ch, 1, ps->data)) < 0)
-			return -1;
-	}
-
-	counter += ret;
-
-	/* Print the decimal point and the fractional part. */
-	if (has_decimal_pt) {
-		char ch = '.';
-
-		if ((ret = ps->str_write(&ch, 1, ps->data)) < 0)
-			return -1;
-
-		counter += ret;
-
-		/* Print leading zeros of the fractional part of the number. */
-		if ((ret = print_padding('0', leading_frac_zeros, ps)) < 0)
-			return -1;
-
-		counter += ret;
-
-		/* Print significant digits of the fractional part of the number. */
-		if (0 < signif_frac_figs) {
-			if ((ret = ps->str_write(buf_frac, signif_frac_figs, ps->data)) < 0)
-				return -1;
-
-			counter += ret;
-		}
-
-		/* Print trailing zeros of the fractional part of the number. */
-		if ((ret = print_padding('0', trailing_frac_zeros, ps)) < 0)
-			return -1;
-
-		counter += ret;
-	}
-
-	/* Trailing padding. */
-	if (flags & __PRINTF_FLAG_LEFTALIGNED) {
-		if ((ret = print_padding(' ', padding_len, ps)) < 0)
-			return -1;
-
-		counter += ret;
-	}
-
-	return counter;
-}
-
-/** Convert, format and print a double according to the %f specifier.
- *
- * @param g     Double to print.
- * @param precision Number of fractional digits to print. If 0 no
- *              decimal point will be printed unless the flag
- *              __PRINTF_FLAG_DECIMALPT is specified.
- * @param width Minimum number of characters to display. Pads
- *              with '0' or ' ' depending on the set flags;
- * @param flags Printf flags.
- * @param ps    Printing functions.
- *
- * @return The number of characters printed; negative on failure.
- */
-static int print_double_fixed(double g, int precision, int width, uint32_t flags,
-    printf_spec_t *ps)
-{
-	if (flags & __PRINTF_FLAG_LEFTALIGNED) {
-		flags &= ~__PRINTF_FLAG_ZEROPADDED;
-	}
-
-	if (flags & __PRINTF_FLAG_DECIMALPT) {
-		flags &= ~__PRINTF_FLAG_NOFRACZEROS;
-	}
-
-	ieee_double_t val = extract_ieee_double(g);
-
-	if (val.is_special) {
-		return print_special(val, width, flags, ps);
-	}
-
-	char buf[MAX_DOUBLE_STR_BUF_SIZE];
-	const size_t buf_size = MAX_DOUBLE_STR_BUF_SIZE;
-	double_str_t val_str;
-
-	val_str.str = buf;
-	val_str.neg = val.is_negative;
-
-	if (0 <= precision) {
-		/*
-		 * Request one more digit so we can round the result. The last
-		 * digit it returns may have an error of at most +/- 1.
-		 */
-		val_str.len = double_to_fixed_str(val, -1, precision + 1, buf, buf_size,
-		    &val_str.dec_exp);
-
-		/*
-		 * Round using the last digit to produce precision fractional digits.
-		 * If less than precision+1 fractional digits were output the last
-		 * digit is definitely inaccurate so also round to get rid of it.
-		 */
-		fp_round_up(buf, &val_str.len, &val_str.dec_exp);
-
-		/* Rounding could have introduced trailing zeros. */
-		if (flags & __PRINTF_FLAG_NOFRACZEROS) {
-			fp_trim_trailing_zeros(buf, &val_str.len, &val_str.dec_exp);
-		}
-	} else {
-		/* Let the implementation figure out the proper precision. */
-		val_str.len = double_to_short_str(val, buf, buf_size, &val_str.dec_exp);
-
-		/* Precision needed for the last significant digit. */
-		precision = max(0, -val_str.dec_exp);
-	}
-
-	return print_double_str_fixed(&val_str, precision, width, flags, ps);
-}
-
-/** Prints the decimal exponent part of a %e specifier formatted number. */
-static int print_exponent(int exp_val, uint32_t flags, printf_spec_t *ps)
-{
-	int counter = 0;
-	int ret;
-
-	char exp_ch = (flags & __PRINTF_FLAG_BIGCHARS) ? 'E' : 'e';
-
-	if ((ret = ps->str_write(&exp_ch, 1, ps->data)) < 0)
-		return -1;
-
-	counter += ret;
-
-	char exp_sign = (exp_val < 0) ? '-' : '+';
-
-	if ((ret = ps->str_write(&exp_sign, 1, ps->data)) < 0)
-		return -1;
-
-	counter += ret;
-
-	/* Print the exponent. */
-	exp_val = abs(exp_val);
-
-	char exp_str[4] = { 0 };
-
-	exp_str[0] = '0' + exp_val / 100;
-	exp_str[1] = '0' + (exp_val % 100) / 10;
-	exp_str[2] = '0' + (exp_val % 10);
-
-	int exp_len = (exp_str[0] == '0') ? 2 : 3;
-	const char *exp_str_start = &exp_str[3] - exp_len;
-
-	if ((ret = ps->str_write(exp_str_start, exp_len, ps->data)) < 0)
-		return -1;
-
-	counter += ret;
-
-	return counter;
-}
-
-/** Format and print the double string repressentation according
- *  to the %e specifier.
- */
-static int print_double_str_scient(double_str_t *val_str, int precision,
-    int width, uint32_t flags, printf_spec_t *ps)
-{
-	int len = val_str->len;
-	int dec_exp = val_str->dec_exp;
-	char *buf  = val_str->str;
-
-	assert(0 < len);
-
-	char sign = get_sign_char(val_str->neg, flags);
-	bool has_decimal_pt = (0 < precision) || (flags & __PRINTF_FLAG_DECIMALPT);
-	int dec_pt_len = has_decimal_pt ? 1 : 0;
-
-	/* Fractional part lengths. */
-	int signif_frac_figs = len - 1;
-	int trailing_frac_zeros = precision - signif_frac_figs;
-
-	if (flags & __PRINTF_FLAG_NOFRACZEROS) {
-		trailing_frac_zeros = 0;
-	}
-
-	int frac_len = signif_frac_figs + trailing_frac_zeros;
-
-	int exp_val = dec_exp + len - 1;
-	/* Account for exponent sign and 'e'; minimum 2 digits. */
-	int exp_len = 2 + (abs(exp_val) >= 100 ? 3 : 2);
-
-	/* Number of non-padding chars to print. */
-	int num_len = (sign ? 1 : 0) + 1 + dec_pt_len + frac_len + exp_len;
-
-	int padding_len = max(0, width - num_len);
-	int ret = 0;
-	int counter = 0;
-
-	if (!(flags & (__PRINTF_FLAG_LEFTALIGNED | __PRINTF_FLAG_ZEROPADDED))) {
-		if ((ret = print_padding(' ', padding_len, ps)) < 0)
-			return -1;
-
-		counter += ret;
-	}
-
-	if (sign) {
-		if ((ret = ps->str_write(&sign, 1, ps->data)) < 0)
-			return -1;
-
-		counter += ret;
-	}
-
-	if (flags & __PRINTF_FLAG_ZEROPADDED) {
-		if ((ret = print_padding('0', padding_len, ps)) < 0)
-			return -1;
-
-		counter += ret;
-	}
-
-	/* Single leading integer. */
-	if ((ret = ps->str_write(buf, 1, ps->data)) < 0)
-		return -1;
-
-	counter += ret;
-
-	/* Print the decimal point and the fractional part. */
-	if (has_decimal_pt) {
-		char ch = '.';
-
-		if ((ret = ps->str_write(&ch, 1, ps->data)) < 0)
-			return -1;
-
-		counter += ret;
-
-		/* Print significant digits of the fractional part of the number. */
-		if (0 < signif_frac_figs) {
-			if ((ret = ps->str_write(buf + 1, signif_frac_figs, ps->data)) < 0)
-				return -1;
-
-			counter += ret;
-		}
-
-		/* Print trailing zeros of the fractional part of the number. */
-		if ((ret = print_padding('0', trailing_frac_zeros, ps)) < 0)
-			return -1;
-
-		counter += ret;
-	}
-
-	/* Print the exponent. */
-	if ((ret = print_exponent(exp_val, flags, ps)) < 0)
-		return -1;
-
-	counter += ret;
-
-	if (flags & __PRINTF_FLAG_LEFTALIGNED) {
-		if ((ret = print_padding(' ', padding_len, ps)) < 0)
-			return -1;
-
-		counter += ret;
-	}
-
-	return counter;
-}
-
-/** Convert, format and print a double according to the %e specifier.
- *
- * Note that if g is large, the output may be huge (3e100 prints
- * with at least 100 digits).
- *
- * %e style: [-]d.dddde+dd
- *  left-justified:  [-]d.dddde+dd[space_pad]
- *  right-justified: [space_pad][-][zero_pad]d.dddde+dd
- *
- * @param g     Double to print.
- * @param precision Number of fractional digits to print, ie
- *              precision + 1 significant digits to display. If 0 no
- *              decimal point will be printed unless the flag
- *              __PRINTF_FLAG_DECIMALPT is specified. If negative
- *              the shortest accurate number will be printed.
- * @param width Minimum number of characters to display. Pads
- *              with '0' or ' ' depending on the set flags;
- * @param flags Printf flags.
- * @param ps    Printing functions.
- *
- * @return The number of characters printed; negative on failure.
- */
-static int print_double_scientific(double g, int precision, int width,
-    uint32_t flags, printf_spec_t *ps)
-{
-	if (flags & __PRINTF_FLAG_LEFTALIGNED) {
-		flags &= ~__PRINTF_FLAG_ZEROPADDED;
-	}
-
-	ieee_double_t val = extract_ieee_double(g);
-
-	if (val.is_special) {
-		return print_special(val, width, flags, ps);
-	}
-
-	char buf[MAX_DOUBLE_STR_BUF_SIZE];
-	const size_t buf_size = MAX_DOUBLE_STR_BUF_SIZE;
-	double_str_t val_str;
-
-	val_str.str = buf;
-	val_str.neg = val.is_negative;
-
-	if (0 <= precision) {
-		/*
-		 * Request one more digit (in addition to the leading integer)
-		 * so we can round the result. The last digit it returns may
-		 * have an error of at most +/- 1.
-		 */
-		val_str.len = double_to_fixed_str(val, precision + 2, -1, buf, buf_size,
-		    &val_str.dec_exp);
-
-		/*
-		 * Round the extra digit to produce precision+1 significant digits.
-		 * If less than precision+2 significant digits were returned the last
-		 * digit is definitely inaccurate so also round to get rid of it.
-		 */
-		fp_round_up(buf, &val_str.len, &val_str.dec_exp);
-
-		/* Rounding could have introduced trailing zeros. */
-		if (flags & __PRINTF_FLAG_NOFRACZEROS) {
-			fp_trim_trailing_zeros(buf, &val_str.len, &val_str.dec_exp);
-		}
-	} else {
-		/* Let the implementation figure out the proper precision. */
-		val_str.len = double_to_short_str(val, buf, buf_size, &val_str.dec_exp);
-
-		/* Use all produced digits. */
-		precision = val_str.len - 1;
-	}
-
-	return print_double_str_scient(&val_str, precision, width, flags, ps);
-}
-
-/** Convert, format and print a double according to the %g specifier.
- *
- * %g style chooses between %f and %e.
- *
- * @param g     Double to print.
- * @param precision Number of significant digits to display; excluding
- *              any leading zeros from this count. If negative
- *              the shortest accurate number will be printed.
- * @param width Minimum number of characters to display. Pads
- *              with '0' or ' ' depending on the set flags;
- * @param flags Printf flags.
- * @param ps    Printing functions.
- *
- * @return The number of characters printed; negative on failure.
- */
-static int print_double_generic(double g, int precision, int width,
-    uint32_t flags, printf_spec_t *ps)
-{
-	ieee_double_t val = extract_ieee_double(g);
-
-	if (val.is_special) {
-		return print_special(val, width, flags, ps);
-	}
-
-	char buf[MAX_DOUBLE_STR_BUF_SIZE];
-	const size_t buf_size = MAX_DOUBLE_STR_BUF_SIZE;
-	int dec_exp;
-	int len;
-
-	/* Honor the user requested number of significant digits. */
-	if (0 <= precision) {
-		/*
-		 * Do a quick and dirty conversion of a single digit to determine
-		 * the decimal exponent.
-		 */
-		len = double_to_fixed_str(val, 1, -1, buf, buf_size, &dec_exp);
-		assert(0 < len);
-
-		precision = max(1, precision);
-
-		if (-4 <= dec_exp && dec_exp < precision) {
-			precision = precision - (dec_exp + 1);
-			return print_double_fixed(g, precision, width,
-			    flags | __PRINTF_FLAG_NOFRACZEROS, ps);
-		} else {
-			--precision;
-			return print_double_scientific(g, precision, width,
-			    flags | __PRINTF_FLAG_NOFRACZEROS, ps);
-		}
-	} else {
-		/* Convert to get the decimal exponent and digit count. */
-		len = double_to_short_str(val, buf, buf_size, &dec_exp);
-		assert(0 < len);
-
-		if (flags & __PRINTF_FLAG_LEFTALIGNED) {
-			flags &= ~__PRINTF_FLAG_ZEROPADDED;
-		}
-
-		double_str_t val_str;
-		val_str.str = buf;
-		val_str.len = len;
-		val_str.neg = val.is_negative;
-		val_str.dec_exp = dec_exp;
-
-		int first_digit_pos = len + dec_exp;
-		int last_digit_pos = dec_exp;
-
-		/* The whole number (15 digits max) fits between dec places 15 .. -6 */
-		if (len <= 15 && -6 <= last_digit_pos && first_digit_pos <= 15) {
-			/* Precision needed for the last significant digit. */
-			precision = max(0, -val_str.dec_exp);
-			return print_double_str_fixed(&val_str, precision, width, flags, ps);
-		} else {
-			/* Use all produced digits. */
-			precision = val_str.len - 1;
-			return print_double_str_scient(&val_str, precision, width, flags, ps);
-		}
-	}
-}
-
-/** Convert, format and print a double according to the specifier.
- *
- * Depending on the specifier it prints the double using the styles
- * %g, %f or %e by means of print_double_generic(), print_double_fixed(),
- * print_double_scientific().
- *
- * @param g     Double to print.
- * @param spec  Specifier of the style to print in; one of: 'g','G','f','F',
- *              'e','E'.
- * @param precision Number of fractional digits to display. If negative
- *              the shortest accurate number will be printed for style %g;
- *              negative precision defaults to 6 for styles %f, %e.
- * @param width Minimum number of characters to display. Pads
- *              with '0' or ' ' depending on the set flags;
- * @param flags Printf flags.
- * @param ps    Printing functions.
- *
- * @return The number of characters printed; negative on failure.
- */
-static int print_double(double g, char spec, int precision, int width,
-    uint32_t flags, printf_spec_t *ps)
-{
-	switch (spec) {
-	case 'F':
-		flags |= __PRINTF_FLAG_BIGCHARS;
-		/* Fallthrough */
-	case 'f':
-		precision = (precision < 0) ? 6 : precision;
-		return print_double_fixed(g, precision, width, flags, ps);
-
-	case 'E':
-		flags |= __PRINTF_FLAG_BIGCHARS;
-		/* Fallthrough */
-	case 'e':
-		precision = (precision < 0) ? 6 : precision;
-		return print_double_scientific(g, precision, width, flags, ps);
-
-	case 'G':
-		flags |= __PRINTF_FLAG_BIGCHARS;
-		/* Fallthrough */
-	case 'g':
-		return print_double_generic(g, precision, width, flags, ps);
-
-	default:
-		assert(false);
-		return -1;
-	}
-}
-
-/** Print formatted string.
- *
- * Print string formatted according to the fmt parameter and variadic arguments.
- * Each formatting directive must have the following form:
- *
- *  \% [ FLAGS ] [ WIDTH ] [ .PRECISION ] [ TYPE ] CONVERSION
- *
- * FLAGS:@n
- *  - "#" Force to print prefix. For \%o conversion, the prefix is 0, for
- *        \%x and \%X prefixes are 0x and 0X and for conversion \%b the
- *        prefix is 0b.
- *
- *  - "-" Align to left.
- *
- *  - "+" Print positive sign just as negative.
- *
- *  - " " If the printed number is positive and "+" flag is not set,
- *        print space in place of sign.
- *
- *  - "0" Print 0 as padding instead of spaces. Zeroes are placed between
- *        sign and the rest of the number. This flag is ignored if "-"
- *        flag is specified.
- *
- * WIDTH:@n
- *  - Specify the minimal width of a printed argument. If it is bigger,
- *    width is ignored. If width is specified with a "*" character instead of
- *    number, width is taken from parameter list. And integer parameter is
- *    expected before parameter for processed conversion specification. If
- *    this value is negative its absolute value is taken and the "-" flag is
- *    set.
- *
- * PRECISION:@n
- *  - Value precision. For numbers it specifies minimum valid numbers.
- *    Smaller numbers are printed with leading zeroes. Bigger numbers are not
- *    affected. Strings with more than precision characters are cut off. Just
- *    as with width, an "*" can be used used instead of a number. An integer
- *    value is then expected in parameters. When both width and precision are
- *    specified using "*", the first parameter is used for width and the
- *    second one for precision.
- *
- * TYPE:@n
- *  - "hh" Signed or unsigned char.@n
- *  - "h"  Signed or unsigned short.@n
- *  - ""   Signed or unsigned int (default value).@n
- *  - "l"  Signed or unsigned long int.@n
- *         If conversion is "c", the character is wint_t (wide character).@n
- *         If conversion is "s", the string is char32_t * (wide string).@n
- *  - "ll" Signed or unsigned long long int.@n
- *  - "z"  Signed or unsigned ssize_t or site_t.@n
- *
- * CONVERSION:@n
- *  - % Print percentile character itself.
- *
- *  - c Print single character. The character is expected to be plain
- *      ASCII (e.g. only values 0 .. 127 are valid).@n
- *      If type is "l", then the character is expected to be wide character
- *      (e.g. values 0 .. 0x10ffff are valid).
- *
- *  - s Print zero terminated string. If a NULL value is passed as
- *      value, "(NULL)" is printed instead.@n
- *      If type is "l", then the string is expected to be wide string.
- *
- *  - P, p Print value of a pointer. Void * value is expected and it is
- *         printed in hexadecimal notation with prefix (as with
- *         \%#0.8X / \%#0.8x for 32-bit or \%#0.16lX / \%#0.16lx for 64-bit
- *         long pointers).
- *
- *  - b Print value as unsigned binary number. Prefix is not printed by
- *      default. (Nonstandard extension.)
- *
- *  - o Print value as unsigned octal number. Prefix is not printed by
- *      default.
- *
- *  - d, i Print signed decimal number. There is no difference between d
- *         and i conversion.
- *
- *  - u Print unsigned decimal number.
- *
- *  - X, x Print hexadecimal number with upper- or lower-case. Prefix is
- *         not printed by default.
- *
- * All other characters from fmt except the formatting directives are printed
- * verbatim.
- *
- * @param fmt Format NULL-terminated string.
- *
- * @return Number of characters printed, negative value on failure.
- *
- */
-int printf_core(const char *fmt, printf_spec_t *ps, va_list ap)
-{
-	size_t i;        /* Index of the currently processed character from fmt */
-	size_t nxt = 0;  /* Index of the next character from fmt */
-	size_t j = 0;    /* Index to the first not printed nonformating character */
-
-	size_t counter = 0;   /* Number of characters printed */
-	int retval;           /* Return values from nested functions */
-
-	while (true) {
-		i = nxt;
-		char32_t uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-
-		if (uc == 0)
-			break;
-
-		/* Control character */
-		if (uc == '%') {
-			/* Print common characters if any processed */
-			if (i > j) {
-				if ((retval = printf_putnchars(&fmt[j], i - j, ps)) < 0) {
-					/* Error */
-					counter = -counter;
-					goto out;
-				}
-				counter += retval;
-			}
-
-			j = i;
-
-			/* Parse modifiers */
-			uint32_t flags = 0;
-			bool end = false;
-
-			do {
-				i = nxt;
-				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-				switch (uc) {
-				case '#':
-					flags |= __PRINTF_FLAG_PREFIX;
-					flags |= __PRINTF_FLAG_DECIMALPT;
-					break;
-				case '-':
-					flags |= __PRINTF_FLAG_LEFTALIGNED;
-					break;
-				case '+':
-					flags |= __PRINTF_FLAG_SHOWPLUS;
-					break;
-				case ' ':
-					flags |= __PRINTF_FLAG_SPACESIGN;
-					break;
-				case '0':
-					flags |= __PRINTF_FLAG_ZEROPADDED;
-					break;
-				default:
-					end = true;
-				}
-			} while (!end);
-
-			/* Width & '*' operator */
-			int width = 0;
-			if (isdigit(uc)) {
-				while (true) {
-					width *= 10;
-					width += uc - '0';
-
-					i = nxt;
-					uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-					if (uc == 0)
-						break;
-					if (!isdigit(uc))
-						break;
-				}
-			} else if (uc == '*') {
-				/* Get width value from argument list */
-				i = nxt;
-				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-				width = (int) va_arg(ap, int);
-				if (width < 0) {
-					/* Negative width sets '-' flag */
-					width *= -1;
-					flags |= __PRINTF_FLAG_LEFTALIGNED;
-				}
-			}
-
-			/* Precision and '*' operator */
-			int precision = -1;
-			if (uc == '.') {
-				i = nxt;
-				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-				if (isdigit(uc)) {
-					precision = 0;
-					while (true) {
-						precision *= 10;
-						precision += uc - '0';
-
-						i = nxt;
-						uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-						if (uc == 0)
-							break;
-						if (!isdigit(uc))
-							break;
-					}
-				} else if (uc == '*') {
-					/* Get precision value from the argument list */
-					i = nxt;
-					uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-					precision = (int) va_arg(ap, int);
-					if (precision < 0) {
-						/* Ignore negative precision - use default instead */
-						precision = -1;
-					}
-				}
-			}
-
-			qualifier_t qualifier;
-
-			switch (uc) {
-			case 't':
-				/* ptrdiff_t */
-				if (sizeof(ptrdiff_t) == sizeof(int32_t))
-					qualifier = PrintfQualifierInt;
-				else
-					qualifier = PrintfQualifierLongLong;
-				i = nxt;
-				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-				break;
-			case 'h':
-				/* Char or short */
-				qualifier = PrintfQualifierShort;
-				i = nxt;
-				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-				if (uc == 'h') {
-					i = nxt;
-					uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-					qualifier = PrintfQualifierByte;
-				}
-				break;
-			case 'l':
-				/* Long or long long */
-				qualifier = PrintfQualifierLong;
-				i = nxt;
-				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-				if (uc == 'l') {
-					i = nxt;
-					uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-					qualifier = PrintfQualifierLongLong;
-				}
-				break;
-			case 'z':
-				qualifier = PrintfQualifierSize;
-				i = nxt;
-				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-				break;
-			case 'j':
-				qualifier = PrintfQualifierMax;
-				i = nxt;
-				uc = str_decode(fmt, &nxt, STR_NO_LIMIT);
-				break;
-			default:
-				/* Default type */
-				qualifier = PrintfQualifierInt;
-			}
-
-			unsigned int base = 10;
-
-			switch (uc) {
-				/*
-				 * String and character conversions.
-				 */
-			case 's':
-				precision = max(0,  precision);
-
-				if (qualifier == PrintfQualifierLong)
-					retval = print_wstr(va_arg(ap, char32_t *), width, precision, flags, ps);
-				else
-					retval = print_str(va_arg(ap, char *), width, precision, flags, ps);
-
-				if (retval < 0) {
-					counter = -counter;
-					goto out;
-				}
-
-				counter += retval;
-				j = nxt;
-				continue;
-			case 'c':
-				if (qualifier == PrintfQualifierLong)
-					retval = print_wchar(va_arg(ap, wint_t), width, flags, ps);
-				else
-					retval = print_char(va_arg(ap, unsigned int), width, flags, ps);
-
-				if (retval < 0) {
-					counter = -counter;
-					goto out;
-				}
-
-				counter += retval;
-				j = nxt;
-				continue;
-
-				/*
-				 * Floating point values
-				 */
-			case 'G':
-			case 'g':
-			case 'F':
-			case 'f':
-			case 'E':
-			case 'e':
-				retval = print_double(va_arg(ap, double), uc, precision,
-				    width, flags, ps);
-
-				if (retval < 0) {
-					counter = -counter;
-					goto out;
-				}
-
-				counter += retval;
-				j = nxt;
-				continue;
-
-				/*
-				 * Integer values
-				 */
-			case 'P':
-				/* Pointer */
-				flags |= __PRINTF_FLAG_BIGCHARS;
-				/* Fallthrough */
-			case 'p':
-				flags |= __PRINTF_FLAG_PREFIX;
-				flags |= __PRINTF_FLAG_ZEROPADDED;
-				base = 16;
-				qualifier = PrintfQualifierPointer;
-				break;
-			case 'b':
-				base = 2;
-				break;
-			case 'o':
-				base = 8;
-				break;
-			case 'd':
-			case 'i':
-				flags |= __PRINTF_FLAG_SIGNED;
-				/* Fallthrough */
-			case 'u':
-				break;
-			case 'X':
-				flags |= __PRINTF_FLAG_BIGCHARS;
-				/* Fallthrough */
-			case 'x':
-				base = 16;
-				break;
-
-			case '%':
-				/* Percentile itself */
-				j = i;
-				continue;
-
-				/*
-				 * Bad formatting.
-				 */
-			default:
-				/*
-				 * Unknown format. Now, j is the index of '%'
-				 * so we will print whole bad format sequence.
-				 */
-				continue;
-			}
-
-			/* Print integers */
-			size_t size;
-			uint64_t number;
-
-			switch (qualifier) {
-			case PrintfQualifierByte:
-				size = sizeof(unsigned char);
-				number = PRINTF_GET_INT_ARGUMENT(int, ap, flags);
-				break;
-			case PrintfQualifierShort:
-				size = sizeof(unsigned short);
-				number = PRINTF_GET_INT_ARGUMENT(int, ap, flags);
-				break;
-			case PrintfQualifierInt:
-				size = sizeof(unsigned int);
-				number = PRINTF_GET_INT_ARGUMENT(int, ap, flags);
-				break;
-			case PrintfQualifierLong:
-				size = sizeof(unsigned long);
-				number = PRINTF_GET_INT_ARGUMENT(long, ap, flags);
-				break;
-			case PrintfQualifierLongLong:
-				size = sizeof(unsigned long long);
-				number = PRINTF_GET_INT_ARGUMENT(long long, ap, flags);
-				break;
-			case PrintfQualifierPointer:
-				size = sizeof(void *);
-				precision = size << 1;
-				number = (uint64_t) (uintptr_t) va_arg(ap, void *);
-				break;
-			case PrintfQualifierSize:
-				size = sizeof(size_t);
-				number = (uint64_t) va_arg(ap, size_t);
-				break;
-			case PrintfQualifierMax:
-				size = sizeof(uintmax_t);
-				number = (uint64_t) va_arg(ap, uintmax_t);
-				break;
-			default:
-				/* Unknown qualifier */
-				counter = -counter;
-				goto out;
-			}
-
-			if ((retval = print_number(number, width, precision,
-			    base, flags, ps)) < 0) {
-				counter = -counter;
-				goto out;
-			}
-
-			counter += retval;
-			j = nxt;
-		}
-	}
-
-	if (i > j) {
-		if ((retval = printf_putnchars(&fmt[j], i - j, ps)) < 0) {
-			/* Error */
-			counter = -counter;
-			goto out;
-		}
-		counter += retval;
-	}
-
-out:
-	return ((int) counter);
-}
-
-/** @}
- */
Index: uspace/lib/c/generic/io/vprintf.c
===================================================================
--- uspace/lib/c/generic/io/vprintf.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/generic/io/vprintf.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -35,5 +35,5 @@
 #include <stdarg.h>
 #include <stdio.h>
-#include <io/printf_core.h>
+#include <printf_core.h>
 #include <fibril_synch.h>
 #include <async.h>
Index: uspace/lib/c/generic/io/vsnprintf.c
===================================================================
--- uspace/lib/c/generic/io/vsnprintf.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/generic/io/vsnprintf.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,5 +36,5 @@
 #include <stdio.h>
 #include <str.h>
-#include <io/printf_core.h>
+#include <printf_core.h>
 #include <errno.h>
 
Index: pace/lib/c/generic/ipc_test.c
===================================================================
--- uspace/lib/c/generic/ipc_test.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,224 +1,0 @@
-/*
- * Copyright (c) 2018 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file IPC test service API
- */
-
-#include <abi/ipc/interfaces.h>
-#include <as.h>
-#include <errno.h>
-#include <ipc/services.h>
-#include <ipc/ipc_test.h>
-#include <loc.h>
-#include <stdlib.h>
-#include <ipc_test.h>
-
-/** Create IPC test service session.
- *
- * @param rvol Place to store pointer to volume service session.
- * @return     EOK on success, ENOMEM if out of memory, EIO if service
- *             cannot be contacted.
- */
-errno_t ipc_test_create(ipc_test_t **rtest)
-{
-	ipc_test_t *test;
-	service_id_t test_svcid;
-	errno_t rc;
-
-	test = calloc(1, sizeof(ipc_test_t));
-	if (test == NULL) {
-		rc = ENOMEM;
-		goto error;
-	}
-
-	rc = loc_service_get_id(SERVICE_NAME_IPC_TEST, &test_svcid, 0);
-	if (rc != EOK) {
-		rc = ENOENT;
-		goto error;
-	}
-
-	test->sess = loc_service_connect(test_svcid, INTERFACE_IPC_TEST, 0);
-	if (test->sess == NULL) {
-		rc = EIO;
-		goto error;
-	}
-
-	*rtest = test;
-	return EOK;
-error:
-	free(test);
-	return rc;
-}
-
-/** Destroy IPC test service session.
- *
- * @param test IPC test service session
- */
-void ipc_test_destroy(ipc_test_t *test)
-{
-	if (test == NULL)
-		return;
-
-	async_hangup(test->sess);
-	free(test);
-}
-
-/** Simple ping.
- *
- * @param test IPC test service
- * @return EOK on success or an error code
- */
-errno_t ipc_test_ping(ipc_test_t *test)
-{
-	async_exch_t *exch;
-	errno_t retval;
-
-	exch = async_exchange_begin(test->sess);
-	retval = async_req_0_0(exch, IPC_TEST_PING);
-	async_exchange_end(exch);
-
-	if (retval != EOK)
-		return retval;
-
-	return EOK;
-}
-
-/** Get size of shared read-only memory area.
- *
- * @param test IPC test service
- * @param rsize Place to store size of the shared area
- * @return EOK on success or an error code
- */
-errno_t ipc_test_get_ro_area_size(ipc_test_t *test, size_t *rsize)
-{
-	async_exch_t *exch;
-	errno_t retval;
-	sysarg_t size;
-
-	exch = async_exchange_begin(test->sess);
-	retval = async_req_0_1(exch, IPC_TEST_GET_RO_AREA_SIZE, &size);
-	async_exchange_end(exch);
-
-	if (retval != EOK)
-		return retval;
-
-	*rsize = size;
-	return EOK;
-}
-
-/** Get size of shared read-write memory area.
- *
- * @param test IPC test service
- * @param rsize Place to store size of the shared area
- * @return EOK on success or an error code
- */
-errno_t ipc_test_get_rw_area_size(ipc_test_t *test, size_t *rsize)
-{
-	async_exch_t *exch;
-	errno_t retval;
-	sysarg_t size;
-
-	exch = async_exchange_begin(test->sess);
-	retval = async_req_0_1(exch, IPC_TEST_GET_RW_AREA_SIZE, &size);
-	async_exchange_end(exch);
-
-	if (retval != EOK)
-		return retval;
-
-	*rsize = size;
-	return EOK;
-}
-
-/** Test share-in of read-only area.
- *
- * @param test IPC test service
- * @param size Size of the shared area
- * @param rptr Place to store pointer to the shared-in area
- * @return EOK on success or an error code
- */
-errno_t ipc_test_share_in_ro(ipc_test_t *test, size_t size, const void **rptr)
-{
-	async_exch_t *exch;
-	ipc_call_t answer;
-	aid_t req;
-	void *dst;
-	errno_t rc;
-
-	exch = async_exchange_begin(test->sess);
-	req = async_send_0(exch, IPC_TEST_SHARE_IN_RO, &answer);
-
-	dst = NULL;
-	rc = async_share_in_start_0_0(exch, size, &dst);
-	if (rc != EOK || dst == AS_MAP_FAILED) {
-		async_exchange_end(exch);
-		async_forget(req);
-		return ENOMEM;
-	}
-
-	async_exchange_end(exch);
-	async_wait_for(req, NULL);
-	*rptr = dst;
-	return EOK;
-}
-
-/** Test share-in of read-write area.
- *
- * @param test IPC test service
- * @param size Size of the shared area
- * @param rptr Place to store pointer to the shared-in area
- * @return EOK on success or an error code
- */
-errno_t ipc_test_share_in_rw(ipc_test_t *test, size_t size, void **rptr)
-{
-	async_exch_t *exch;
-	ipc_call_t answer;
-	aid_t req;
-	void *dst;
-	errno_t rc;
-
-	exch = async_exchange_begin(test->sess);
-	req = async_send_0(exch, IPC_TEST_SHARE_IN_RW, &answer);
-
-	rc = async_share_in_start_0_0(exch, size, &dst);
-	if (rc != EOK || dst == AS_MAP_FAILED) {
-		async_exchange_end(exch);
-		async_forget(req);
-		return ENOMEM;
-	}
-
-	async_exchange_end(exch);
-	async_wait_for(req, NULL);
-	*rptr = dst;
-	return EOK;
-}
-
-/** @}
- */
Index: uspace/lib/c/generic/loc.c
===================================================================
--- uspace/lib/c/generic/loc.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/generic/loc.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,5 +1,5 @@
 /*
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2007 Josef Cejka
- * Copyright (c) 2011 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,8 +39,5 @@
 #include <stdbool.h>
 
-static FIBRIL_MUTEX_INITIALIZE(loc_supp_block_mutex);
 static FIBRIL_MUTEX_INITIALIZE(loc_cons_block_mutex);
-
-static FIBRIL_MUTEX_INITIALIZE(loc_supplier_mutex);
 static FIBRIL_MUTEX_INITIALIZE(loc_consumer_mutex);
 
@@ -50,8 +47,5 @@
 static void *cat_change_arg = NULL;
 
-static async_sess_t *loc_supp_block_sess = NULL;
 static async_sess_t *loc_cons_block_sess = NULL;
-
-static async_sess_t *loc_supplier_sess = NULL;
 static async_sess_t *loc_consumer_sess = NULL;
 
@@ -108,5 +102,5 @@
 	if (!loc_callback_created) {
 		async_exch_t *exch =
-		    loc_exchange_begin_blocking(INTERFACE_LOC_CONSUMER);
+		    loc_exchange_begin_blocking();
 
 		ipc_call_t answer;
@@ -135,96 +129,49 @@
 /** Start an async exchange on the loc session (blocking).
  *
- * @param iface Location service interface to choose
- *
  * @return New exchange.
  *
  */
-async_exch_t *loc_exchange_begin_blocking(iface_t iface)
-{
-	switch (iface) {
-	case INTERFACE_LOC_SUPPLIER:
-		fibril_mutex_lock(&loc_supp_block_mutex);
-
-		while (loc_supp_block_sess == NULL) {
-			clone_session(&loc_supplier_mutex, loc_supplier_sess,
-			    &loc_supp_block_sess);
-
-			if (loc_supp_block_sess == NULL)
-				loc_supp_block_sess =
-				    service_connect_blocking(SERVICE_LOC,
-				    INTERFACE_LOC_SUPPLIER, 0, NULL);
-		}
-
-		fibril_mutex_unlock(&loc_supp_block_mutex);
-
-		clone_session(&loc_supplier_mutex, loc_supp_block_sess,
-		    &loc_supplier_sess);
-
-		return async_exchange_begin(loc_supp_block_sess);
-	case INTERFACE_LOC_CONSUMER:
-		fibril_mutex_lock(&loc_cons_block_mutex);
-
-		while (loc_cons_block_sess == NULL) {
-			clone_session(&loc_consumer_mutex, loc_consumer_sess,
-			    &loc_cons_block_sess);
-
-			if (loc_cons_block_sess == NULL)
-				loc_cons_block_sess =
-				    service_connect_blocking(SERVICE_LOC,
-				    INTERFACE_LOC_CONSUMER, 0, NULL);
-		}
-
-		fibril_mutex_unlock(&loc_cons_block_mutex);
-
-		clone_session(&loc_consumer_mutex, loc_cons_block_sess,
-		    &loc_consumer_sess);
-
-		return async_exchange_begin(loc_cons_block_sess);
-	default:
+async_exch_t *loc_exchange_begin_blocking(void)
+{
+	fibril_mutex_lock(&loc_cons_block_mutex);
+
+	while (loc_cons_block_sess == NULL) {
+		clone_session(&loc_consumer_mutex, loc_consumer_sess,
+		    &loc_cons_block_sess);
+
+		if (loc_cons_block_sess == NULL)
+			loc_cons_block_sess =
+			    service_connect_blocking(SERVICE_LOC,
+			    INTERFACE_LOC_CONSUMER, 0, NULL);
+	}
+
+	fibril_mutex_unlock(&loc_cons_block_mutex);
+
+	clone_session(&loc_consumer_mutex, loc_cons_block_sess,
+	    &loc_consumer_sess);
+
+	return async_exchange_begin(loc_cons_block_sess);
+}
+
+/** Start an async exchange on the loc session.
+ *
+ * @return New exchange.
+ *
+ */
+async_exch_t *loc_exchange_begin(void)
+{
+	fibril_mutex_lock(&loc_consumer_mutex);
+
+	if (loc_consumer_sess == NULL)
+		loc_consumer_sess =
+		    service_connect(SERVICE_LOC,
+		    INTERFACE_LOC_CONSUMER, 0, NULL);
+
+	fibril_mutex_unlock(&loc_consumer_mutex);
+
+	if (loc_consumer_sess == NULL)
 		return NULL;
-	}
-}
-
-/** Start an async exchange on the loc session.
- *
- * @param iface Location service interface to choose
- *
- * @return New exchange.
- *
- */
-async_exch_t *loc_exchange_begin(iface_t iface)
-{
-	switch (iface) {
-	case INTERFACE_LOC_SUPPLIER:
-		fibril_mutex_lock(&loc_supplier_mutex);
-
-		if (loc_supplier_sess == NULL)
-			loc_supplier_sess =
-			    service_connect(SERVICE_LOC,
-			    INTERFACE_LOC_SUPPLIER, 0, NULL);
-
-		fibril_mutex_unlock(&loc_supplier_mutex);
-
-		if (loc_supplier_sess == NULL)
-			return NULL;
-
-		return async_exchange_begin(loc_supplier_sess);
-	case INTERFACE_LOC_CONSUMER:
-		fibril_mutex_lock(&loc_consumer_mutex);
-
-		if (loc_consumer_sess == NULL)
-			loc_consumer_sess =
-			    service_connect(SERVICE_LOC,
-			    INTERFACE_LOC_CONSUMER, 0, NULL);
-
-		fibril_mutex_unlock(&loc_consumer_mutex);
-
-		if (loc_consumer_sess == NULL)
-			return NULL;
-
-		return async_exchange_begin(loc_consumer_sess);
-	default:
-		return NULL;
-	}
+
+	return async_exchange_begin(loc_consumer_sess);
 }
 
@@ -239,8 +186,27 @@
 }
 
-/** Register new server with loc. */
-errno_t loc_server_register(const char *name)
-{
-	async_exch_t *exch = loc_exchange_begin_blocking(INTERFACE_LOC_SUPPLIER);
+/** Register new server with loc.
+ *
+ * @param name Server name
+ * @param rsrv Place to store new server object on success
+ * @return EOK on succes or an error code
+ */
+errno_t loc_server_register(const char *name, loc_srv_t **rsrv)
+{
+	async_exch_t *exch;
+	loc_srv_t *srv;
+
+	srv = calloc(1, sizeof(loc_srv_t));
+	if (srv == NULL)
+		return ENOMEM;
+
+	srv->sess = service_connect_blocking(SERVICE_LOC,
+	    INTERFACE_LOC_SUPPLIER, 0, NULL);
+	if (srv->sess == NULL) {
+		free(srv);
+		return ENOMEM;
+	}
+
+	exch = async_exchange_begin(srv->sess);
 
 	ipc_call_t answer;
@@ -250,5 +216,7 @@
 	if (retval != EOK) {
 		async_forget(req);
-		loc_exchange_end(exch);
+		async_exchange_end(exch);
+		async_hangup(srv->sess);
+		free(srv);
 		return retval;
 	}
@@ -262,19 +230,39 @@
 	 */
 	async_wait_for(req, &retval);
-	loc_exchange_end(exch);
-
+	async_exchange_end(exch);
+
+	if (retval != EOK) {
+		async_hangup(srv->sess);
+		free(srv);
+		return retval;
+	}
+
+	*rsrv = srv;
 	return retval;
 }
 
+/** Unregister server from loc.
+ *
+ * Unregister server and free server object.
+ *
+ * @param srv Server object
+ */
+void loc_server_unregister(loc_srv_t *srv)
+{
+	async_hangup(srv->sess);
+	free(srv);
+}
+
 /** Register new service.
  *
- * @param      fqsn  Fully qualified service name
- * @param[out] sid   Service ID of new service
- *
- */
-errno_t loc_service_register(const char *fqsn, service_id_t *sid)
-{
-	async_exch_t *exch = loc_exchange_begin_blocking(INTERFACE_LOC_SUPPLIER);
-
+ * @param srv Server object
+ * @param fqsn Fully qualified service name
+ * @param sid  Service ID of new service
+ *
+ */
+errno_t loc_service_register(loc_srv_t *srv, const char *fqsn,
+    service_id_t *sid)
+{
+	async_exch_t *exch = async_exchange_begin(srv->sess);
 	ipc_call_t answer;
 	aid_t req = async_send_0(exch, LOC_SERVICE_REGISTER, &answer);
@@ -283,5 +271,5 @@
 	if (retval != EOK) {
 		async_forget(req);
-		loc_exchange_end(exch);
+		async_exchange_end(exch);
 		return retval;
 	}
@@ -293,5 +281,5 @@
 	 */
 	async_wait_for(req, &retval);
-	loc_exchange_end(exch);
+	async_exchange_end(exch);
 
 	if (retval != EOK) {
@@ -310,14 +298,15 @@
 /** Unregister service.
  *
- * @param sid	Service ID
- */
-errno_t loc_service_unregister(service_id_t sid)
+ * @param srv Server object
+ * @param sid Service ID
+ */
+errno_t loc_service_unregister(loc_srv_t *srv, service_id_t sid)
 {
 	async_exch_t *exch;
 	errno_t retval;
 
-	exch = loc_exchange_begin_blocking(INTERFACE_LOC_SUPPLIER);
+	exch = async_exchange_begin(srv->sess);
 	retval = async_req_1_0(exch, LOC_SERVICE_UNREGISTER, sid);
-	loc_exchange_end(exch);
+	async_exchange_end(exch);
 
 	return (errno_t)retval;
@@ -330,7 +319,7 @@
 
 	if (flags & IPC_FLAG_BLOCKING)
-		exch = loc_exchange_begin_blocking(INTERFACE_LOC_CONSUMER);
+		exch = loc_exchange_begin_blocking();
 	else {
-		exch = loc_exchange_begin(INTERFACE_LOC_CONSUMER);
+		exch = loc_exchange_begin();
 		if (exch == NULL)
 			return errno;
@@ -383,5 +372,5 @@
 
 	*name = NULL;
-	exch = loc_exchange_begin_blocking(INTERFACE_LOC_CONSUMER);
+	exch = loc_exchange_begin_blocking();
 
 	ipc_call_t answer;
@@ -463,7 +452,7 @@
 
 	if (flags & IPC_FLAG_BLOCKING)
-		exch = loc_exchange_begin_blocking(INTERFACE_LOC_CONSUMER);
+		exch = loc_exchange_begin_blocking();
 	else {
-		exch = loc_exchange_begin(INTERFACE_LOC_CONSUMER);
+		exch = loc_exchange_begin();
 		if (exch == NULL)
 			return errno;
@@ -512,7 +501,7 @@
 
 	if (flags & IPC_FLAG_BLOCKING)
-		exch = loc_exchange_begin_blocking(INTERFACE_LOC_CONSUMER);
+		exch = loc_exchange_begin_blocking();
 	else {
-		exch = loc_exchange_begin(INTERFACE_LOC_CONSUMER);
+		exch = loc_exchange_begin();
 		if (exch == NULL)
 			return errno;
@@ -548,5 +537,5 @@
 loc_object_type_t loc_id_probe(service_id_t handle)
 {
-	async_exch_t *exch = loc_exchange_begin_blocking(INTERFACE_LOC_CONSUMER);
+	async_exch_t *exch = loc_exchange_begin_blocking();
 
 	sysarg_t type;
@@ -579,5 +568,5 @@
 int loc_null_create(void)
 {
-	async_exch_t *exch = loc_exchange_begin_blocking(INTERFACE_LOC_CONSUMER);
+	async_exch_t *exch = loc_exchange_begin_blocking();
 
 	sysarg_t null_id;
@@ -594,5 +583,5 @@
 void loc_null_destroy(int null_id)
 {
-	async_exch_t *exch = loc_exchange_begin_blocking(INTERFACE_LOC_CONSUMER);
+	async_exch_t *exch = loc_exchange_begin_blocking();
 	async_req_1_0(exch, LOC_NULL_DESTROY, (sysarg_t) null_id);
 	loc_exchange_end(exch);
@@ -611,16 +600,19 @@
 /** Add service to category.
  *
- * @param svc_id	Service ID
- * @param cat_id	Category ID
- * @return		EOK on success or an error code
- */
-errno_t loc_service_add_to_cat(service_id_t svc_id, service_id_t cat_id)
+ * @param srv    Server object
+ * @param svc_id Service ID
+ * @param cat_id Category ID
+ *
+ * @return EOK on success or an error code
+ */
+errno_t loc_service_add_to_cat(loc_srv_t *srv, service_id_t svc_id,
+    service_id_t cat_id)
 {
 	async_exch_t *exch;
 	errno_t retval;
 
-	exch = loc_exchange_begin_blocking(INTERFACE_LOC_SUPPLIER);
+	exch = async_exchange_begin(srv->sess);
 	retval = async_req_2_0(exch, LOC_SERVICE_ADD_TO_CAT, svc_id, cat_id);
-	loc_exchange_end(exch);
+	async_exchange_end(exch);
 
 	return retval;
@@ -641,5 +633,5 @@
 size_t loc_count_namespaces(void)
 {
-	async_exch_t *exch = loc_exchange_begin_blocking(INTERFACE_LOC_CONSUMER);
+	async_exch_t *exch = loc_exchange_begin_blocking();
 	size_t size = loc_count_namespaces_internal(exch);
 	loc_exchange_end(exch);
@@ -650,5 +642,5 @@
 size_t loc_count_services(service_id_t ns_handle)
 {
-	async_exch_t *exch = loc_exchange_begin_blocking(INTERFACE_LOC_CONSUMER);
+	async_exch_t *exch = loc_exchange_begin_blocking();
 	size_t size = loc_count_services_internal(exch, ns_handle);
 	loc_exchange_end(exch);
@@ -661,5 +653,5 @@
 	/* Loop until read is succesful */
 	while (true) {
-		async_exch_t *exch = loc_exchange_begin_blocking(INTERFACE_LOC_CONSUMER);
+		async_exch_t *exch = loc_exchange_begin_blocking();
 		size_t count = loc_count_namespaces_internal(exch);
 		loc_exchange_end(exch);
@@ -672,5 +664,5 @@
 			return 0;
 
-		exch = loc_exchange_begin(INTERFACE_LOC_CONSUMER);
+		exch = loc_exchange_begin();
 
 		ipc_call_t answer;
@@ -710,5 +702,5 @@
 	/* Loop until read is succesful */
 	while (true) {
-		async_exch_t *exch = loc_exchange_begin_blocking(INTERFACE_LOC_CONSUMER);
+		async_exch_t *exch = loc_exchange_begin_blocking();
 		size_t count = loc_count_services_internal(exch, ns_handle);
 		loc_exchange_end(exch);
@@ -721,5 +713,5 @@
 			return 0;
 
-		exch = loc_exchange_begin(INTERFACE_LOC_CONSUMER);
+		exch = loc_exchange_begin();
 
 		ipc_call_t answer;
@@ -758,5 +750,5 @@
     sysarg_t *id_buf, size_t buf_size, size_t *act_size)
 {
-	async_exch_t *exch = loc_exchange_begin_blocking(INTERFACE_LOC_CONSUMER);
+	async_exch_t *exch = loc_exchange_begin_blocking();
 
 	ipc_call_t answer;
Index: uspace/lib/c/generic/malloc.c
===================================================================
--- uspace/lib/c/generic/malloc.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/generic/malloc.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -34,5 +34,5 @@
  */
 
-#include <malloc.h>
+#include <stdlib.h>
 #include <stdalign.h>
 #include <stdbool.h>
@@ -47,4 +47,5 @@
 #include <stdlib.h>
 #include <adt/gcdlcm.h>
+#include <malloc.h>
 
 #include "private/malloc.h"
@@ -773,24 +774,4 @@
 }
 
-/** Allocate memory by number of elements
- *
- * @param nmemb Number of members to allocate.
- * @param size  Size of one member in bytes.
- *
- * @return Allocated memory or NULL.
- *
- */
-void *calloc(const size_t nmemb, const size_t size)
-{
-	// FIXME: Check for overflow
-
-	void *block = malloc(nmemb * size);
-	if (block == NULL)
-		return NULL;
-
-	memset(block, 0, nmemb * size);
-	return block;
-}
-
 /** Allocate memory
  *
Index: pace/lib/c/generic/mem.c
===================================================================
--- uspace/lib/c/generic/mem.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,281 +1,0 @@
-/*
- * Copyright (c) 2005 Martin Decky
- * Copyright (c) 2018 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#include <mem.h>
-#include <stdlib.h>
-#include <stddef.h>
-#include <stdint.h>
-#include "private/cc.h"
-
-/** Fill memory block with a constant value. */
-ATTRIBUTE_OPTIMIZE_NO_TLDP
-    void *memset(void *dest, int b, size_t n)
-{
-	char *pb;
-	unsigned long *pw;
-	size_t word_size;
-	size_t n_words;
-
-	unsigned long pattern;
-	size_t i;
-	size_t fill;
-
-	/* Fill initial segment. */
-	word_size = sizeof(unsigned long);
-	fill = word_size - ((uintptr_t) dest & (word_size - 1));
-	if (fill > n)
-		fill = n;
-
-	pb = dest;
-
-	i = fill;
-	while (i-- != 0)
-		*pb++ = b;
-
-	/* Compute remaining size. */
-	n -= fill;
-	if (n == 0)
-		return dest;
-
-	n_words = n / word_size;
-	n = n % word_size;
-	pw = (unsigned long *) pb;
-
-	/* Create word-sized pattern for aligned segment. */
-	pattern = 0;
-	i = word_size;
-	while (i-- != 0)
-		pattern = (pattern << 8) | (uint8_t) b;
-
-	/* Fill aligned segment. */
-	i = n_words;
-	while (i-- != 0)
-		*pw++ = pattern;
-
-	pb = (char *) pw;
-
-	/* Fill final segment. */
-	i = n;
-	while (i-- != 0)
-		*pb++ = b;
-
-	return dest;
-}
-
-struct along {
-	unsigned long n;
-} __attribute__((packed));
-
-static void *unaligned_memcpy(void *dst, const void *src, size_t n)
-{
-	size_t i, j;
-	struct along *adst = dst;
-	const struct along *asrc = src;
-
-	for (i = 0; i < n / sizeof(unsigned long); i++)
-		adst[i].n = asrc[i].n;
-
-	for (j = 0; j < n % sizeof(unsigned long); j++)
-		((unsigned char *) (((unsigned long *) dst) + i))[j] =
-		    ((unsigned char *) (((unsigned long *) src) + i))[j];
-
-	return (char *) dst;
-}
-
-/** Copy memory block. */
-ATTRIBUTE_OPTIMIZE_NO_TLDP
-    void *memcpy(void *dst, const void *src, size_t n)
-{
-	size_t i;
-	size_t mod, fill;
-	size_t word_size;
-	size_t n_words;
-
-	const unsigned long *srcw;
-	unsigned long *dstw;
-	const uint8_t *srcb;
-	uint8_t *dstb;
-
-	word_size = sizeof(unsigned long);
-
-	/*
-	 * Are source and destination addresses congruent modulo word_size?
-	 * If not, use unaligned_memcpy().
-	 */
-
-	if (((uintptr_t) dst & (word_size - 1)) !=
-	    ((uintptr_t) src & (word_size - 1)))
-		return unaligned_memcpy(dst, src, n);
-
-	/*
-	 * mod is the address modulo word size. fill is the length of the
-	 * initial buffer segment before the first word boundary.
-	 * If the buffer is very short, use unaligned_memcpy(), too.
-	 */
-
-	mod = (uintptr_t) dst & (word_size - 1);
-	fill = word_size - mod;
-	if (fill > n)
-		fill = n;
-
-	/* Copy the initial segment. */
-
-	srcb = src;
-	dstb = dst;
-
-	i = fill;
-	while (i-- != 0)
-		*dstb++ = *srcb++;
-
-	/* Compute remaining length. */
-
-	n -= fill;
-	if (n == 0)
-		return dst;
-
-	/* Pointers to aligned segment. */
-
-	dstw = (unsigned long *) dstb;
-	srcw = (const unsigned long *) srcb;
-
-	n_words = n / word_size;	/* Number of whole words to copy. */
-	n -= n_words * word_size;	/* Remaining bytes at the end. */
-
-	/* "Fast" copy. */
-	i = n_words;
-	while (i-- != 0)
-		*dstw++ = *srcw++;
-
-	/*
-	 * Copy the rest.
-	 */
-
-	srcb = (const uint8_t *) srcw;
-	dstb = (uint8_t *) dstw;
-
-	i = n;
-	while (i-- != 0)
-		*dstb++ = *srcb++;
-
-	return dst;
-}
-
-/** Move memory block with possible overlapping. */
-void *memmove(void *dst, const void *src, size_t n)
-{
-	const uint8_t *sp;
-	uint8_t *dp;
-
-	/* Nothing to do? */
-	if (src == dst)
-		return dst;
-
-	/* Non-overlapping? */
-	if (dst >= src + n || src >= dst + n) {
-		return memcpy(dst, src, n);
-	}
-
-	/* Which direction? */
-	if (src > dst) {
-		/* Forwards. */
-		sp = src;
-		dp = dst;
-
-		while (n-- != 0)
-			*dp++ = *sp++;
-	} else {
-		/* Backwards. */
-		sp = src + (n - 1);
-		dp = dst + (n - 1);
-
-		while (n-- != 0)
-			*dp-- = *sp--;
-	}
-
-	return dst;
-}
-
-/** Compare two memory areas.
- *
- * @param s1  Pointer to the first area to compare.
- * @param s2  Pointer to the second area to compare.
- * @param len Size of the areas in bytes.
- *
- * @return Zero if areas have the same contents. If they differ,
- *	   the sign of the result is the same as the sign of the
- *	   difference of the first pair of different bytes.
- *
- */
-int memcmp(const void *s1, const void *s2, size_t len)
-{
-	uint8_t *u1 = (uint8_t *) s1;
-	uint8_t *u2 = (uint8_t *) s2;
-	size_t i;
-
-	for (i = 0; i < len; i++) {
-		if (*u1 != *u2)
-			return (int)(*u1) - (int)(*u2);
-		++u1;
-		++u2;
-	}
-
-	return 0;
-}
-
-/** Search memory area.
- *
- * @param s Memory area
- * @param c Character (byte) to search for
- * @param n Size of memory area in bytes
- *
- * @return Pointer to the first occurrence of @a c in the first @a n
- *         bytes of @a s or @c NULL if not found.
- */
-void *memchr(const void *s, int c, size_t n)
-{
-	uint8_t *u = (uint8_t *) s;
-	unsigned char uc = (unsigned char) c;
-	size_t i;
-
-	for (i = 0; i < n; i++) {
-		if (u[i] == uc)
-			return (void *) &u[i];
-	}
-
-	return NULL;
-}
-
-/** @}
- */
Index: pace/lib/c/generic/qsort.c
===================================================================
--- uspace/lib/c/generic/qsort.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,214 +1,0 @@
-/*
- * Copyright (c) 2017 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-
-/**
- * @file
- * @brief Quicksort.
- */
-
-#include <qsort.h>
-#include <stdbool.h>
-#include <stddef.h>
-
-/** Quicksort spec */
-typedef struct {
-	void *base;
-	size_t nmemb;
-	size_t size;
-	int (*compar)(const void *, const void *, void *);
-	void *arg;
-} qs_spec_t;
-
-/** Comparison function wrapper.
- *
- * Performs qsort_r comparison using qsort comparison function
- *
- * @param a First element
- * @param b Second element
- * @param arg qsort comparison function
- */
-static int compar_wrap(const void *a, const void *b, void *arg)
-{
-	int (*compar)(const void *, const void *) =
-	    (int (*)(const void *, const void *))arg;
-
-	return compar(a, b);
-}
-
-/** Determine if one element is less-than another element.
- *
- * @param qs Quicksort spec
- * @param i First element index
- * @param j Second element index
- */
-static bool elem_lt(qs_spec_t *qs, size_t i, size_t j)
-{
-	const void *a;
-	const void *b;
-	int r;
-
-	a = qs->base + i * qs->size;
-	b = qs->base + j * qs->size;
-
-	r = qs->compar(a, b, qs->arg);
-
-	return r < 0;
-}
-
-/** Swap two elements.
- *
- * @param qs Quicksort spec
- * @param i First element index
- * @param j Second element index
- */
-static void elem_swap(qs_spec_t *qs, size_t i, size_t j)
-{
-	char *a;
-	char *b;
-	char t;
-	size_t k;
-
-	a = qs->base + i * qs->size;
-	b = qs->base + j * qs->size;
-
-	for (k = 0; k < qs->size; k++) {
-		t = a[k];
-		a[k] = b[k];
-		b[k] = t;
-	}
-}
-
-/** Partition a range of indices.
- *
- * @param qs Quicksort spec
- * @param lo Lower bound (inclusive)
- * @param hi Upper bound (inclusive)
- * @return Pivot index
- */
-static size_t partition(qs_spec_t *qs, size_t lo, size_t hi)
-{
-	size_t pivot;
-	size_t i, j;
-
-	pivot = lo + (hi - lo) / 2;
-	i = lo;
-	j = hi;
-	while (true) {
-		while (elem_lt(qs, i, pivot))
-			++i;
-		while (elem_lt(qs, pivot, j))
-			--j;
-
-		if (i >= j)
-			return j;
-
-		elem_swap(qs, i, j);
-
-		/* Need to follow pivot movement */
-		if (i == pivot)
-			pivot = j;
-		else if (j == pivot)
-			pivot = i;
-
-		i++;
-		j--;
-	}
-}
-
-/** Sort a range of indices.
- *
- * @param qs Quicksort spec
- * @param lo Lower bound (inclusive)
- * @param hi Upper bound (inclusive)
- */
-static void quicksort(qs_spec_t *qs, size_t lo, size_t hi)
-{
-	size_t p;
-
-	if (lo < hi) {
-		p = partition(qs, lo, hi);
-		quicksort(qs, lo, p);
-		quicksort(qs, p + 1, hi);
-	}
-}
-
-/** Quicksort.
- *
- * @param base Array to sort
- * @param nmemb Number of array members
- * @param size Size of member in bytes
- * @param compar Comparison function
- */
-void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *,
-    const void *))
-{
-	qs_spec_t qs;
-
-	if (nmemb == 0)
-		return;
-
-	qs.base = base;
-	qs.nmemb = nmemb;
-	qs.size = size;
-	qs.compar = compar_wrap;
-	qs.arg = compar;
-
-	quicksort(&qs, 0, nmemb - 1);
-}
-
-/** Quicksort with extra argument to comparison function.
- *
- * @param base Array to sort
- * @param nmemb Number of array members
- * @param size Size of member in bytes
- * @param compar Comparison function
- * @param arg Argument to comparison function
- */
-void qsort_r(void *base, size_t nmemb, size_t size, int (*compar)(const void *,
-    const void *, void *), void *arg)
-{
-	qs_spec_t qs;
-
-	if (nmemb == 0)
-		return;
-
-	qs.base = base;
-	qs.nmemb = nmemb;
-	qs.size = size;
-	qs.compar = compar;
-	qs.arg = arg;
-
-	quicksort(&qs, 0, nmemb - 1);
-}
-
-/** @}
- */
Index: uspace/lib/c/generic/rndgen.c
===================================================================
--- uspace/lib/c/generic/rndgen.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/generic/rndgen.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -92,4 +92,30 @@
 }
 
+/** Generate random 16-bit integer.
+ *
+ * @param rndgen Random number generator
+ * @param rw Place to store random 16-bit integer
+ * @return EOK on success or error code
+ */
+errno_t rndgen_uint16(rndgen_t *rndgen, uint16_t *rw)
+{
+	int i;
+	uint8_t b;
+	uint16_t w;
+	errno_t rc;
+
+	w = 0;
+	for (i = 0; i < 2; i++) {
+		rc = rndgen_uint8(rndgen, &b);
+		if (rc != EOK)
+			return rc;
+
+		w = (w << 8) | b;
+	}
+
+	*rw = w;
+	return EOK;
+}
+
 /** Generate random 32-bit integer.
  *
Index: uspace/lib/c/generic/rtld/module.c
===================================================================
--- uspace/lib/c/generic/rtld/module.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/generic/rtld/module.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2008 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -343,9 +343,21 @@
 {
 	list_foreach(rtld->modules, modules_link, module_t, m) {
-		/* Skip rtld module, since it has already been processed */
-		if (m != &rtld->rtld) {
+		/*
+		 * Skip rtld module, since it has already been processed.
+		 * Skip start / main program -- leave it for later
+		 */
+		if (m != &rtld->rtld && m != start) {
 			module_process_relocs(m);
 		}
 	}
+
+	/*
+	 * Now that shared libraries have been processed and their variables
+	 * are thus initialized, we can process the main program,
+	 * which may contain COPY relocations that copy value from shared
+	 * library variables to instances of those variables defined
+	 * in the main program.
+	 */
+	module_process_relocs(start);
 }
 
Index: uspace/lib/c/generic/rtld/rtld.c
===================================================================
--- uspace/lib/c/generic/rtld/rtld.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/generic/rtld/rtld.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -187,5 +187,7 @@
 		assert(((uintptr_t) dtv[m->id]) % m->tls_align == 0);
 
-		memcpy(dtv[m->id], m->tdata, m->tdata_size);
+		if (m->tdata)
+			memcpy(dtv[m->id], m->tdata, m->tdata_size);
+
 		memset(dtv[m->id] + m->tdata_size, 0, m->tbss_size);
 	}
Index: uspace/lib/c/generic/rtld/symbol.c
===================================================================
--- uspace/lib/c/generic/rtld/symbol.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/generic/rtld/symbol.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -135,8 +135,21 @@
 	modules_untag(start->rtld);
 
-	/* Insert root (the program) into the queue and tag it */
+	/*
+	 * Insert root (the program) into the queue and tag it.
+	 *
+	 * We disable the dangling-pointer warning because the compiler incorrectly
+	 * assumes that we leak local address (queue) to a parent scope (to start
+	 * argument). However, we always empty the list so the pointer cannot
+	 * actually escape. Probably the compiler can never statically analyze that
+	 * correctly.
+	 */
 	list_initialize(&queue);
 	start->bfs_tag = true;
+#pragma GCC diagnostic push
+#if defined(__GNUC__) && (__GNUC__ >= 12)
+#pragma GCC diagnostic ignored "-Wdangling-pointer"
+#endif
 	list_append(&start->queue_link, &queue);
+#pragma GCC diagnostic pop
 
 	/* If the symbol is found, it will be stored in 'sym' */
Index: pace/lib/c/generic/str.c
===================================================================
--- uspace/lib/c/generic/str.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,1548 +1,0 @@
-/*
- * Copyright (c) 2001-2004 Jakub Jermar
- * Copyright (c) 2005 Martin Decky
- * Copyright (c) 2008 Jiri Svoboda
- * Copyright (c) 2011 Martin Sucha
- * Copyright (c) 2011 Oleg Romanenko
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-
-/**
- * @file
- * @brief String functions.
- *
- * Strings and characters use the Universal Character Set (UCS). The standard
- * strings, called just strings are encoded in UTF-8. Wide strings (encoded
- * in UTF-32) are supported to a limited degree. A single character is
- * represented as char32_t.@n
- *
- * Overview of the terminology:@n
- *
- *  Term                  Meaning
- *  --------------------  ----------------------------------------------------
- *  byte                  8 bits stored in uint8_t (unsigned 8 bit integer)
- *
- *  character             UTF-32 encoded Unicode character, stored in char32_t
- *                        (unsigned 32 bit integer), code points 0 .. 1114111
- *                        are valid
- *
- *  ASCII character       7 bit encoded ASCII character, stored in char
- *                        (usually signed 8 bit integer), code points 0 .. 127
- *                        are valid
- *
- *  string                UTF-8 encoded NULL-terminated Unicode string, char *
- *
- *  wide string           UTF-32 encoded NULL-terminated Unicode string,
- *                        char32_t *
- *
- *  [wide] string size    number of BYTES in a [wide] string (excluding
- *                        the NULL-terminator), size_t
- *
- *  [wide] string length  number of CHARACTERS in a [wide] string (excluding
- *                        the NULL-terminator), size_t
- *
- *  [wide] string width   number of display cells on a monospace display taken
- *                        by a [wide] string, size_t
- *
- *
- * Overview of string metrics:@n
- *
- *  Metric  Abbrev.  Type     Meaning
- *  ------  ------   ------   -------------------------------------------------
- *  size    n        size_t   number of BYTES in a string (excluding the
- *                            NULL-terminator)
- *
- *  length  l        size_t   number of CHARACTERS in a string (excluding the
- *                            null terminator)
- *
- *  width  w         size_t   number of display cells on a monospace display
- *                            taken by a string
- *
- *
- * Function naming prefixes:@n
- *
- *  chr_    operate on characters
- *  ascii_  operate on ASCII characters
- *  str_    operate on strings
- *  wstr_   operate on wide strings
- *
- *  [w]str_[n|l|w]  operate on a prefix limited by size, length
- *                  or width
- *
- *
- * A specific character inside a [wide] string can be referred to by:@n
- *
- *  pointer (char *, char32_t *)
- *  byte offset (size_t)
- *  character index (size_t)
- *
- */
-
-#include <str.h>
-
-#include <assert.h>
-#include <ctype.h>
-#include <errno.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <stdlib.h>
-
-#include <align.h>
-#include <mem.h>
-
-/** Byte mask consisting of lowest @n bits (out of 8) */
-#define LO_MASK_8(n)  ((uint8_t) ((1 << (n)) - 1))
-
-/** Byte mask consisting of lowest @n bits (out of 32) */
-#define LO_MASK_32(n)  ((uint32_t) ((1 << (n)) - 1))
-
-/** Byte mask consisting of highest @n bits (out of 8) */
-#define HI_MASK_8(n)  (~LO_MASK_8(8 - (n)))
-
-/** Number of data bits in a UTF-8 continuation byte */
-#define CONT_BITS  6
-
-/** Decode a single character from a string.
- *
- * Decode a single character from a string of size @a size. Decoding starts
- * at @a offset and this offset is moved to the beginning of the next
- * character. In case of decoding error, offset generally advances at least
- * by one. However, offset is never moved beyond size.
- *
- * @param str    String (not necessarily NULL-terminated).
- * @param offset Byte offset in string where to start decoding.
- * @param size   Size of the string (in bytes).
- *
- * @return Value of decoded character, U_SPECIAL on decoding error or
- *         NULL if attempt to decode beyond @a size.
- *
- */
-char32_t str_decode(const char *str, size_t *offset, size_t size)
-{
-	if (*offset + 1 > size)
-		return 0;
-
-	/* First byte read from string */
-	uint8_t b0 = (uint8_t) str[(*offset)++];
-
-	/* Determine code length */
-
-	unsigned int b0_bits;  /* Data bits in first byte */
-	unsigned int cbytes;   /* Number of continuation bytes */
-
-	if ((b0 & 0x80) == 0) {
-		/* 0xxxxxxx (Plain ASCII) */
-		b0_bits = 7;
-		cbytes = 0;
-	} else if ((b0 & 0xe0) == 0xc0) {
-		/* 110xxxxx 10xxxxxx */
-		b0_bits = 5;
-		cbytes = 1;
-	} else if ((b0 & 0xf0) == 0xe0) {
-		/* 1110xxxx 10xxxxxx 10xxxxxx */
-		b0_bits = 4;
-		cbytes = 2;
-	} else if ((b0 & 0xf8) == 0xf0) {
-		/* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
-		b0_bits = 3;
-		cbytes = 3;
-	} else {
-		/* 10xxxxxx -- unexpected continuation byte */
-		return U_SPECIAL;
-	}
-
-	if (*offset + cbytes > size)
-		return U_SPECIAL;
-
-	char32_t ch = b0 & LO_MASK_8(b0_bits);
-
-	/* Decode continuation bytes */
-	while (cbytes > 0) {
-		uint8_t b = (uint8_t) str[(*offset)++];
-
-		/* Must be 10xxxxxx */
-		if ((b & 0xc0) != 0x80)
-			return U_SPECIAL;
-
-		/* Shift data bits to ch */
-		ch = (ch << CONT_BITS) | (char32_t) (b & LO_MASK_8(CONT_BITS));
-		cbytes--;
-	}
-
-	return ch;
-}
-
-/** Decode a single character from a string to the left.
- *
- * Decode a single character from a string of size @a size. Decoding starts
- * at @a offset and this offset is moved to the beginning of the previous
- * character. In case of decoding error, offset generally decreases at least
- * by one. However, offset is never moved before 0.
- *
- * @param str    String (not necessarily NULL-terminated).
- * @param offset Byte offset in string where to start decoding.
- * @param size   Size of the string (in bytes).
- *
- * @return Value of decoded character, U_SPECIAL on decoding error or
- *         NULL if attempt to decode beyond @a start of str.
- *
- */
-char32_t str_decode_reverse(const char *str, size_t *offset, size_t size)
-{
-	if (*offset == 0)
-		return 0;
-
-	size_t processed = 0;
-	/* Continue while continuation bytes found */
-	while (*offset > 0 && processed < 4) {
-		uint8_t b = (uint8_t) str[--(*offset)];
-
-		if (processed == 0 && (b & 0x80) == 0) {
-			/* 0xxxxxxx (Plain ASCII) */
-			return b & 0x7f;
-		} else if ((b & 0xe0) == 0xc0 || (b & 0xf0) == 0xe0 ||
-		    (b & 0xf8) == 0xf0) {
-			/* Start byte */
-			size_t start_offset = *offset;
-			return str_decode(str, &start_offset, size);
-		} else if ((b & 0xc0) != 0x80) {
-			/* Not a continuation byte */
-			return U_SPECIAL;
-		}
-		processed++;
-	}
-	/* Too many continuation bytes */
-	return U_SPECIAL;
-}
-
-/** Encode a single character to string representation.
- *
- * Encode a single character to string representation (i.e. UTF-8) and store
- * it into a buffer at @a offset. Encoding starts at @a offset and this offset
- * is moved to the position where the next character can be written to.
- *
- * @param ch     Input character.
- * @param str    Output buffer.
- * @param offset Byte offset where to start writing.
- * @param size   Size of the output buffer (in bytes).
- *
- * @return EOK if the character was encoded successfully, EOVERFLOW if there
- *         was not enough space in the output buffer or EINVAL if the character
- *         code was invalid.
- */
-errno_t chr_encode(const char32_t ch, char *str, size_t *offset, size_t size)
-{
-	if (*offset >= size)
-		return EOVERFLOW;
-
-	if (!chr_check(ch))
-		return EINVAL;
-
-	/*
-	 * Unsigned version of ch (bit operations should only be done
-	 * on unsigned types).
-	 */
-	uint32_t cc = (uint32_t) ch;
-
-	/* Determine how many continuation bytes are needed */
-
-	unsigned int b0_bits;  /* Data bits in first byte */
-	unsigned int cbytes;   /* Number of continuation bytes */
-
-	if ((cc & ~LO_MASK_32(7)) == 0) {
-		b0_bits = 7;
-		cbytes = 0;
-	} else if ((cc & ~LO_MASK_32(11)) == 0) {
-		b0_bits = 5;
-		cbytes = 1;
-	} else if ((cc & ~LO_MASK_32(16)) == 0) {
-		b0_bits = 4;
-		cbytes = 2;
-	} else if ((cc & ~LO_MASK_32(21)) == 0) {
-		b0_bits = 3;
-		cbytes = 3;
-	} else {
-		/* Codes longer than 21 bits are not supported */
-		return EINVAL;
-	}
-
-	/* Check for available space in buffer */
-	if (*offset + cbytes >= size)
-		return EOVERFLOW;
-
-	/* Encode continuation bytes */
-	unsigned int i;
-	for (i = cbytes; i > 0; i--) {
-		str[*offset + i] = 0x80 | (cc & LO_MASK_32(CONT_BITS));
-		cc = cc >> CONT_BITS;
-	}
-
-	/* Encode first byte */
-	str[*offset] = (cc & LO_MASK_32(b0_bits)) | HI_MASK_8(8 - b0_bits - 1);
-
-	/* Advance offset */
-	*offset += cbytes + 1;
-
-	return EOK;
-}
-
-/** Get size of string.
- *
- * Get the number of bytes which are used by the string @a str (excluding the
- * NULL-terminator).
- *
- * @param str String to consider.
- *
- * @return Number of bytes used by the string
- *
- */
-size_t str_size(const char *str)
-{
-	size_t size = 0;
-
-	while (*str++ != 0)
-		size++;
-
-	return size;
-}
-
-/** Get size of wide string.
- *
- * Get the number of bytes which are used by the wide string @a str (excluding the
- * NULL-terminator).
- *
- * @param str Wide string to consider.
- *
- * @return Number of bytes used by the wide string
- *
- */
-size_t wstr_size(const char32_t *str)
-{
-	return (wstr_length(str) * sizeof(char32_t));
-}
-
-/** Get size of string with length limit.
- *
- * Get the number of bytes which are used by up to @a max_len first
- * characters in the string @a str. If @a max_len is greater than
- * the length of @a str, the entire string is measured (excluding the
- * NULL-terminator).
- *
- * @param str     String to consider.
- * @param max_len Maximum number of characters to measure.
- *
- * @return Number of bytes used by the characters.
- *
- */
-size_t str_lsize(const char *str, size_t max_len)
-{
-	size_t len = 0;
-	size_t offset = 0;
-
-	while (len < max_len) {
-		if (str_decode(str, &offset, STR_NO_LIMIT) == 0)
-			break;
-
-		len++;
-	}
-
-	return offset;
-}
-
-/** Get size of string with size limit.
- *
- * Get the number of bytes which are used by the string @a str
- * (excluding the NULL-terminator), but no more than @max_size bytes.
- *
- * @param str      String to consider.
- * @param max_size Maximum number of bytes to measure.
- *
- * @return Number of bytes used by the string
- *
- */
-size_t str_nsize(const char *str, size_t max_size)
-{
-	size_t size = 0;
-
-	while ((*str++ != 0) && (size < max_size))
-		size++;
-
-	return size;
-}
-
-/** Get size of wide string with size limit.
- *
- * Get the number of bytes which are used by the wide string @a str
- * (excluding the NULL-terminator), but no more than @max_size bytes.
- *
- * @param str      Wide string to consider.
- * @param max_size Maximum number of bytes to measure.
- *
- * @return Number of bytes used by the wide string
- *
- */
-size_t wstr_nsize(const char32_t *str, size_t max_size)
-{
-	return (wstr_nlength(str, max_size) * sizeof(char32_t));
-}
-
-/** Get size of wide string with length limit.
- *
- * Get the number of bytes which are used by up to @a max_len first
- * wide characters in the wide string @a str. If @a max_len is greater than
- * the length of @a str, the entire wide string is measured (excluding the
- * NULL-terminator).
- *
- * @param str     Wide string to consider.
- * @param max_len Maximum number of wide characters to measure.
- *
- * @return Number of bytes used by the wide characters.
- *
- */
-size_t wstr_lsize(const char32_t *str, size_t max_len)
-{
-	return (wstr_nlength(str, max_len * sizeof(char32_t)) * sizeof(char32_t));
-}
-
-/** Get number of characters in a string.
- *
- * @param str NULL-terminated string.
- *
- * @return Number of characters in string.
- *
- */
-size_t str_length(const char *str)
-{
-	size_t len = 0;
-	size_t offset = 0;
-
-	while (str_decode(str, &offset, STR_NO_LIMIT) != 0)
-		len++;
-
-	return len;
-}
-
-/** Get number of characters in a wide string.
- *
- * @param str NULL-terminated wide string.
- *
- * @return Number of characters in @a str.
- *
- */
-size_t wstr_length(const char32_t *wstr)
-{
-	size_t len = 0;
-
-	while (*wstr++ != 0)
-		len++;
-
-	return len;
-}
-
-/** Get number of characters in a string with size limit.
- *
- * @param str  NULL-terminated string.
- * @param size Maximum number of bytes to consider.
- *
- * @return Number of characters in string.
- *
- */
-size_t str_nlength(const char *str, size_t size)
-{
-	size_t len = 0;
-	size_t offset = 0;
-
-	while (str_decode(str, &offset, size) != 0)
-		len++;
-
-	return len;
-}
-
-/** Get number of characters in a string with size limit.
- *
- * @param str  NULL-terminated string.
- * @param size Maximum number of bytes to consider.
- *
- * @return Number of characters in string.
- *
- */
-size_t wstr_nlength(const char32_t *str, size_t size)
-{
-	size_t len = 0;
-	size_t limit = ALIGN_DOWN(size, sizeof(char32_t));
-	size_t offset = 0;
-
-	while ((offset < limit) && (*str++ != 0)) {
-		len++;
-		offset += sizeof(char32_t);
-	}
-
-	return len;
-}
-
-/** Get character display width on a character cell display.
- *
- * @param ch	Character
- * @return	Width of character in cells.
- */
-size_t chr_width(char32_t ch)
-{
-	return 1;
-}
-
-/** Get string display width on a character cell display.
- *
- * @param str	String
- * @return	Width of string in cells.
- */
-size_t str_width(const char *str)
-{
-	size_t width = 0;
-	size_t offset = 0;
-	char32_t ch;
-
-	while ((ch = str_decode(str, &offset, STR_NO_LIMIT)) != 0)
-		width += chr_width(ch);
-
-	return width;
-}
-
-/** Check whether character is plain ASCII.
- *
- * @return True if character is plain ASCII.
- *
- */
-bool ascii_check(char32_t ch)
-{
-	if (ch <= 127)
-		return true;
-
-	return false;
-}
-
-/** Check whether character is valid
- *
- * @return True if character is a valid Unicode code point.
- *
- */
-bool chr_check(char32_t ch)
-{
-	if (ch <= 1114111)
-		return true;
-
-	return false;
-}
-
-/** Compare two NULL terminated strings.
- *
- * Do a char-by-char comparison of two NULL-terminated strings.
- * The strings are considered equal iff their length is equal
- * and both strings consist of the same sequence of characters.
- *
- * A string S1 is less than another string S2 if it has a character with
- * lower value at the first character position where the strings differ.
- * If the strings differ in length, the shorter one is treated as if
- * padded by characters with a value of zero.
- *
- * @param s1 First string to compare.
- * @param s2 Second string to compare.
- *
- * @return 0 if the strings are equal, -1 if the first is less than the second,
- *         1 if the second is less than the first.
- *
- */
-int str_cmp(const char *s1, const char *s2)
-{
-	char32_t c1 = 0;
-	char32_t c2 = 0;
-
-	size_t off1 = 0;
-	size_t off2 = 0;
-
-	while (true) {
-		c1 = str_decode(s1, &off1, STR_NO_LIMIT);
-		c2 = str_decode(s2, &off2, STR_NO_LIMIT);
-
-		if (c1 < c2)
-			return -1;
-
-		if (c1 > c2)
-			return 1;
-
-		if (c1 == 0 || c2 == 0)
-			break;
-	}
-
-	return 0;
-}
-
-/** Compare two NULL terminated strings with length limit.
- *
- * Do a char-by-char comparison of two NULL-terminated strings.
- * The strings are considered equal iff
- * min(str_length(s1), max_len) == min(str_length(s2), max_len)
- * and both strings consist of the same sequence of characters,
- * up to max_len characters.
- *
- * A string S1 is less than another string S2 if it has a character with
- * lower value at the first character position where the strings differ.
- * If the strings differ in length, the shorter one is treated as if
- * padded by characters with a value of zero. Only the first max_len
- * characters are considered.
- *
- * @param s1      First string to compare.
- * @param s2      Second string to compare.
- * @param max_len Maximum number of characters to consider.
- *
- * @return 0 if the strings are equal, -1 if the first is less than the second,
- *         1 if the second is less than the first.
- *
- */
-int str_lcmp(const char *s1, const char *s2, size_t max_len)
-{
-	char32_t c1 = 0;
-	char32_t c2 = 0;
-
-	size_t off1 = 0;
-	size_t off2 = 0;
-
-	size_t len = 0;
-
-	while (true) {
-		if (len >= max_len)
-			break;
-
-		c1 = str_decode(s1, &off1, STR_NO_LIMIT);
-		c2 = str_decode(s2, &off2, STR_NO_LIMIT);
-
-		if (c1 < c2)
-			return -1;
-
-		if (c1 > c2)
-			return 1;
-
-		if (c1 == 0 || c2 == 0)
-			break;
-
-		++len;
-	}
-
-	return 0;
-
-}
-
-/** Compare two NULL terminated strings in case-insensitive manner.
- *
- * Do a char-by-char comparison of two NULL-terminated strings.
- * The strings are considered equal iff their length is equal
- * and both strings consist of the same sequence of characters
- * when converted to lower case.
- *
- * A string S1 is less than another string S2 if it has a character with
- * lower value at the first character position where the strings differ.
- * If the strings differ in length, the shorter one is treated as if
- * padded by characters with a value of zero.
- *
- * @param s1 First string to compare.
- * @param s2 Second string to compare.
- *
- * @return 0 if the strings are equal, -1 if the first is less than the second,
- *         1 if the second is less than the first.
- *
- */
-int str_casecmp(const char *s1, const char *s2)
-{
-	char32_t c1 = 0;
-	char32_t c2 = 0;
-
-	size_t off1 = 0;
-	size_t off2 = 0;
-
-	while (true) {
-		c1 = tolower(str_decode(s1, &off1, STR_NO_LIMIT));
-		c2 = tolower(str_decode(s2, &off2, STR_NO_LIMIT));
-
-		if (c1 < c2)
-			return -1;
-
-		if (c1 > c2)
-			return 1;
-
-		if (c1 == 0 || c2 == 0)
-			break;
-	}
-
-	return 0;
-}
-
-/** Compare two NULL terminated strings with length limit in case-insensitive
- * manner.
- *
- * Do a char-by-char comparison of two NULL-terminated strings.
- * The strings are considered equal iff
- * min(str_length(s1), max_len) == min(str_length(s2), max_len)
- * and both strings consist of the same sequence of characters,
- * up to max_len characters.
- *
- * A string S1 is less than another string S2 if it has a character with
- * lower value at the first character position where the strings differ.
- * If the strings differ in length, the shorter one is treated as if
- * padded by characters with a value of zero. Only the first max_len
- * characters are considered.
- *
- * @param s1      First string to compare.
- * @param s2      Second string to compare.
- * @param max_len Maximum number of characters to consider.
- *
- * @return 0 if the strings are equal, -1 if the first is less than the second,
- *         1 if the second is less than the first.
- *
- */
-int str_lcasecmp(const char *s1, const char *s2, size_t max_len)
-{
-	char32_t c1 = 0;
-	char32_t c2 = 0;
-
-	size_t off1 = 0;
-	size_t off2 = 0;
-
-	size_t len = 0;
-
-	while (true) {
-		if (len >= max_len)
-			break;
-
-		c1 = tolower(str_decode(s1, &off1, STR_NO_LIMIT));
-		c2 = tolower(str_decode(s2, &off2, STR_NO_LIMIT));
-
-		if (c1 < c2)
-			return -1;
-
-		if (c1 > c2)
-			return 1;
-
-		if (c1 == 0 || c2 == 0)
-			break;
-
-		++len;
-	}
-
-	return 0;
-
-}
-
-/** Test whether p is a prefix of s.
- *
- * Do a char-by-char comparison of two NULL-terminated strings
- * and determine if p is a prefix of s.
- *
- * @param s The string in which to look
- * @param p The string to check if it is a prefix of s
- *
- * @return true iff p is prefix of s else false
- *
- */
-bool str_test_prefix(const char *s, const char *p)
-{
-	char32_t c1 = 0;
-	char32_t c2 = 0;
-
-	size_t off1 = 0;
-	size_t off2 = 0;
-
-	while (true) {
-		c1 = str_decode(s, &off1, STR_NO_LIMIT);
-		c2 = str_decode(p, &off2, STR_NO_LIMIT);
-
-		if (c2 == 0)
-			return true;
-
-		if (c1 != c2)
-			return false;
-
-		if (c1 == 0)
-			break;
-	}
-
-	return false;
-}
-
-/** Get a string suffix.
- *
- * Return a string suffix defined by the prefix length.
- *
- * @param s             The string to get the suffix from.
- * @param prefix_length Number of prefix characters to ignore.
- *
- * @return String suffix.
- *
- */
-const char *str_suffix(const char *s, size_t prefix_length)
-{
-	size_t off = 0;
-	size_t i = 0;
-
-	while (true) {
-		str_decode(s, &off, STR_NO_LIMIT);
-		i++;
-
-		if (i >= prefix_length)
-			break;
-	}
-
-	return s + off;
-}
-
-/** Copy string.
- *
- * Copy source string @a src to destination buffer @a dest.
- * No more than @a size bytes are written. If the size of the output buffer
- * is at least one byte, the output string will always be well-formed, i.e.
- * null-terminated and containing only complete characters.
- *
- * @param dest  Destination buffer.
- * @param count Size of the destination buffer (must be > 0).
- * @param src   Source string.
- *
- */
-void str_cpy(char *dest, size_t size, const char *src)
-{
-	/* There must be space for a null terminator in the buffer. */
-	assert(size > 0);
-	assert(src != NULL);
-
-	size_t src_off = 0;
-	size_t dest_off = 0;
-
-	char32_t ch;
-	while ((ch = str_decode(src, &src_off, STR_NO_LIMIT)) != 0) {
-		if (chr_encode(ch, dest, &dest_off, size - 1) != EOK)
-			break;
-	}
-
-	dest[dest_off] = '\0';
-}
-
-/** Copy size-limited substring.
- *
- * Copy prefix of string @a src of max. size @a size to destination buffer
- * @a dest. No more than @a size bytes are written. The output string will
- * always be well-formed, i.e. null-terminated and containing only complete
- * characters.
- *
- * No more than @a n bytes are read from the input string, so it does not
- * have to be null-terminated.
- *
- * @param dest  Destination buffer.
- * @param count Size of the destination buffer (must be > 0).
- * @param src   Source string.
- * @param n     Maximum number of bytes to read from @a src.
- *
- */
-void str_ncpy(char *dest, size_t size, const char *src, size_t n)
-{
-	/* There must be space for a null terminator in the buffer. */
-	assert(size > 0);
-
-	size_t src_off = 0;
-	size_t dest_off = 0;
-
-	char32_t ch;
-	while ((ch = str_decode(src, &src_off, n)) != 0) {
-		if (chr_encode(ch, dest, &dest_off, size - 1) != EOK)
-			break;
-	}
-
-	dest[dest_off] = '\0';
-}
-
-/** Append one string to another.
- *
- * Append source string @a src to string in destination buffer @a dest.
- * Size of the destination buffer is @a dest. If the size of the output buffer
- * is at least one byte, the output string will always be well-formed, i.e.
- * null-terminated and containing only complete characters.
- *
- * @param dest   Destination buffer.
- * @param count Size of the destination buffer.
- * @param src   Source string.
- */
-void str_append(char *dest, size_t size, const char *src)
-{
-	size_t dstr_size;
-
-	dstr_size = str_size(dest);
-	if (dstr_size >= size)
-		return;
-
-	str_cpy(dest + dstr_size, size - dstr_size, src);
-}
-
-/** Convert space-padded ASCII to string.
- *
- * Common legacy text encoding in hardware is 7-bit ASCII fitted into
- * a fixed-width byte buffer (bit 7 always zero), right-padded with spaces
- * (ASCII 0x20). Convert space-padded ascii to string representation.
- *
- * If the text does not fit into the destination buffer, the function converts
- * as many characters as possible and returns EOVERFLOW.
- *
- * If the text contains non-ASCII bytes (with bit 7 set), the whole string is
- * converted anyway and invalid characters are replaced with question marks
- * (U_SPECIAL) and the function returns EIO.
- *
- * Regardless of return value upon return @a dest will always be well-formed.
- *
- * @param dest		Destination buffer
- * @param size		Size of destination buffer
- * @param src		Space-padded ASCII.
- * @param n		Size of the source buffer in bytes.
- *
- * @return		EOK on success, EOVERFLOW if the text does not fit
- *			destination buffer, EIO if the text contains
- *			non-ASCII bytes.
- */
-errno_t spascii_to_str(char *dest, size_t size, const uint8_t *src, size_t n)
-{
-	size_t sidx;
-	size_t didx;
-	size_t dlast;
-	uint8_t byte;
-	errno_t rc;
-	errno_t result;
-
-	/* There must be space for a null terminator in the buffer. */
-	assert(size > 0);
-	result = EOK;
-
-	didx = 0;
-	dlast = 0;
-	for (sidx = 0; sidx < n; ++sidx) {
-		byte = src[sidx];
-		if (!ascii_check(byte)) {
-			byte = U_SPECIAL;
-			result = EIO;
-		}
-
-		rc = chr_encode(byte, dest, &didx, size - 1);
-		if (rc != EOK) {
-			assert(rc == EOVERFLOW);
-			dest[didx] = '\0';
-			return rc;
-		}
-
-		/* Remember dest index after last non-empty character */
-		if (byte != 0x20)
-			dlast = didx;
-	}
-
-	/* Terminate string after last non-empty character */
-	dest[dlast] = '\0';
-	return result;
-}
-
-/** Convert wide string to string.
- *
- * Convert wide string @a src to string. The output is written to the buffer
- * specified by @a dest and @a size. @a size must be non-zero and the string
- * written will always be well-formed.
- *
- * @param dest	Destination buffer.
- * @param size	Size of the destination buffer.
- * @param src	Source wide string.
- */
-void wstr_to_str(char *dest, size_t size, const char32_t *src)
-{
-	char32_t ch;
-	size_t src_idx;
-	size_t dest_off;
-
-	/* There must be space for a null terminator in the buffer. */
-	assert(size > 0);
-
-	src_idx = 0;
-	dest_off = 0;
-
-	while ((ch = src[src_idx++]) != 0) {
-		if (chr_encode(ch, dest, &dest_off, size - 1) != EOK)
-			break;
-	}
-
-	dest[dest_off] = '\0';
-}
-
-/** Convert UTF16 string to string.
- *
- * Convert utf16 string @a src to string. The output is written to the buffer
- * specified by @a dest and @a size. @a size must be non-zero and the string
- * written will always be well-formed. Surrogate pairs also supported.
- *
- * @param dest	Destination buffer.
- * @param size	Size of the destination buffer.
- * @param src	Source utf16 string.
- *
- * @return EOK, if success, an error code otherwise.
- */
-errno_t utf16_to_str(char *dest, size_t size, const uint16_t *src)
-{
-	size_t idx = 0, dest_off = 0;
-	char32_t ch;
-	errno_t rc = EOK;
-
-	/* There must be space for a null terminator in the buffer. */
-	assert(size > 0);
-
-	while (src[idx]) {
-		if ((src[idx] & 0xfc00) == 0xd800) {
-			if (src[idx + 1] && (src[idx + 1] & 0xfc00) == 0xdc00) {
-				ch = 0x10000;
-				ch += (src[idx] & 0x03FF) << 10;
-				ch += (src[idx + 1] & 0x03FF);
-				idx += 2;
-			} else
-				break;
-		} else {
-			ch = src[idx];
-			idx++;
-		}
-		rc = chr_encode(ch, dest, &dest_off, size - 1);
-		if (rc != EOK)
-			break;
-	}
-	dest[dest_off] = '\0';
-	return rc;
-}
-
-/** Convert string to UTF16 string.
- *
- * Convert string @a src to utf16 string. The output is written to the buffer
- * specified by @a dest and @a dlen. @a dlen must be non-zero and the string
- * written will always be well-formed. Surrogate pairs also supported.
- *
- * @param dest	Destination buffer.
- * @param dlen	Number of utf16 characters that fit in the destination buffer.
- * @param src	Source string.
- *
- * @return EOK, if success, an error code otherwise.
- */
-errno_t str_to_utf16(uint16_t *dest, size_t dlen, const char *src)
-{
-	errno_t rc = EOK;
-	size_t offset = 0;
-	size_t idx = 0;
-	char32_t c;
-
-	assert(dlen > 0);
-
-	while ((c = str_decode(src, &offset, STR_NO_LIMIT)) != 0) {
-		if (c > 0x10000) {
-			if (idx + 2 >= dlen - 1) {
-				rc = EOVERFLOW;
-				break;
-			}
-			c = (c - 0x10000);
-			dest[idx] = 0xD800 | (c >> 10);
-			dest[idx + 1] = 0xDC00 | (c & 0x3FF);
-			idx++;
-		} else {
-			dest[idx] = c;
-		}
-
-		idx++;
-		if (idx >= dlen - 1) {
-			rc = EOVERFLOW;
-			break;
-		}
-	}
-
-	dest[idx] = '\0';
-	return rc;
-}
-
-/** Get size of UTF-16 string.
- *
- * Get the number of words which are used by the UTF-16 string @a ustr
- * (excluding the NULL-terminator).
- *
- * @param ustr UTF-16 string to consider.
- *
- * @return Number of words used by the UTF-16 string
- *
- */
-size_t utf16_wsize(const uint16_t *ustr)
-{
-	size_t wsize = 0;
-
-	while (*ustr++ != 0)
-		wsize++;
-
-	return wsize;
-}
-
-/** Convert wide string to new string.
- *
- * Convert wide string @a src to string. Space for the new string is allocated
- * on the heap.
- *
- * @param src	Source wide string.
- * @return	New string.
- */
-char *wstr_to_astr(const char32_t *src)
-{
-	char dbuf[STR_BOUNDS(1)];
-	char *str;
-	char32_t ch;
-
-	size_t src_idx;
-	size_t dest_off;
-	size_t dest_size;
-
-	/* Compute size of encoded string. */
-
-	src_idx = 0;
-	dest_size = 0;
-
-	while ((ch = src[src_idx++]) != 0) {
-		dest_off = 0;
-		if (chr_encode(ch, dbuf, &dest_off, STR_BOUNDS(1)) != EOK)
-			break;
-		dest_size += dest_off;
-	}
-
-	str = malloc(dest_size + 1);
-	if (str == NULL)
-		return NULL;
-
-	/* Encode string. */
-
-	src_idx = 0;
-	dest_off = 0;
-
-	while ((ch = src[src_idx++]) != 0) {
-		if (chr_encode(ch, str, &dest_off, dest_size) != EOK)
-			break;
-	}
-
-	str[dest_size] = '\0';
-	return str;
-}
-
-/** Convert string to wide string.
- *
- * Convert string @a src to wide string. The output is written to the
- * buffer specified by @a dest and @a dlen. @a dlen must be non-zero
- * and the wide string written will always be null-terminated.
- *
- * @param dest	Destination buffer.
- * @param dlen	Length of destination buffer (number of wchars).
- * @param src	Source string.
- */
-void str_to_wstr(char32_t *dest, size_t dlen, const char *src)
-{
-	size_t offset;
-	size_t di;
-	char32_t c;
-
-	assert(dlen > 0);
-
-	offset = 0;
-	di = 0;
-
-	do {
-		if (di >= dlen - 1)
-			break;
-
-		c = str_decode(src, &offset, STR_NO_LIMIT);
-		dest[di++] = c;
-	} while (c != '\0');
-
-	dest[dlen - 1] = '\0';
-}
-
-/** Convert string to wide string.
- *
- * Convert string @a src to wide string. A new wide NULL-terminated
- * string will be allocated on the heap.
- *
- * @param src	Source string.
- */
-char32_t *str_to_awstr(const char *str)
-{
-	size_t len = str_length(str);
-
-	char32_t *wstr = calloc(len + 1, sizeof(char32_t));
-	if (wstr == NULL)
-		return NULL;
-
-	str_to_wstr(wstr, len + 1, str);
-	return wstr;
-}
-
-/** Find first occurence of character in string.
- *
- * @param str String to search.
- * @param ch  Character to look for.
- *
- * @return Pointer to character in @a str or NULL if not found.
- */
-char *str_chr(const char *str, char32_t ch)
-{
-	char32_t acc;
-	size_t off = 0;
-	size_t last = 0;
-
-	while ((acc = str_decode(str, &off, STR_NO_LIMIT)) != 0) {
-		if (acc == ch)
-			return (char *) (str + last);
-		last = off;
-	}
-
-	return NULL;
-}
-
-/** Find first occurence of substring in string.
- *
- * @param hs  Haystack (string)
- * @param n   Needle (substring to look for)
- *
- * @return Pointer to character in @a hs or @c NULL if not found.
- */
-char *str_str(const char *hs, const char *n)
-{
-	size_t off = 0;
-
-	if (str_lcmp(hs, n, str_length(n)) == 0)
-		return (char *)hs;
-
-	while (str_decode(hs, &off, STR_NO_LIMIT) != 0) {
-		if (str_lcmp(hs + off, n, str_length(n)) == 0)
-			return (char *)(hs + off);
-	}
-
-	return NULL;
-}
-
-/** Removes specified trailing characters from a string.
- *
- * @param str String to remove from.
- * @param ch  Character to remove.
- */
-void str_rtrim(char *str, char32_t ch)
-{
-	size_t off = 0;
-	size_t pos = 0;
-	char32_t c;
-	bool update_last_chunk = true;
-	char *last_chunk = NULL;
-
-	while ((c = str_decode(str, &off, STR_NO_LIMIT))) {
-		if (c != ch) {
-			update_last_chunk = true;
-			last_chunk = NULL;
-		} else if (update_last_chunk) {
-			update_last_chunk = false;
-			last_chunk = (str + pos);
-		}
-		pos = off;
-	}
-
-	if (last_chunk)
-		*last_chunk = '\0';
-}
-
-/** Removes specified leading characters from a string.
- *
- * @param str String to remove from.
- * @param ch  Character to remove.
- */
-void str_ltrim(char *str, char32_t ch)
-{
-	char32_t acc;
-	size_t off = 0;
-	size_t pos = 0;
-	size_t str_sz = str_size(str);
-
-	while ((acc = str_decode(str, &off, STR_NO_LIMIT)) != 0) {
-		if (acc != ch)
-			break;
-		else
-			pos = off;
-	}
-
-	if (pos > 0) {
-		memmove(str, &str[pos], str_sz - pos);
-		pos = str_sz - pos;
-		str[pos] = '\0';
-	}
-}
-
-/** Find last occurence of character in string.
- *
- * @param str String to search.
- * @param ch  Character to look for.
- *
- * @return Pointer to character in @a str or NULL if not found.
- */
-char *str_rchr(const char *str, char32_t ch)
-{
-	char32_t acc;
-	size_t off = 0;
-	size_t last = 0;
-	const char *res = NULL;
-
-	while ((acc = str_decode(str, &off, STR_NO_LIMIT)) != 0) {
-		if (acc == ch)
-			res = (str + last);
-		last = off;
-	}
-
-	return (char *) res;
-}
-
-/** Insert a wide character into a wide string.
- *
- * Insert a wide character into a wide string at position
- * @a pos. The characters after the position are shifted.
- *
- * @param str     String to insert to.
- * @param ch      Character to insert to.
- * @param pos     Character index where to insert.
- * @param max_pos Characters in the buffer.
- *
- * @return True if the insertion was sucessful, false if the position
- *         is out of bounds.
- *
- */
-bool wstr_linsert(char32_t *str, char32_t ch, size_t pos, size_t max_pos)
-{
-	size_t len = wstr_length(str);
-
-	if ((pos > len) || (pos + 1 > max_pos))
-		return false;
-
-	size_t i;
-	for (i = len; i + 1 > pos; i--)
-		str[i + 1] = str[i];
-
-	str[pos] = ch;
-
-	return true;
-}
-
-/** Remove a wide character from a wide string.
- *
- * Remove a wide character from a wide string at position
- * @a pos. The characters after the position are shifted.
- *
- * @param str String to remove from.
- * @param pos Character index to remove.
- *
- * @return True if the removal was sucessful, false if the position
- *         is out of bounds.
- *
- */
-bool wstr_remove(char32_t *str, size_t pos)
-{
-	size_t len = wstr_length(str);
-
-	if (pos >= len)
-		return false;
-
-	size_t i;
-	for (i = pos + 1; i <= len; i++)
-		str[i - 1] = str[i];
-
-	return true;
-}
-
-/** Duplicate string.
- *
- * Allocate a new string and copy characters from the source
- * string into it. The duplicate string is allocated via sleeping
- * malloc(), thus this function can sleep in no memory conditions.
- *
- * The allocation cannot fail and the return value is always
- * a valid pointer. The duplicate string is always a well-formed
- * null-terminated UTF-8 string, but it can differ from the source
- * string on the byte level.
- *
- * @param src Source string.
- *
- * @return Duplicate string.
- *
- */
-char *str_dup(const char *src)
-{
-	size_t size = str_size(src) + 1;
-	char *dest = malloc(size);
-	if (!dest)
-		return NULL;
-
-	str_cpy(dest, size, src);
-	return dest;
-}
-
-/** Duplicate string with size limit.
- *
- * Allocate a new string and copy up to @max_size bytes from the source
- * string into it. The duplicate string is allocated via sleeping
- * malloc(), thus this function can sleep in no memory conditions.
- * No more than @max_size + 1 bytes is allocated, but if the size
- * occupied by the source string is smaller than @max_size + 1,
- * less is allocated.
- *
- * The allocation cannot fail and the return value is always
- * a valid pointer. The duplicate string is always a well-formed
- * null-terminated UTF-8 string, but it can differ from the source
- * string on the byte level.
- *
- * @param src Source string.
- * @param n   Maximum number of bytes to duplicate.
- *
- * @return Duplicate string.
- *
- */
-char *str_ndup(const char *src, size_t n)
-{
-	size_t size = str_size(src);
-	if (size > n)
-		size = n;
-
-	char *dest = malloc(size + 1);
-	if (!dest)
-		return NULL;
-
-	str_ncpy(dest, size + 1, src, size);
-	return dest;
-}
-
-/** Split string by delimiters.
- *
- * @param s             String to be tokenized. May not be NULL.
- * @param delim		String with the delimiters.
- * @param next		Variable which will receive the pointer to the
- *                      continuation of the string following the first
- *                      occurrence of any of the delimiter characters.
- *                      May be NULL.
- * @return              Pointer to the prefix of @a s before the first
- *                      delimiter character. NULL if no such prefix
- *                      exists.
- */
-char *str_tok(char *s, const char *delim, char **next)
-{
-	char *start, *end;
-
-	if (!s)
-		return NULL;
-
-	size_t len = str_size(s);
-	size_t cur;
-	size_t tmp;
-	char32_t ch;
-
-	/* Skip over leading delimiters. */
-	tmp = 0;
-	cur = 0;
-	while ((ch = str_decode(s, &tmp, len)) && str_chr(delim, ch))
-		cur = tmp;
-	start = &s[cur];
-
-	/* Skip over token characters. */
-	tmp = cur;
-	while ((ch = str_decode(s, &tmp, len)) && !str_chr(delim, ch))
-		cur = tmp;
-	end = &s[cur];
-	if (next)
-		*next = (ch ? &s[tmp] : &s[cur]);
-
-	if (start == end)
-		return NULL;	/* No more tokens. */
-
-	/* Overwrite delimiter with NULL terminator. */
-	*end = '\0';
-	return start;
-}
-
-void order_suffix(const uint64_t val, uint64_t *rv, char *suffix)
-{
-	if (val > UINT64_C(10000000000000000000)) {
-		*rv = val / UINT64_C(1000000000000000000);
-		*suffix = 'Z';
-	} else if (val > UINT64_C(1000000000000000000)) {
-		*rv = val / UINT64_C(1000000000000000);
-		*suffix = 'E';
-	} else if (val > UINT64_C(1000000000000000)) {
-		*rv = val / UINT64_C(1000000000000);
-		*suffix = 'T';
-	} else if (val > UINT64_C(1000000000000)) {
-		*rv = val / UINT64_C(1000000000);
-		*suffix = 'G';
-	} else if (val > UINT64_C(1000000000)) {
-		*rv = val / UINT64_C(1000000);
-		*suffix = 'M';
-	} else if (val > UINT64_C(1000000)) {
-		*rv = val / UINT64_C(1000);
-		*suffix = 'k';
-	} else {
-		*rv = val;
-		*suffix = ' ';
-	}
-}
-
-void bin_order_suffix(const uint64_t val, uint64_t *rv, const char **suffix,
-    bool fixed)
-{
-	if (val > UINT64_C(1152921504606846976)) {
-		*rv = val / UINT64_C(1125899906842624);
-		*suffix = "EiB";
-	} else if (val > UINT64_C(1125899906842624)) {
-		*rv = val / UINT64_C(1099511627776);
-		*suffix = "TiB";
-	} else if (val > UINT64_C(1099511627776)) {
-		*rv = val / UINT64_C(1073741824);
-		*suffix = "GiB";
-	} else if (val > UINT64_C(1073741824)) {
-		*rv = val / UINT64_C(1048576);
-		*suffix = "MiB";
-	} else if (val > UINT64_C(1048576)) {
-		*rv = val / UINT64_C(1024);
-		*suffix = "KiB";
-	} else {
-		*rv = val;
-		if (fixed)
-			*suffix = "B  ";
-		else
-			*suffix = "B";
-	}
-}
-
-/** @}
- */
Index: pace/lib/c/generic/str_error.c
===================================================================
--- uspace/lib/c/generic/str_error.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,117 +1,0 @@
-/*
- * Copyright (c) 2010 Martin Decky
- * Copyright (c) 2017 CZ.NIC, z.s.p.o.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#include <errno.h>
-#include <str_error.h>
-#include <stdio.h>
-#include <fibril.h>
-
-#define NOERR_LEN  64
-
-/*
- * The arrays below are automatically generated from the same file that
- * errno.h constants are generated from. Triple-include of the same list
- * with redefinitions of __errno() macro are used to ensure that the
- * information cannot get out of synch. This is inpired by musl libc.
- */
-
-#undef __errno_entry
-#define __errno_entry(name, num, desc) name,
-
-static const errno_t err_num[] = {
-#include <abi/errno.in>
-};
-
-#undef __errno_entry
-#define __errno_entry(name, num, desc) #name,
-
-static const char *err_name[] = {
-#include <abi/errno.in>
-};
-
-#undef __errno_entry
-#define __errno_entry(name, num, desc) "[" #name "] " desc,
-
-static const char *err_desc[] = {
-#include <abi/errno.in>
-};
-
-static fibril_local char noerr[NOERR_LEN];
-
-/* Returns index corresponding to the given errno, or -1 if not found. */
-static int find_errno(errno_t e)
-{
-	/*
-	 * Just a dumb linear search.
-	 * There are too few entries to warrant anything smarter.
-	 */
-
-	int len = sizeof(err_num) / sizeof(errno_t);
-
-	for (int i = 0; i < len; i++) {
-		if (err_num[i] == e) {
-			return i;
-		}
-	}
-
-	return -1;
-}
-
-const char *str_error_name(errno_t e)
-{
-	int i = find_errno(e);
-
-	if (i >= 0) {
-		return err_name[i];
-	}
-
-	snprintf(noerr, NOERR_LEN, "(%d)", (int)e);
-	return noerr;
-}
-
-const char *str_error(errno_t e)
-{
-	int i = find_errno(e);
-
-	if (i >= 0) {
-		return err_desc[i];
-	}
-
-	snprintf(noerr, NOERR_LEN, "Unknown error code (%d)", (int)e);
-	return noerr;
-}
-
-/** @}
- */
Index: pace/lib/c/generic/strtol.c
===================================================================
--- uspace/lib/c/generic/strtol.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,538 +1,0 @@
-/*
- * Copyright (c) 2005 Martin Decky
- * Copyright (c) 2008 Jiri Svoboda
- * Copyright (c) 2011 Martin Sucha
- * Copyright (c) 2011 Oleg Romanenko
- * Copyright (c) 2011 Jiri Zarevucky
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#include <assert.h>
-#include <ctype.h>
-#include <errno.h>
-#include <inttypes.h>
-#include <limits.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <str.h>
-
-// FIXME: The original HelenOS functions return EOVERFLOW instead
-//        of ERANGE. It's a pointless distinction from standard functions,
-//        so we should change that. Beware the callers though.
-
-// TODO: more unit tests
-
-static inline int _digit_value(int c)
-{
-	if (isdigit(c)) {
-		return c - '0';
-	} else if (islower(c)) {
-		return c - 'a' + 10;
-	} else if (isupper(c)) {
-		return c - 'A' + 10;
-	}
-	return INT_MAX;
-}
-
-/*
- * FIXME: workaround for GCC "optimizing" the overflow check
- * into soft-emulated 128b multiplication using `__multi3`,
- * which we don't currently implement.
- */
-__attribute__((noinline)) static uintmax_t _max_value(int base)
-{
-	return UINTMAX_MAX / base;
-}
-
-static inline int _prefixbase(const char *restrict *nptrptr, bool nonstd)
-{
-	const char *nptr = *nptrptr;
-
-	if (nptr[0] != '0')
-		return 10;
-
-	if (nptr[1] == 'x' || nptr[1] == 'X') {
-		if (_digit_value(nptr[2]) < 16) {
-			*nptrptr += 2;
-			return 16;
-		}
-	}
-
-	if (nonstd) {
-		switch (nptr[1]) {
-		case 'b':
-		case 'B':
-			if (_digit_value(nptr[2]) < 2) {
-				*nptrptr += 2;
-				return 2;
-			}
-			break;
-		case 'o':
-		case 'O':
-			if (_digit_value(nptr[2]) < 8) {
-				*nptrptr += 2;
-				return 8;
-			}
-			break;
-		case 'd':
-		case 'D':
-		case 't':
-		case 'T':
-			if (_digit_value(nptr[2]) < 10) {
-				*nptrptr += 2;
-				return 10;
-			}
-			break;
-		}
-	}
-
-	return 8;
-}
-
-static inline uintmax_t _strtoumax(
-    const char *restrict nptr, char **restrict endptr, int base,
-    bool *restrict sgn, errno_t *err, bool nonstd)
-{
-	assert(nptr != NULL);
-	assert(sgn != NULL);
-
-	const char *first = nptr;
-
-	/* Skip leading whitespace. */
-
-	while (isspace(*nptr)) {
-		nptr++;
-	}
-
-	/* Parse sign, if any. */
-
-	switch (*nptr) {
-	case '-':
-		*sgn = true;
-		nptr++;
-		break;
-	case '+':
-		nptr++;
-		break;
-	}
-
-	/* Figure out the base. */
-
-	if (base == 0)
-		base = _prefixbase(&nptr, nonstd);
-
-	if (base == 16 && !nonstd) {
-		/*
-		 * Standard strto* functions allow hexadecimal prefix to be
-		 * present when base is explicitly set to 16.
-		 * Our nonstandard str_* functions don't allow it.
-		 * I don't know if that is intended, just matching the original
-		 * functionality here.
-		 */
-
-		if (nptr[0] == '0' && (nptr[1] == 'x' || nptr[1] == 'X') &&
-		    _digit_value(nptr[2]) < base)
-			nptr += 2;
-	}
-
-	if (base < 2 || base > 36) {
-		*err = EINVAL;
-		return 0;
-	}
-
-	/* Must be at least one digit. */
-
-	if (_digit_value(*nptr) >= base) {
-		/* No digits on input. */
-		if (endptr != NULL)
-			*endptr = (char *) first;
-		return 0;
-	}
-
-	/* Read the value.  */
-
-	uintmax_t result = 0;
-	uintmax_t max = _max_value(base);
-	int digit;
-
-	while (digit = _digit_value(*nptr), digit < base) {
-		if (result > max ||
-		    __builtin_add_overflow(result * base, digit, &result)) {
-
-			*err = nonstd ? EOVERFLOW : ERANGE;
-			result = UINTMAX_MAX;
-			break;
-		}
-
-		nptr++;
-	}
-
-	/* Set endptr. */
-
-	if (endptr != NULL) {
-		/*
-		 * Move the pointer to the end of the number,
-		 * in case it isn't there already.
-		 * This can happen when the number has legal formatting,
-		 * but is out of range of the target type.
-		 */
-		while (_digit_value(*nptr) < base) {
-			nptr++;
-		}
-
-		*endptr = (char *) nptr;
-	}
-
-	return result;
-}
-
-static inline intmax_t _strtosigned(const char *nptr, char **endptr, int base,
-    intmax_t min, intmax_t max, errno_t *err, bool nonstd)
-{
-	bool sgn = false;
-	uintmax_t number = _strtoumax(nptr, endptr, base, &sgn, err, nonstd);
-
-	if (number > (uintmax_t) max) {
-		if (sgn && (number - 1 == (uintmax_t) max)) {
-			return min;
-		}
-
-		*err = nonstd ? EOVERFLOW : ERANGE;
-		return (sgn ? min : max);
-	}
-
-	return (sgn ? -number : number);
-}
-
-static inline uintmax_t _strtounsigned(const char *nptr, char **endptr, int base,
-    uintmax_t max, errno_t *err, bool nonstd)
-{
-	bool sgn = false;
-	uintmax_t number = _strtoumax(nptr, endptr, base, &sgn, err, nonstd);
-
-	if (nonstd && sgn) {
-		/* Do not allow negative values */
-		*err = EINVAL;
-		return 0;
-	}
-
-	if (number > max) {
-		*err = nonstd ? EOVERFLOW : ERANGE;
-		return max;
-	}
-
-	return (sgn ? -number : number);
-}
-
-/** Convert initial part of string to long int according to given base.
- * The number may begin with an arbitrary number of whitespaces followed by
- * optional sign (`+' or `-'). If the base is 0 or 16, the prefix `0x' may be
- * inserted and the number will be taken as hexadecimal one. If the base is 0
- * and the number begin with a zero, number will be taken as octal one (as with
- * base 8). Otherwise the base 0 is taken as decimal.
- *
- * @param nptr		Pointer to string.
- * @param[out] endptr	If not NULL, function stores here pointer to the first
- * 			invalid character.
- * @param base		Zero or number between 2 and 36 inclusive.
- * @return		Result of conversion.
- */
-long strtol(const char *nptr, char **endptr, int base)
-{
-	return _strtosigned(nptr, endptr, base, LONG_MIN, LONG_MAX, &errno, false);
-}
-
-/** Convert initial part of string to unsigned long according to given base.
- * The number may begin with an arbitrary number of whitespaces followed by
- * optional sign (`+' or `-'). If the base is 0 or 16, the prefix `0x' may be
- * inserted and the number will be taken as hexadecimal one. If the base is 0
- * and the number begin with a zero, number will be taken as octal one (as with
- * base 8). Otherwise the base 0 is taken as decimal.
- *
- * @param nptr		Pointer to string.
- * @param[out] endptr	If not NULL, function stores here pointer to the first
- * 			invalid character
- * @param base		Zero or number between 2 and 36 inclusive.
- * @return		Result of conversion.
- */
-unsigned long strtoul(const char *nptr, char **endptr, int base)
-{
-	return _strtounsigned(nptr, endptr, base, ULONG_MAX, &errno, false);
-}
-
-long long strtoll(const char *nptr, char **endptr, int base)
-{
-	return _strtosigned(nptr, endptr, base, LLONG_MIN, LLONG_MAX, &errno, false);
-}
-
-unsigned long long strtoull(const char *nptr, char **endptr, int base)
-{
-	return _strtounsigned(nptr, endptr, base, ULLONG_MAX, &errno, false);
-}
-
-intmax_t strtoimax(const char *nptr, char **endptr, int base)
-{
-	return _strtosigned(nptr, endptr, base, INTMAX_MIN, INTMAX_MAX, &errno, false);
-}
-
-uintmax_t strtoumax(const char *nptr, char **endptr, int base)
-{
-	return _strtounsigned(nptr, endptr, base, UINTMAX_MAX, &errno, false);
-}
-
-int atoi(const char *nptr)
-{
-	return (int)strtol(nptr, NULL, 10);
-}
-
-long atol(const char *nptr)
-{
-	return strtol(nptr, NULL, 10);
-}
-
-long long atoll(const char *nptr)
-{
-	return strtoll(nptr, NULL, 10);
-}
-
-/** Convert string to uint8_t.
- *
- * @param nptr   Pointer to string.
- * @param endptr If not NULL, pointer to the first invalid character
- *               is stored here.
- * @param base   Zero or number between 2 and 36 inclusive.
- * @param strict Do not allow any trailing characters.
- * @param result Result of the conversion.
- *
- * @return EOK if conversion was successful.
- *
- */
-errno_t str_uint8_t(const char *nptr, const char **endptr, unsigned int base,
-    bool strict, uint8_t *result)
-{
-	assert(result != NULL);
-
-	errno_t rc = EOK;
-	char *lendptr = (char *) nptr;
-
-	uintmax_t r = _strtounsigned(nptr, &lendptr, base, UINT8_MAX, &rc, true);
-
-	if (endptr)
-		*endptr = lendptr;
-
-	if (rc != EOK)
-		return rc;
-
-	if (strict && *lendptr != '\0')
-		return EINVAL;
-
-	*result = r;
-	return EOK;
-}
-
-/** Convert string to uint16_t.
- *
- * @param nptr   Pointer to string.
- * @param endptr If not NULL, pointer to the first invalid character
- *               is stored here.
- * @param base   Zero or number between 2 and 36 inclusive.
- * @param strict Do not allow any trailing characters.
- * @param result Result of the conversion.
- *
- * @return EOK if conversion was successful.
- *
- */
-errno_t str_uint16_t(const char *nptr, const char **endptr, unsigned int base,
-    bool strict, uint16_t *result)
-{
-	assert(result != NULL);
-
-	errno_t rc = EOK;
-	char *lendptr = (char *) nptr;
-
-	uintmax_t r = _strtounsigned(nptr, &lendptr, base, UINT16_MAX, &rc, true);
-
-	if (endptr)
-		*endptr = lendptr;
-
-	if (rc != EOK)
-		return rc;
-
-	if (strict && *lendptr != '\0')
-		return EINVAL;
-
-	*result = r;
-	return EOK;
-}
-
-/** Convert string to uint32_t.
- *
- * @param nptr   Pointer to string.
- * @param endptr If not NULL, pointer to the first invalid character
- *               is stored here.
- * @param base   Zero or number between 2 and 36 inclusive.
- * @param strict Do not allow any trailing characters.
- * @param result Result of the conversion.
- *
- * @return EOK if conversion was successful.
- *
- */
-errno_t str_uint32_t(const char *nptr, const char **endptr, unsigned int base,
-    bool strict, uint32_t *result)
-{
-	assert(result != NULL);
-
-	errno_t rc = EOK;
-	char *lendptr = (char *) nptr;
-
-	uintmax_t r = _strtounsigned(nptr, &lendptr, base, UINT32_MAX, &rc, true);
-
-	if (endptr)
-		*endptr = lendptr;
-
-	if (rc != EOK)
-		return rc;
-
-	if (strict && *lendptr != '\0')
-		return EINVAL;
-
-	*result = r;
-	return EOK;
-}
-
-/** Convert string to uint64_t.
- *
- * @param nptr   Pointer to string.
- * @param endptr If not NULL, pointer to the first invalid character
- *               is stored here.
- * @param base   Zero or number between 2 and 36 inclusive.
- * @param strict Do not allow any trailing characters.
- * @param result Result of the conversion.
- *
- * @return EOK if conversion was successful.
- *
- */
-errno_t str_uint64_t(const char *nptr, const char **endptr, unsigned int base,
-    bool strict, uint64_t *result)
-{
-	assert(result != NULL);
-
-	errno_t rc = EOK;
-	char *lendptr = (char *) nptr;
-
-	uintmax_t r = _strtounsigned(nptr, &lendptr, base, UINT64_MAX, &rc, true);
-
-	if (endptr)
-		*endptr = lendptr;
-
-	if (rc != EOK)
-		return rc;
-
-	if (strict && *lendptr != '\0')
-		return EINVAL;
-
-	*result = r;
-	return EOK;
-}
-
-/** Convert string to int64_t.
- *
- * @param nptr   Pointer to string.
- * @param endptr If not NULL, pointer to the first invalid character
- *               is stored here.
- * @param base   Zero or number between 2 and 36 inclusive.
- * @param strict Do not allow any trailing characters.
- * @param result Result of the conversion.
- *
- * @return EOK if conversion was successful.
- *
- */
-errno_t str_int64_t(const char *nptr, const char **endptr, unsigned int base,
-    bool strict, int64_t *result)
-{
-	assert(result != NULL);
-
-	errno_t rc = EOK;
-	char *lendptr = (char *) nptr;
-
-	intmax_t r = _strtosigned(nptr, &lendptr, base, INT64_MIN, INT64_MAX, &rc, true);
-
-	if (endptr)
-		*endptr = lendptr;
-
-	if (rc != EOK)
-		return rc;
-
-	if (strict && *lendptr != '\0')
-		return EINVAL;
-
-	*result = r;
-	return EOK;
-}
-
-/** Convert string to size_t.
- *
- * @param nptr   Pointer to string.
- * @param endptr If not NULL, pointer to the first invalid character
- *               is stored here.
- * @param base   Zero or number between 2 and 36 inclusive.
- * @param strict Do not allow any trailing characters.
- * @param result Result of the conversion.
- *
- * @return EOK if conversion was successful.
- *
- */
-errno_t str_size_t(const char *nptr, const char **endptr, unsigned int base,
-    bool strict, size_t *result)
-{
-	assert(result != NULL);
-
-	errno_t rc = EOK;
-	char *lendptr = (char *) nptr;
-
-	uintmax_t r = _strtounsigned(nptr, &lendptr, base, SIZE_MAX, &rc, true);
-
-	if (endptr)
-		*endptr = lendptr;
-
-	if (rc != EOK)
-		return rc;
-
-	if (strict && *lendptr != '\0')
-		return EINVAL;
-
-	*result = r;
-	return EOK;
-}
-
-/** @}
- */
Index: uspace/lib/c/generic/thread/fibril_synch.c
===================================================================
--- uspace/lib/c/generic/thread/fibril_synch.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/generic/thread/fibril_synch.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -160,11 +160,4 @@
 {
 	check_fibril_for_deadlock(oi, fibril_self());
-}
-
-void fibril_mutex_initialize(fibril_mutex_t *fm)
-{
-	fm->oi.owned_by = NULL;
-	fm->counter = 1;
-	list_initialize(&fm->waiters);
 }
 
Index: uspace/lib/c/generic/uuid.c
===================================================================
--- uspace/lib/c/generic/uuid.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/generic/uuid.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -41,4 +41,13 @@
 #include <stdio.h>
 
+static void encode16_be(uint8_t *, uint16_t);
+static void encode16_le(uint8_t *, uint16_t);
+static void encode32_be(uint8_t *, uint32_t);
+static void encode32_le(uint8_t *, uint32_t);
+static uint16_t decode16_be(uint8_t *);
+static uint16_t decode16_le(uint8_t *);
+static uint32_t decode32_be(uint8_t *);
+static uint32_t decode32_le(uint8_t *);
+
 /** Generate UUID.
  *
@@ -56,6 +65,36 @@
 		return EIO;
 
-	for (i = 0; i < uuid_bytes; i++) {
-		rc = rndgen_uint8(rndgen, &uuid->b[i]);
+	rc = rndgen_uint32(rndgen, &uuid->time_low);
+	if (rc != EOK) {
+		rc = EIO;
+		goto error;
+	}
+
+	rc = rndgen_uint16(rndgen, &uuid->time_mid);
+	if (rc != EOK) {
+		rc = EIO;
+		goto error;
+	}
+
+	rc = rndgen_uint16(rndgen, &uuid->time_hi_and_version);
+	if (rc != EOK) {
+		rc = EIO;
+		goto error;
+	}
+
+	rc = rndgen_uint8(rndgen, &uuid->clock_seq_hi_and_reserved);
+	if (rc != EOK) {
+		rc = EIO;
+		goto error;
+	}
+
+	rc = rndgen_uint8(rndgen, &uuid->clock_seq_low);
+	if (rc != EOK) {
+		rc = EIO;
+		goto error;
+	}
+
+	for (i = 0; i < _UUID_NODE_LEN; i++) {
+		rc = rndgen_uint8(rndgen, &uuid->node[i]);
 		if (rc != EOK) {
 			rc = EIO;
@@ -65,6 +104,6 @@
 
 	/* Version 4 UUID from random or pseudo-random numbers */
-	uuid->b[6] = (uuid->b[6] & 0x0f) | 0x40;
-	uuid->b[8] = (uuid->b[8] & 0x3f) | 0x80;
+	uuid->time_hi_and_version = (uuid->time_hi_and_version & 0x0fff) | 0x4000;
+	uuid->clock_seq_hi_and_reserved = (uuid->clock_seq_hi_and_reserved & 0x3f) | 0x80;
 
 error:
@@ -73,4 +112,5 @@
 }
 
+
 /** Encode UUID into binary form per RFC 4122.
  *
@@ -82,6 +122,12 @@
 	int i;
 
-	for (i = 0; i < uuid_bytes; i++)
-		buf[i] = uuid->b[i];
+	encode32_be(buf, uuid->time_low);
+	encode16_be(buf + 4, uuid->time_mid);
+	encode16_be(buf + 6, uuid->time_hi_and_version);
+	buf[8] = uuid->clock_seq_hi_and_reserved;
+	buf[9] = uuid->clock_seq_low;
+
+	for (i = 0; i < _UUID_NODE_LEN; i++)
+		buf[10 + i] = uuid->node[i];
 }
 
@@ -95,6 +141,50 @@
 	int i;
 
-	for (i = 0; i < uuid_bytes; i++)
-		uuid->b[i] = buf[i];
+	uuid->time_low = decode32_be(buf);
+	uuid->time_mid = decode16_be(buf + 4);
+	uuid->time_hi_and_version = decode16_be(buf + 6);
+	uuid->clock_seq_hi_and_reserved = buf[8];
+	uuid->clock_seq_low = buf[9];
+
+	for (i = 0; i < _UUID_NODE_LEN; i++)
+		uuid->node[i] = buf[10 + i];
+}
+
+/** Encode UUID into little-endian (GPT) binary form.
+ *
+ * @param uuid UUID
+ * @param buf 16-byte destination buffer
+ */
+void uuid_encode_le(uuid_t *uuid, uint8_t *buf)
+{
+	int i;
+
+	encode32_le(buf, uuid->time_low);
+	encode16_le(buf + 4, uuid->time_mid);
+	encode16_le(buf + 6, uuid->time_hi_and_version);
+	buf[8] = uuid->clock_seq_hi_and_reserved;
+	buf[9] = uuid->clock_seq_low;
+
+	for (i = 0; i < _UUID_NODE_LEN; i++)
+		buf[10 + i] = uuid->node[i];
+}
+
+/** Decode UUID from little-endian (GPT) binary form.
+ *
+ * @param buf 16-byte source buffer
+ * @param uuid Place to store UUID
+ */
+void uuid_decode_le(uint8_t *buf, uuid_t *uuid)
+{
+	int i;
+
+	uuid->time_low = decode32_le(buf);
+	uuid->time_mid = decode16_le(buf + 4);
+	uuid->time_hi_and_version = decode16_le(buf + 6);
+	uuid->clock_seq_hi_and_reserved = buf[8];
+	uuid->clock_seq_low = buf[9];
+
+	for (i = 0; i < _UUID_NODE_LEN; i++)
+		uuid->node[i] = buf[10 + i];
 }
 
@@ -142,20 +232,16 @@
 		return EINVAL;
 
-	uuid->b[0] = time_low >> 24;
-	uuid->b[1] = (time_low >> 16) & 0xff;
-	uuid->b[2] = (time_low >> 8) & 0xff;
-	uuid->b[3] = time_low & 0xff;
-
-	uuid->b[4] = time_mid >> 8;
-	uuid->b[5] = time_mid & 0xff;
-
-	uuid->b[6] = time_ver >> 8;
-	uuid->b[7] = time_ver & 0xff;
-
-	uuid->b[8] = clock >> 8;
-	uuid->b[9] = clock & 0xff;
-
-	for (i = 0; i < 6; i++)
-		uuid->b[10 + i] = (node >> 8 * (5 - i)) & 0xff;
+	uuid->time_low = time_low;
+
+	uuid->time_mid = time_mid;
+
+	uuid->time_hi_and_version = time_ver;
+
+	uuid->clock_seq_hi_and_reserved = clock >> 8;
+
+	uuid->clock_seq_low = clock & 0xff;
+
+	for (i = 0; i < _UUID_NODE_LEN; i++)
+		uuid->node[i] = (node >> 8 * (5 - i)) & 0xff;
 
 	if (endptr != NULL) {
@@ -183,9 +269,12 @@
 		return ENOMEM;
 
-	const char *format = "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x";
+	const char *format = "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x";
 	if (uppercase)
-		format = "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X";
-
-	int ret = snprintf(str, size, format, uuid->b[0], uuid->b[1], uuid->b[2], uuid->b[3], uuid->b[4], uuid->b[5], uuid->b[6], uuid->b[7], uuid->b[8], uuid->b[9], uuid->b[10], uuid->b[11], uuid->b[12], uuid->b[13], uuid->b[14], uuid->b[15]);
+		format = "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X";
+
+	int ret = snprintf(str, size, format, uuid->time_low, uuid->time_mid,
+	    uuid->time_hi_and_version, uuid->clock_seq_hi_and_reserved,
+	    uuid->clock_seq_low, uuid->node[0], uuid->node[1], uuid->node[2],
+	    uuid->node[3], uuid->node[4], uuid->node[5]);
 
 	if (ret != 36) {
@@ -198,4 +287,52 @@
 }
 
+static void encode16_be(uint8_t *buf, uint16_t value)
+{
+	buf[0] = (value >> 8) & 0xff;
+	buf[1] = value & 0xff;
+}
+
+static void encode16_le(uint8_t *buf, uint16_t value)
+{
+	buf[0] = value & 0xff;
+	buf[1] = (value >> 8) & 0xff;
+}
+
+static void encode32_be(uint8_t *buf, uint32_t value)
+{
+	buf[0] = (value >> 24) & 0xff;
+	buf[1] = (value >> 16) & 0xff;
+	buf[2] = (value >> 8) & 0xff;
+	buf[3] = value & 0xff;
+}
+
+static void encode32_le(uint8_t *buf, uint32_t value)
+{
+	buf[0] = value & 0xff;
+	buf[1] = (value >> 8) & 0xff;
+	buf[2] = (value >> 16) & 0xff;
+	buf[3] = (value >> 24) & 0xff;
+}
+
+static uint16_t decode16_be(uint8_t *buf)
+{
+	return ((buf[0] << 8) | buf[1]);
+}
+
+static uint16_t decode16_le(uint8_t *buf)
+{
+	return ((buf[1] << 8) | buf[0]);
+}
+
+static uint32_t decode32_be(uint8_t *buf)
+{
+	return ((buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]);
+}
+
+static uint32_t decode32_le(uint8_t *buf)
+{
+	return ((buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]);
+}
+
 /** @}
  */
Index: pace/lib/c/include/adt/checksum.h
===================================================================
--- uspace/lib/c/include/adt/checksum.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,47 +1,0 @@
-/*
- * Copyright (c) 2012 Dominik Taborsky
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_CHECKSUM_H_
-#define _LIBC_CHECKSUM_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-extern uint32_t compute_crc32(uint8_t *, size_t);
-extern uint32_t compute_crc32_seed(uint8_t *, size_t, uint32_t);
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/adt/circ_buf.h
===================================================================
--- uspace/lib/c/include/adt/circ_buf.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,66 +1,0 @@
-/*
- * Copyright (c) 2017 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file Circular buffer
- */
-
-#ifndef _LIBC_CIRC_BUF_H_
-#define _LIBC_CIRC_BUF_H_
-
-#include <errno.h>
-#include <stddef.h>
-
-/** Circular buffer */
-typedef struct {
-	/** Buffer */
-	void *buf;
-	/** Number of buffer members */
-	size_t nmemb;
-	/** Member size */
-	size_t size;
-	/** Read position */
-	size_t rp;
-	/** Write position */
-	size_t wp;
-	/** Number of used entries */
-	size_t nused;
-} circ_buf_t;
-
-extern void circ_buf_init(circ_buf_t *, void *, size_t, size_t);
-extern size_t circ_buf_nfree(circ_buf_t *);
-extern size_t circ_buf_nused(circ_buf_t *);
-extern errno_t circ_buf_push(circ_buf_t *, const void *);
-extern errno_t circ_buf_pop(circ_buf_t *, void *);
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/adt/fifo.h
===================================================================
--- uspace/lib/c/include/adt/fifo.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,127 +1,0 @@
-/*
- * Copyright (c) 2006 Jakub Jermar
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-/*
- * This implementation of FIFO stores values in an array
- * (static or dynamic). As such, these FIFOs have upper bound
- * on number of values they can store. Push and pop operations
- * are done via accessing the array through head and tail indices.
- * Because of better operation ordering in fifo_pop(), the access
- * policy for these two indices is to 'increment (mod size of FIFO)
- * and use'.
- */
-
-#ifndef _LIBC_FIFO_H_
-#define _LIBC_FIFO_H_
-
-#include <stdlib.h>
-
-typedef unsigned long fifo_count_t;
-typedef unsigned long fifo_index_t;
-
-#define FIFO_CREATE_STATIC(name, t, itms) \
-	struct { \
-		t fifo[(itms)]; \
-		fifo_count_t items; \
-		fifo_index_t head; \
-		fifo_index_t tail; \
-	} name
-
-/** Create and initialize static FIFO.
- *
- * FIFO is allocated statically.
- * This macro is suitable for creating smaller FIFOs.
- *
- * @param name Name of FIFO.
- * @param t Type of values stored in FIFO.
- * @param itms Number of items that can be stored in FIFO.
- */
-#define FIFO_INITIALIZE_STATIC(name, t, itms)		\
-	FIFO_CREATE_STATIC(name, t, itms) = {		\
-		.items = (itms),			\
-		.head = 0,				\
-		.tail = 0				\
-	}
-
-/** Create and prepare dynamic FIFO.
- *
- * FIFO is allocated dynamically.
- * This macro is suitable for creating larger FIFOs.
- *
- * @param name Name of FIFO.
- * @param t Type of values stored in FIFO.
- * @param itms Number of items that can be stored in FIFO.
- */
-#define FIFO_INITIALIZE_DYNAMIC(name, t, itms)		\
-	struct {					\
-		t *fifo;				\
-		fifo_count_t items;			\
-		fifo_index_t head;			\
-		fifo_index_t tail;			\
-	} name = {					\
-		.fifo = NULL,				\
-		.items = (itms),			\
-		.head = 0,				\
-		.tail = 0				\
-	}
-
-/** Pop value from head of FIFO.
- *
- * @param name FIFO name.
- *
- * @return Leading value in FIFO.
- */
-#define fifo_pop(name) \
-	name.fifo[name.head = (name.head + 1) < name.items ? (name.head + 1) : 0]
-
-/** Push value to tail of FIFO.
- *
- * @param name FIFO name.
- * @param value Value to be appended to FIFO.
- *
- */
-#define fifo_push(name, value) \
-	name.fifo[name.tail = (name.tail + 1) < name.items ? (name.tail + 1) : 0] = (value)
-
-/** Allocate memory for dynamic FIFO.
- *
- * @param name FIFO name.
- */
-#define fifo_create(name) \
-	name.fifo = malloc(sizeof(*name.fifo) * name.items)
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/adt/gcdlcm.h
===================================================================
--- uspace/lib/c/include/adt/gcdlcm.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,74 +1,0 @@
-/*
- * Copyright (c) 2009 Martin Decky
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_GCDLCM_H_
-#define _LIBC_GCDLCM_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#define DECLARE_GCD(type, name) \
-	static inline type name(type a, type b) \
-	{ \
-		if (a == 0) \
-			return b; \
-		 \
-		while (b != 0) { \
-			if (a > b) \
-				a -= b; \
-			else \
-				b -= a; \
-		} \
-		 \
-		return a; \
-	}
-
-#define DECLARE_LCM(type, name, gcd) \
-	static inline type name(type a, type b) \
-	{ \
-		return (a * b) / gcd(a, b); \
-	}
-
-DECLARE_GCD(uint32_t, gcd32);
-DECLARE_GCD(uint64_t, gcd64);
-DECLARE_GCD(size_t, gcd);
-
-DECLARE_LCM(uint32_t, lcm32, gcd32);
-DECLARE_LCM(uint64_t, lcm64, gcd64);
-DECLARE_LCM(size_t, lcm, gcd);
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/adt/hash.h
===================================================================
--- uspace/lib/c/include/adt/hash.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,113 +1,0 @@
-/*
- * Copyright (c) 2012 Adam Hraska
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-#ifndef _LIBC_ADT_HASH_H_
-#define _LIBC_ADT_HASH_H_
-
-#include <stdint.h>
-#include <types/common.h>
-
-/** Produces a uniform hash affecting all output bits from the skewed input. */
-static inline uint32_t hash_mix32(uint32_t hash)
-{
-	/*
-	 * Thomas Wang's modification of Bob Jenkin's hash mixing function:
-	 * http://www.concentric.net/~Ttwang/tech/inthash.htm
-	 * Public domain.
-	 */
-	hash = ~hash + (hash << 15);
-	hash = hash ^ (hash >> 12);
-	hash = hash + (hash << 2);
-	hash = hash ^ (hash >> 4);
-	hash = hash * 2057;
-	hash = hash ^ (hash >> 16);
-	return hash;
-}
-
-/** Produces a uniform hash affecting all output bits from the skewed input. */
-static inline uint64_t hash_mix64(uint64_t hash)
-{
-	/*
-	 * Thomas Wang's public domain 64-bit hash mixing function:
-	 * http://www.concentric.net/~Ttwang/tech/inthash.htm
-	 */
-	hash = (hash ^ 61) ^ (hash >> 16);
-	hash = hash + (hash << 3);
-	hash = hash ^ (hash >> 4);
-	hash = hash * 0x27d4eb2d;
-	hash = hash ^ (hash >> 15);
-	/*
-	 * Lower order bits are mixed more thoroughly. Swap them with
-	 * the higher order bits and make the resulting higher order bits
-	 * more usable.
-	 */
-	return (hash << 32) | (hash >> 32);
-}
-
-/** Produces a uniform hash affecting all output bits from the skewed input. */
-static inline size_t hash_mix(size_t hash)
-{
-#ifdef __32_BITS__
-	return hash_mix32(hash);
-#elif defined(__64_BITS__)
-	return hash_mix64(hash);
-#else
-#error Unknown size_t size - cannot select proper hash mix function.
-#endif
-}
-
-/** Use to create a hash from multiple values.
- *
- * Typical usage:
- * @code
- * int car_id;
- * bool car_convertible;
- * // ..
- * size_t hash = 0;
- * hash = hash_combine(hash, car_id);
- * hash = hash_combine(hash, car_convertible);
- * // Now use hash as a hash of both car_id and car_convertible.
- * @endcode
- */
-static inline size_t hash_combine(size_t seed, size_t hash)
-{
-	/*
-	 * todo: use Bob Jenkin's proper mixing hash pass:
-	 * http://burtleburtle.net/bob/c/lookup3.c
-	 */
-	seed ^= hash + 0x9e3779b9 +
-	    ((seed << 5) | (seed >> (sizeof(size_t) * 8 - 5)));
-	return seed;
-}
-
-#endif
Index: pace/lib/c/include/adt/hash_table.h
===================================================================
--- uspace/lib/c/include/adt/hash_table.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,108 +1,0 @@
-/*
- * Copyright (c) 2006 Jakub Jermar
- * Copyright (c) 2012 Adam Hraska
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_HASH_TABLE_H_
-#define _LIBC_HASH_TABLE_H_
-
-#include <adt/list.h>
-#include <stdbool.h>
-#include <macros.h>
-#include <member.h>
-
-/** Opaque hash table link type. */
-typedef struct ht_link {
-	link_t link;
-} ht_link_t;
-
-/** Set of operations for hash table. */
-typedef struct {
-	/** Returns the hash of the key stored in the item (ie its lookup key). */
-	size_t (*hash)(const ht_link_t *item);
-
-	/** Returns the hash of the key. */
-	size_t (*key_hash)(const void *key);
-
-	/** True if the items are equal (have the same lookup keys). */
-	bool (*equal)(const ht_link_t *item1, const ht_link_t *item2);
-
-	/** Returns true if the key is equal to the item's lookup key. */
-	bool (*key_equal)(const void *key, const ht_link_t *item);
-
-	/** Hash table item removal callback.
-	 *
-	 * Must not invoke any mutating functions of the hash table.
-	 *
-	 * @param item Item that was removed from the hash table.
-	 */
-	void (*remove_callback)(ht_link_t *item);
-} hash_table_ops_t;
-
-/** Hash table structure. */
-typedef struct {
-	hash_table_ops_t *op;
-	list_t *bucket;
-	size_t bucket_cnt;
-	size_t full_item_cnt;
-	size_t item_cnt;
-	size_t max_load;
-	bool apply_ongoing;
-} hash_table_t;
-
-#define hash_table_get_inst(item, type, member) \
-	member_to_inst((item), type, member)
-
-extern bool hash_table_create(hash_table_t *, size_t, size_t,
-    hash_table_ops_t *);
-extern void hash_table_destroy(hash_table_t *);
-
-extern bool hash_table_empty(hash_table_t *);
-extern size_t hash_table_size(hash_table_t *);
-
-extern void hash_table_clear(hash_table_t *);
-extern void hash_table_insert(hash_table_t *, ht_link_t *);
-extern bool hash_table_insert_unique(hash_table_t *, ht_link_t *);
-extern ht_link_t *hash_table_find(const hash_table_t *, const void *);
-extern ht_link_t *hash_table_find_next(const hash_table_t *, ht_link_t *,
-    ht_link_t *);
-extern size_t hash_table_remove(hash_table_t *, const void *);
-extern void hash_table_remove_item(hash_table_t *, ht_link_t *);
-extern void hash_table_apply(hash_table_t *, bool (*)(ht_link_t *, void *),
-    void *);
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/adt/list.h
===================================================================
--- uspace/lib/c/include/adt/list.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,451 +1,0 @@
-/*
- * Copyright (c) 2001-2004 Jakub Jermar
- * Copyright (c) 2013 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_LIST_H_
-#define _LIBC_LIST_H_
-
-#include <assert.h>
-#include <member.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <trace.h>
-#include <_bits/decls.h>
-
-#ifndef __cplusplus
-
-/**
- * We don't define the macros in C++ to avoid polluting headers with
- * namespaceless names. We don't actually need them, so this is fine.
- * We still allow including the rest of the file (in `helenos` namespace)
- * so that we can expose publicly visible types that have list_t members.
- */
-
-/** Declare and initialize statically allocated list.
- *
- * @param name Name of the new statically allocated list.
- *
- */
-#define LIST_INITIALIZE(name) \
-	list_t name = LIST_INITIALIZER(name)
-
-/** Initializer for statically allocated list.
- *
- * @code
- * struct named_list {
- *     const char *name;
- *     list_t list;
- * } var = {
- *     .name = "default name",
- *     .list = LIST_INITIALIZER(name_list.list)
- * };
- * @endcode
- *
- * @param name Name of the new statically allocated list.
- *
- */
-#define LIST_INITIALIZER(name) \
-	{ \
-		.head = { \
-			.prev = &(name).head, \
-			.next = &(name).head \
-		} \
-	}
-
-#define list_get_instance(link, type, member) \
-	member_to_inst(link, type, member)
-
-#define list_foreach(list, member, itype, iterator) \
-	for (itype *iterator = NULL; iterator == NULL; iterator = (itype *) sizeof(itype)) \
-		for (link_t *_link = (list).head.next; \
-		    iterator = list_get_instance(_link, itype, member), \
-		    _link != &(list).head; _link = _link->next)
-
-#define list_foreach_rev(list, member, itype, iterator) \
-	for (itype *iterator = NULL; iterator == NULL; iterator = (itype *) sizeof(itype)) \
-		for (link_t *_link = (list).head.prev; \
-		    iterator = list_get_instance(_link, itype, member), \
-		    _link != &(list).head; _link = _link->prev)
-
-/** Unlike list_foreach(), allows removing items while traversing a list.
- *
- * @code
- * list_t mylist;
- * typedef struct item {
- *     int value;
- *     link_t item_link;
- * } item_t;
- *
- * //..
- *
- * // Print each list element's value and remove the element from the list.
- * list_foreach_safe(mylist, cur_link, next_link) {
- *     item_t *cur_item = list_get_instance(cur_link, item_t, item_link);
- *     printf("%d\n", cur_item->value);
- *     list_remove(cur_link);
- * }
- * @endcode
- *
- * @param list List to traverse.
- * @param iterator Iterator to the current element of the list.
- *             The item this iterator points may be safely removed
- *             from the list.
- * @param next_iter Iterator to the next element of the list.
- */
-#define list_foreach_safe(list, iterator, next_iter) \
-	for (link_t *iterator = (list).head.next, \
-	    *next_iter = iterator->next; \
-	    iterator != &(list).head; \
-	    iterator = next_iter, next_iter = iterator->next)
-
-#define assert_link_not_used(link) \
-	assert(!link_used(link))
-
-#define list_pop(list, type, member) \
-	((type *) list_pop_internal(list, \
-	    (list_link_to_void(&(((type *) NULL)->member)) - NULL)))
-
-#endif  /* !__cplusplus */
-
-__HELENOS_DECLS_BEGIN;
-
-/** Doubly linked list link. */
-typedef struct __adt_list_link {
-	struct __adt_list_link *prev;  /**< Pointer to the previous item in the list. */
-	struct __adt_list_link *next;  /**< Pointer to the next item in the list. */
-} link_t;
-
-/** Doubly linked list. */
-typedef struct {
-	link_t head;  /**< List head. Does not have any data. */
-} list_t;
-
-extern bool list_member(const link_t *, const list_t *);
-extern void list_splice(list_t *, link_t *);
-extern size_t list_count(const list_t *);
-
-/** Returns true if the link is definitely part of a list. False if not sure. */
-static inline bool link_in_use(const link_t *link)
-{
-	return link->prev != NULL && link->next != NULL;
-}
-
-/** Initialize doubly-linked circular list link
- *
- * Initialize doubly-linked list link.
- *
- * @param link Pointer to link_t structure to be initialized.
- *
- */
-_NO_TRACE static inline void link_initialize(link_t *link)
-{
-	link->prev = NULL;
-	link->next = NULL;
-}
-
-/** Initialize doubly-linked circular list
- *
- * Initialize doubly-linked circular list.
- *
- * @param list Pointer to list_t structure.
- *
- */
-_NO_TRACE static inline void list_initialize(list_t *list)
-{
-	list->head.prev = &list->head;
-	list->head.next = &list->head;
-}
-
-/** Insert item before another item in doubly-linked circular list.
- *
- */
-static inline void list_insert_before(link_t *lnew, link_t *lold)
-{
-	lnew->next = lold;
-	lnew->prev = lold->prev;
-	lold->prev->next = lnew;
-	lold->prev = lnew;
-}
-
-/** Insert item after another item in doubly-linked circular list.
- *
- */
-static inline void list_insert_after(link_t *lnew, link_t *lold)
-{
-	lnew->prev = lold;
-	lnew->next = lold->next;
-	lold->next->prev = lnew;
-	lold->next = lnew;
-}
-
-/** Add item to the beginning of doubly-linked circular list
- *
- * Add item to the beginning of doubly-linked circular list.
- *
- * @param link Pointer to link_t structure to be added.
- * @param list Pointer to list_t structure.
- *
- */
-_NO_TRACE static inline void list_prepend(link_t *link, list_t *list)
-{
-	list_insert_after(link, &list->head);
-}
-
-/** Add item to the end of doubly-linked circular list
- *
- * Add item to the end of doubly-linked circular list.
- *
- * @param link Pointer to link_t structure to be added.
- * @param list Pointer to list_t structure.
- *
- */
-_NO_TRACE static inline void list_append(link_t *link, list_t *list)
-{
-	list_insert_before(link, &list->head);
-}
-
-/** Remove item from doubly-linked circular list
- *
- * Remove item from doubly-linked circular list.
- *
- * @param link Pointer to link_t structure to be removed from the list
- *             it is contained in.
- *
- */
-_NO_TRACE static inline void list_remove(link_t *link)
-{
-	if ((link->prev != NULL) && (link->next != NULL)) {
-		link->next->prev = link->prev;
-		link->prev->next = link->next;
-	}
-
-	link_initialize(link);
-}
-
-/** Query emptiness of doubly-linked circular list
- *
- * Query emptiness of doubly-linked circular list.
- *
- * @param list Pointer to lins_t structure.
- *
- */
-_NO_TRACE static inline bool list_empty(const list_t *list)
-{
-	return (list->head.next == &list->head);
-}
-
-/** Get first item in list.
- *
- * @param list Pointer to list_t structure.
- *
- * @return Head item of the list.
- * @return NULL if the list is empty.
- *
- */
-static inline link_t *list_first(const list_t *list)
-{
-	return ((list->head.next == &list->head) ? NULL : list->head.next);
-}
-
-/** Get last item in list.
- *
- * @param list Pointer to list_t structure.
- *
- * @return Head item of the list.
- * @return NULL if the list is empty.
- *
- */
-static inline link_t *list_last(const list_t *list)
-{
-	return (list->head.prev == &list->head) ? NULL : list->head.prev;
-}
-
-/** Get next item in list.
- *
- * @param link Current item link
- * @param list List containing @a link
- *
- * @return Next item or NULL if @a link is the last item.
- */
-static inline link_t *list_next(const link_t *link, const list_t *list)
-{
-	return (link->next == &list->head) ? NULL : link->next;
-}
-
-/** Get previous item in list.
- *
- * @param link Current item link
- * @param list List containing @a link
- *
- * @return Previous item or NULL if @a link is the first item.
- */
-static inline link_t *list_prev(const link_t *link, const list_t *list)
-{
-	return (link->prev == &list->head) ? NULL : link->prev;
-}
-
-/** Split or concatenate headless doubly-linked circular list
- *
- * Split or concatenate headless doubly-linked circular list.
- *
- * Note that the algorithm works both directions:
- * concatenates splitted lists and splits concatenated lists.
- *
- * @param part1 Pointer to link_t structure leading the first
- *              (half of the headless) list.
- * @param part2 Pointer to link_t structure leading the second
- *              (half of the headless) list.
- *
- */
-_NO_TRACE static inline void headless_list_split_or_concat(link_t *part1, link_t *part2)
-{
-	part1->prev->next = part2;
-	part2->prev->next = part1;
-
-	link_t *hlp = part1->prev;
-
-	part1->prev = part2->prev;
-	part2->prev = hlp;
-}
-
-/** Split headless doubly-linked circular list
- *
- * Split headless doubly-linked circular list.
- *
- * @param part1 Pointer to link_t structure leading
- *              the first half of the headless list.
- * @param part2 Pointer to link_t structure leading
- *              the second half of the headless list.
- *
- */
-_NO_TRACE static inline void headless_list_split(link_t *part1, link_t *part2)
-{
-	headless_list_split_or_concat(part1, part2);
-}
-
-/** Concatenate two headless doubly-linked circular lists
- *
- * Concatenate two headless doubly-linked circular lists.
- *
- * @param part1 Pointer to link_t structure leading
- *              the first headless list.
- * @param part2 Pointer to link_t structure leading
- *              the second headless list.
- *
- */
-_NO_TRACE static inline void headless_list_concat(link_t *part1, link_t *part2)
-{
-	headless_list_split_or_concat(part1, part2);
-}
-
-/** Concatenate two lists
- *
- * Concatenate lists @a list1 and @a list2, producing a single
- * list @a list1 containing items from both (in @a list1, @a list2
- * order) and empty list @a list2.
- *
- * @param list1		First list and concatenated output
- * @param list2 	Second list and empty output.
- *
- */
-_NO_TRACE static inline void list_concat(list_t *list1, list_t *list2)
-{
-	list_splice(list2, list1->head.prev);
-}
-
-/** Get n-th item in a list.
- *
- * @param list Pointer to link_t structure representing the list.
- * @param n    Item number (indexed from zero).
- *
- * @return n-th item of the list.
- * @return NULL if no n-th item found.
- *
- */
-static inline link_t *list_nth(const list_t *list, size_t n)
-{
-	size_t cnt = 0;
-
-	link_t *link = list_first(list);
-	while (link != NULL) {
-		if (cnt == n)
-			return link;
-
-		cnt++;
-		link = list_next(link, list);
-	}
-
-	return NULL;
-}
-
-/** Verify that argument type is a pointer to link_t (at compile time).
- *
- * This can be used to check argument type in a macro.
- */
-static inline const void *list_link_to_void(const link_t *link)
-{
-	return link;
-}
-
-/** Determine if link is used.
- *
- * @param link Link
- * @return @c true if link is used, @c false if not.
- */
-static inline bool link_used(link_t *link)
-{
-	if (link->prev == NULL && link->next == NULL)
-		return false;
-
-	assert(link->prev != NULL && link->next != NULL);
-	return true;
-}
-
-static inline void *list_pop_internal(list_t *list, ptrdiff_t offset)
-{
-	link_t *tmp = list_first(list);
-	if (tmp == NULL)
-		return NULL;
-
-	list_remove(tmp);
-	return (void *) (((uint8_t *) tmp) - offset);
-}
-
-__HELENOS_DECLS_END;
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/adt/odict.h
===================================================================
--- uspace/lib/c/include/adt/odict.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,1 +1,0 @@
-../../../../../kernel/generic/include/adt/odict.h
Index: pace/lib/c/include/bsearch.h
===================================================================
--- uspace/lib/c/include/bsearch.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,51 +1,0 @@
-/*
- * Copyright (c) 2018 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_BSEARCH_H_
-#define _LIBC_BSEARCH_H_
-
-#include <stddef.h>
-#include <_bits/decls.h>
-
-__C_DECLS_BEGIN;
-
-extern void *bsearch(const void *, const void *, size_t, size_t,
-    int (*)(const void *, const void *));
-
-__C_DECLS_END;
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/clipboard.h
===================================================================
--- uspace/lib/c/include/clipboard.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,46 +1,0 @@
-/*
- * Copyright (c) 2009 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_CLIPBOARD_H_
-#define _LIBC_CLIPBOARD_H_
-
-#include <errno.h>
-
-extern errno_t clipboard_put_str(const char *);
-extern errno_t clipboard_get_str(char **);
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/corecfg.h
===================================================================
--- uspace/lib/c/include/corecfg.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,48 +1,0 @@
-/*
- * Copyright (c) 2013 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_CORECFG_H_
-#define _LIBC_CORECFG_H_
-
-#include <errno.h>
-#include <stdbool.h>
-
-extern errno_t corecfg_init(void);
-extern errno_t corecfg_set_enable(bool);
-extern errno_t corecfg_get_enable(bool *);
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/ctype.h
===================================================================
--- uspace/lib/c/include/ctype.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,51 +1,0 @@
-/*
- * Copyright (c) 2006 Josef Cejka
- * 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.
- */
-
-#ifndef _LIBC_CTYPE_H_
-#define _LIBC_CTYPE_H_
-
-#include <_bits/decls.h>
-
-__C_DECLS_BEGIN;
-int islower(int);
-int isupper(int);
-int isalpha(int);
-int isdigit(int);
-int isalnum(int);
-int isblank(int);
-int iscntrl(int);
-int isprint(int);
-int isgraph(int);
-int isspace(int);
-int ispunct(int);
-int isxdigit(int);
-int tolower(int);
-int toupper(int);
-__C_DECLS_END;
-
-#endif
Index: uspace/lib/c/include/dbgcon.h
===================================================================
--- uspace/lib/c/include/dbgcon.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/c/include/dbgcon.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,45 @@
+/*
+ * 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 libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBC_DBGCON_H_
+#define _LIBC_DBGCON_H_
+
+#include <stdbool.h>
+
+extern bool dbgcon_enable(void);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/c/include/device/hw_res.h
===================================================================
--- uspace/lib/c/include/device/hw_res.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/include/device/hw_res.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2010 Lenka Trochtova
  * All rights reserved.
@@ -56,4 +57,5 @@
 	HW_RES_DMA_CHANNEL_SETUP,
 	HW_RES_DMA_CHANNEL_REMAIN,
+	HW_RES_GET_FLAGS
 } hw_res_method_t;
 
@@ -116,4 +118,9 @@
 }
 
+typedef enum {
+	/** This is an PCI/ISA bridge, not 'classic' ISA bus */
+	hwf_isa_bridge = 0x1
+} hw_res_flags_t;
+
 extern errno_t hw_res_get_resource_list(async_sess_t *, hw_resource_list_t *);
 extern errno_t hw_res_enable_interrupt(async_sess_t *, int);
@@ -124,4 +131,5 @@
     uint32_t, uint8_t);
 extern errno_t hw_res_dma_channel_remain(async_sess_t *, unsigned, size_t *);
+extern errno_t hw_res_get_flags(async_sess_t *, hw_res_flags_t *);
 
 #endif
Index: uspace/lib/c/include/fibril_synch.h
===================================================================
--- uspace/lib/c/include/fibril_synch.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/include/fibril_synch.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -153,5 +153,17 @@
 extern void __fibril_synch_fini(void);
 
-extern void fibril_mutex_initialize(fibril_mutex_t *);
+/** Initialize fibril mutex.
+ *
+ * Kept as in-line to allow constexpr marker for C++ library where this
+ * is used by C++ mutex type (list initialization are two assignments
+ * so it is actually reasonable to have this inlined).
+ */
+static inline __CONSTEXPR void fibril_mutex_initialize(fibril_mutex_t *fm)
+{
+	fm->oi.owned_by = NULL;
+	fm->counter = 1;
+	list_initialize(&fm->waiters);
+}
+
 extern void fibril_mutex_lock(fibril_mutex_t *);
 extern bool fibril_mutex_trylock(fibril_mutex_t *);
Index: pace/lib/c/include/gsort.h
===================================================================
--- uspace/lib/c/include/gsort.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,48 +1,0 @@
-/*
- * Copyright (c) 2005 Sergey Bondari
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_SORT_H_
-#define _LIBC_SORT_H_
-
-#include <stddef.h>
-#include <stdbool.h>
-
-typedef int (*sort_cmp_t)(void *, void *, void *);
-
-extern bool gsort(void *, size_t, size_t, sort_cmp_t, void *);
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/io/charfield.h
===================================================================
--- uspace/lib/c/include/io/charfield.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,107 +1,0 @@
-/*
- * Copyright (c) 2006 Josef Cejka
- * Copyright (c) 2011 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 libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_IO_CHARFIELD_H_
-#define _LIBC_IO_CHARFIELD_H_
-
-#include <stdbool.h>
-#include <uchar.h>
-#include <io/color.h>
-#include <io/style.h>
-#include <io/pixel.h>
-
-typedef enum {
-	CHAR_FLAG_NONE = 0,
-	CHAR_FLAG_DIRTY = 1
-} char_flags_t;
-
-typedef enum {
-	CHAR_ATTR_STYLE,
-	CHAR_ATTR_INDEX,
-	CHAR_ATTR_RGB
-} char_attr_type_t;
-
-typedef struct {
-	console_color_t bgcolor;
-	console_color_t fgcolor;
-	console_color_attr_t attr;
-} char_attr_index_t;
-
-typedef struct {
-	pixel_t bgcolor;
-	pixel_t fgcolor;
-} char_attr_rgb_t;
-
-typedef union {
-	console_style_t style;
-	char_attr_index_t index;
-	char_attr_rgb_t rgb;
-} char_attr_val_t;
-
-typedef struct {
-	char_attr_type_t type;
-	char_attr_val_t val;
-} char_attrs_t;
-
-typedef struct {
-	char32_t ch;
-	char_attrs_t attrs;
-	char_flags_t flags;
-} charfield_t;
-
-static inline bool attrs_same(char_attrs_t a1, char_attrs_t a2)
-{
-	if (a1.type != a2.type)
-		return false;
-
-	switch (a1.type) {
-	case CHAR_ATTR_STYLE:
-		return (a1.val.style == a2.val.style);
-	case CHAR_ATTR_INDEX:
-		return (a1.val.index.bgcolor == a2.val.index.bgcolor) &&
-		    (a1.val.index.fgcolor == a2.val.index.fgcolor) &&
-		    (a1.val.index.attr == a2.val.index.attr);
-	case CHAR_ATTR_RGB:
-		return (a1.val.rgb.bgcolor == a2.val.rgb.bgcolor) &&
-		    (a1.val.rgb.fgcolor == a2.val.rgb.fgcolor);
-	}
-
-	return false;
-}
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/io/chargrid.h
===================================================================
--- uspace/lib/c/include/io/chargrid.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,103 +1,0 @@
-/*
- * Copyright (c) 2011 Martin Decky
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/**
- * @file
- */
-
-#ifndef _LIBC_IO_CHARGRID_H_
-#define _LIBC_IO_CHARGRID_H_
-
-#include <io/charfield.h>
-#include <types/common.h>
-#include <stddef.h>
-
-typedef enum {
-	CHARGRID_FLAG_NONE = 0,
-	CHARGRID_FLAG_SHARED = 1
-} chargrid_flag_t;
-
-typedef struct {
-	size_t size;            /**< Structure size */
-	chargrid_flag_t flags;  /**< Screenbuffer flags */
-
-	sysarg_t cols;          /**< Number of columns */
-	sysarg_t rows;          /**< Number of rows */
-
-	sysarg_t col;           /**< Current column */
-	sysarg_t row;           /**< Current row */
-	bool cursor_visible;    /**< Cursor visibility */
-
-	char_attrs_t attrs;     /**< Current attributes */
-
-	sysarg_t top_row;       /**< The first row in the cyclic buffer */
-	charfield_t data[];     /**< Screen contents (cyclic buffer) */
-} chargrid_t;
-
-static inline charfield_t *chargrid_charfield_at(chargrid_t *chargrid,
-    sysarg_t col, sysarg_t row)
-{
-	return chargrid->data +
-	    ((row + chargrid->top_row) % chargrid->rows) * chargrid->cols +
-	    col;
-}
-
-extern chargrid_t *chargrid_create(sysarg_t, sysarg_t,
-    chargrid_flag_t);
-extern void chargrid_destroy(chargrid_t *);
-
-extern bool chargrid_cursor_at(chargrid_t *, sysarg_t, sysarg_t);
-
-extern sysarg_t chargrid_get_top_row(chargrid_t *);
-
-extern sysarg_t chargrid_putuchar(chargrid_t *, char32_t, bool);
-extern sysarg_t chargrid_newline(chargrid_t *);
-extern sysarg_t chargrid_tabstop(chargrid_t *, sysarg_t);
-extern sysarg_t chargrid_backspace(chargrid_t *);
-
-extern void chargrid_clear(chargrid_t *);
-extern void chargrid_clear_row(chargrid_t *, sysarg_t);
-
-extern void chargrid_set_cursor(chargrid_t *, sysarg_t, sysarg_t);
-extern void chargrid_set_cursor_visibility(chargrid_t *, bool);
-extern bool chargrid_get_cursor_visibility(chargrid_t *);
-
-extern void chargrid_get_cursor(chargrid_t *, sysarg_t *, sysarg_t *);
-
-extern void chargrid_set_style(chargrid_t *, console_style_t);
-extern void chargrid_set_color(chargrid_t *, console_color_t,
-    console_color_t, console_color_attr_t);
-extern void chargrid_set_rgb_color(chargrid_t *, pixel_t, pixel_t);
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/io/color.h
===================================================================
--- uspace/lib/c/include/io/color.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,58 +1,0 @@
-/*
- * Copyright (c) 2008 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_IO_COLOR_H_
-#define _LIBC_IO_COLOR_H_
-
-typedef enum {
-	COLOR_BLACK   = 0,
-	COLOR_BLUE    = 1,
-	COLOR_GREEN   = 2,
-	COLOR_CYAN    = 3,
-	COLOR_RED     = 4,
-	COLOR_MAGENTA = 5,
-	COLOR_YELLOW  = 6,
-	COLOR_WHITE   = 7
-} console_color_t;
-
-typedef enum {
-	CATTR_NORMAL = 0,
-	CATTR_BRIGHT = 8,
-	CATTR_BLINK  = 16
-} console_color_attr_t;
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/io/con_srv.h
===================================================================
--- uspace/lib/c/include/io/con_srv.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,98 +1,0 @@
-/*
- * Copyright (c) 2021 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_CON_SRV_H_
-#define _LIBC_CON_SRV_H_
-
-#include <adt/list.h>
-#include <async.h>
-#include <fibril_synch.h>
-#include <io/charfield.h>
-#include <io/color.h>
-#include <io/concaps.h>
-#include <io/cons_event.h>
-#include <io/pixel.h>
-#include <io/style.h>
-#include <stdbool.h>
-#include <time.h>
-#include <stddef.h>
-
-typedef struct con_ops con_ops_t;
-
-/** Service setup (per sevice) */
-typedef struct {
-	con_ops_t *ops;
-	void *sarg;
-	/** Period to check for abort */
-	usec_t abort_timeout;
-	bool aborted;
-} con_srvs_t;
-
-/** Server structure (per client session) */
-typedef struct {
-	con_srvs_t *srvs;
-	async_sess_t *client_sess;
-	void *carg;
-} con_srv_t;
-
-struct con_ops {
-	errno_t (*open)(con_srvs_t *, con_srv_t *);
-	errno_t (*close)(con_srv_t *);
-	errno_t (*read)(con_srv_t *, void *, size_t, size_t *);
-	errno_t (*write)(con_srv_t *, void *, size_t, size_t *);
-	void (*sync)(con_srv_t *);
-	void (*clear)(con_srv_t *);
-	void (*set_pos)(con_srv_t *, sysarg_t col, sysarg_t row);
-	errno_t (*get_pos)(con_srv_t *, sysarg_t *, sysarg_t *);
-	errno_t (*get_size)(con_srv_t *, sysarg_t *, sysarg_t *);
-	errno_t (*get_color_cap)(con_srv_t *, console_caps_t *);
-	void (*set_style)(con_srv_t *, console_style_t);
-	void (*set_color)(con_srv_t *, console_color_t, console_color_t,
-	    console_color_attr_t);
-	void (*set_rgb_color)(con_srv_t *, pixel_t, pixel_t);
-	void (*set_cursor_visibility)(con_srv_t *, bool);
-	errno_t (*get_event)(con_srv_t *, cons_event_t *);
-	errno_t (*map)(con_srv_t *, sysarg_t, sysarg_t, charfield_t **);
-	void (*unmap)(con_srv_t *);
-	void (*update)(con_srv_t *, sysarg_t, sysarg_t, sysarg_t, sysarg_t);
-};
-
-extern void con_srvs_init(con_srvs_t *);
-
-extern errno_t con_conn(ipc_call_t *, con_srvs_t *);
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/io/concaps.h
===================================================================
--- uspace/lib/c/include/io/concaps.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,48 +1,0 @@
-/*
- * Copyright (c) 2012 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 libcipc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_IO_CONCAPS_H_
-#define _LIBC_IO_CONCAPS_H_
-
-typedef enum {
-	CONSOLE_CAP_NONE = 0,
-	CONSOLE_CAP_STYLE = 1,
-	CONSOLE_CAP_INDEXED = 2,
-	CONSOLE_CAP_RGB = 4
-} console_caps_t;
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/io/cons_event.h
===================================================================
--- uspace/lib/c/include/io/cons_event.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,66 +1,0 @@
-/*
- * Copyright (c) 2013 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_IO_CONS_EVENT_H_
-#define _LIBC_IO_CONS_EVENT_H_
-
-#include <adt/list.h>
-#include <io/kbd_event.h>
-#include <io/pos_event.h>
-
-typedef enum {
-	/** Key event */
-	CEV_KEY,
-	/** Position event */
-	CEV_POS
-} cons_event_type_t;
-
-/** Console event structure. */
-typedef struct {
-	/** List handle */
-	link_t link;
-
-	/** Event type */
-	cons_event_type_t type;
-
-	union {
-		kbd_event_t key;
-		pos_event_t pos;
-	} ev;
-} cons_event_t;
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/io/console.h
===================================================================
--- uspace/lib/c/include/io/console.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,98 +1,0 @@
-/*
- * Copyright (c) 2021 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_IO_CONSOLE_H_
-#define _LIBC_IO_CONSOLE_H_
-
-#include <time.h>
-#include <io/charfield.h>
-#include <io/concaps.h>
-#include <io/kbd_event.h>
-#include <io/cons_event.h>
-#include <io/keycode.h>
-#include <async.h>
-#include <stdbool.h>
-#include <stdio.h>
-
-/** Console control structure. */
-typedef struct {
-	/** Console input file */
-	FILE *input;
-
-	/** Console output file */
-	FILE *output;
-
-	/** Console input session */
-	async_sess_t *input_sess;
-
-	/** Console output session */
-	async_sess_t *output_sess;
-
-	/** Input request call with timeout */
-	ipc_call_t input_call;
-
-	/** Input response with timeout */
-	aid_t input_aid;
-} console_ctrl_t;
-
-extern console_ctrl_t *console_init(FILE *, FILE *);
-extern void console_done(console_ctrl_t *);
-extern bool console_kcon(void);
-
-extern void console_flush(console_ctrl_t *);
-extern void console_clear(console_ctrl_t *);
-
-extern errno_t console_get_size(console_ctrl_t *, sysarg_t *, sysarg_t *);
-extern errno_t console_get_pos(console_ctrl_t *, sysarg_t *, sysarg_t *);
-extern void console_set_pos(console_ctrl_t *, sysarg_t, sysarg_t);
-
-extern void console_set_style(console_ctrl_t *, uint8_t);
-extern void console_set_color(console_ctrl_t *, uint8_t, uint8_t, uint8_t);
-extern void console_set_rgb_color(console_ctrl_t *, uint32_t, uint32_t);
-
-extern void console_cursor_visibility(console_ctrl_t *, bool);
-extern errno_t console_get_color_cap(console_ctrl_t *, sysarg_t *);
-extern errno_t console_get_event(console_ctrl_t *, cons_event_t *);
-extern errno_t console_get_event_timeout(console_ctrl_t *, cons_event_t *,
-    usec_t *);
-extern errno_t console_map(console_ctrl_t *, sysarg_t, sysarg_t,
-    charfield_t **);
-extern void console_unmap(console_ctrl_t *, charfield_t *);
-extern errno_t console_update(console_ctrl_t *, sysarg_t, sysarg_t, sysarg_t,
-    sysarg_t);
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/io/input.h
===================================================================
--- uspace/lib/c/include/io/input.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,65 +1,0 @@
-/*
- * Copyright (c) 2012 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_IO_INPUT_H_
-#define _LIBC_IO_INPUT_H_
-
-#include <async.h>
-#include <io/kbd_event.h>
-
-struct input_ev_ops;
-
-typedef struct {
-	async_sess_t *sess;
-	struct input_ev_ops *ev_ops;
-	void *user;
-} input_t;
-
-typedef struct input_ev_ops {
-	errno_t (*active)(input_t *);
-	errno_t (*deactive)(input_t *);
-	errno_t (*key)(input_t *, kbd_event_type_t, keycode_t, keymod_t, char32_t);
-	errno_t (*move)(input_t *, int, int);
-	errno_t (*abs_move)(input_t *, unsigned, unsigned, unsigned, unsigned);
-	errno_t (*button)(input_t *, int, int);
-} input_ev_ops_t;
-
-extern errno_t input_open(async_sess_t *, input_ev_ops_t *, void *, input_t **);
-extern void input_close(input_t *);
-extern errno_t input_activate(input_t *);
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/io/kbd_event.h
===================================================================
--- uspace/lib/c/include/io/kbd_event.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,68 +1,0 @@
-/*
- * Copyright (c) 2012 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_IO_KBD_EVENT_H_
-#define _LIBC_IO_KBD_EVENT_H_
-
-#include <adt/list.h>
-#include <inttypes.h>
-#include <io/keycode.h>
-
-typedef enum {
-	KEY_PRESS,
-	KEY_RELEASE
-} kbd_event_type_t;
-
-/** Console event structure. */
-typedef struct {
-	/** List handle */
-	link_t link;
-
-	/** Press or release event. */
-	kbd_event_type_t type;
-
-	/** Keycode of the key that was pressed or released. */
-	keycode_t key;
-
-	/** Bitmask of modifiers held. */
-	keymod_t mods;
-
-	/** The character that was generated or '\0' for none. */
-	char32_t c;
-} kbd_event_t;
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/io/keycode.h
===================================================================
--- uspace/lib/c/include/io/keycode.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,223 +1,0 @@
-/*
- * Copyright (c) 2009 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_IO_KEYCODE_H_
-#define _LIBC_IO_KEYCODE_H_
-
-/** Keycode definitions.
- *
- * A keycode identifies a key by its position on the keyboard, rather
- * than by its label. For human readability, key positions are noted
- * with the key label on a keyboard with US layout. This label has
- * nothing to do with the character, that the key produces
- * -- this is determined by the keymap.
- *
- * The keyboard model reflects a standard PC keyboard layout.
- * Non-standard keyboards need to be mapped to this model in some
- * logical way. Scancodes are mapped to keycodes with a scanmap.
- *
- * For easier mapping to the model and to emphasize the nature of keycodes,
- * they really are organized here by position, rather than by label.
- */
-typedef enum {
-
-	/* Main block row 1 */
-
-	KC_BACKTICK = 1,
-
-	KC_1,
-	KC_2,
-	KC_3,
-	KC_4,
-	KC_5,
-	KC_6,
-	KC_7,
-	KC_8,
-	KC_9,
-	KC_0,
-
-	KC_MINUS,
-	KC_EQUALS,
-	KC_BACKSPACE,
-
-	/* Main block row 2 */
-
-	KC_TAB,
-
-	KC_Q,
-	KC_W,
-	KC_E,
-	KC_R,
-	KC_T,
-	KC_Y,
-	KC_U,
-	KC_I,
-	KC_O,
-	KC_P,
-
-	KC_LBRACKET,
-	KC_RBRACKET,
-
-	/* Main block row 3 */
-
-	KC_CAPS_LOCK,
-
-	KC_A,
-	KC_S,
-	KC_D,
-	KC_F,
-	KC_G,
-	KC_H,
-	KC_J,
-	KC_K,
-	KC_L,
-
-	KC_SEMICOLON,
-	KC_QUOTE,
-	KC_BACKSLASH,
-	KC_HASH,
-
-	KC_ENTER,
-
-	/* Main block row 4 */
-
-	KC_LSHIFT,
-
-	KC_Z,
-	KC_X,
-	KC_C,
-	KC_V,
-	KC_B,
-	KC_N,
-	KC_M,
-
-	KC_COMMA,
-	KC_PERIOD,
-	KC_SLASH,
-
-	KC_RSHIFT,
-
-	/* Main block row 5 */
-
-	KC_LCTRL,
-	KC_LALT,
-	KC_SPACE,
-	KC_RALT,
-	KC_RCTRL,
-
-	/* Function keys block */
-
-	KC_ESCAPE,
-
-	KC_F1,
-	KC_F2,
-	KC_F3,
-	KC_F4,
-	KC_F5,
-	KC_F6,
-	KC_F7,
-	KC_F8,
-	KC_F9,
-	KC_F10,
-	KC_F11,
-	KC_F12,
-
-	KC_PRTSCR,
-	KC_SYSREQ,
-	KC_SCROLL_LOCK,
-	KC_PAUSE,
-	KC_BREAK,
-
-	/* Cursor keys block */
-
-	KC_INSERT,
-	KC_HOME,
-	KC_PAGE_UP,
-
-	KC_DELETE,
-	KC_END,
-	KC_PAGE_DOWN,
-
-	KC_UP,
-	KC_LEFT,
-	KC_DOWN,
-	KC_RIGHT,
-
-	/* Numeric block */
-
-	KC_NUM_LOCK,
-	KC_NSLASH,
-	KC_NTIMES,
-	KC_NMINUS,
-
-	KC_NPLUS,
-	KC_NENTER,
-
-	KC_N7,
-	KC_N8,
-	KC_N9,
-
-	KC_N4,
-	KC_N5,
-	KC_N6,
-
-	KC_N1,
-	KC_N2,
-	KC_N3,
-
-	KC_N0,
-	KC_NPERIOD
-
-} keycode_t;
-
-typedef enum {
-	KM_LSHIFT      = 0x001,
-	KM_RSHIFT      = 0x002,
-	KM_LCTRL       = 0x004,
-	KM_RCTRL       = 0x008,
-	KM_LALT        = 0x010,
-	KM_RALT        = 0x020,
-	KM_CAPS_LOCK   = 0x040,
-	KM_NUM_LOCK    = 0x080,
-	KM_SCROLL_LOCK = 0x100,
-
-	KM_SHIFT       = KM_LSHIFT | KM_RSHIFT,
-	KM_CTRL        = KM_LCTRL | KM_RCTRL,
-	KM_ALT         = KM_LALT | KM_RALT
-} keymod_t;
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/io/output.h
===================================================================
--- uspace/lib/c/include/io/output.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,59 +1,0 @@
-/*
- * Copyright (c) 2011 Martin Decky
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_IO_OUTPUT_H_
-#define _LIBC_IO_OUTPUT_H_
-
-#include <ipc/output.h>
-#include <io/chargrid.h>
-#include <io/console.h>
-
-extern errno_t output_yield(async_sess_t *);
-extern errno_t output_claim(async_sess_t *);
-extern errno_t output_get_dimensions(async_sess_t *, sysarg_t *, sysarg_t *);
-extern errno_t output_get_caps(async_sess_t *, console_caps_t *);
-
-extern frontbuf_handle_t output_frontbuf_create(async_sess_t *, chargrid_t *);
-
-extern errno_t output_cursor_update(async_sess_t *, frontbuf_handle_t);
-extern errno_t output_set_style(async_sess_t *, console_style_t);
-
-extern errno_t output_update(async_sess_t *, frontbuf_handle_t);
-extern errno_t output_damage(async_sess_t *, frontbuf_handle_t,
-    sysarg_t, sysarg_t, sysarg_t, sysarg_t);
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/io/pos_event.h
===================================================================
--- uspace/lib/c/include/io/pos_event.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,59 +1,0 @@
-/*
- * Copyright (c) 2012 Petr Koupy
- * Copyright (c) 2013 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_IO_POS_EVENT_H_
-#define _LIBC_IO_POS_EVENT_H_
-
-#include <types/common.h>
-
-typedef enum {
-	POS_UPDATE,
-	POS_PRESS,
-	POS_RELEASE
-} pos_event_type_t;
-
-/** Positioning device event */
-typedef struct {
-	sysarg_t pos_id;
-	pos_event_type_t type;
-	sysarg_t btn_num;
-	sysarg_t hpos;
-	sysarg_t vpos;
-} pos_event_t;
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/io/printf_core.h
===================================================================
--- uspace/lib/c/include/io/printf_core.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,59 +1,0 @@
-/*
- * Copyright (c) 2006 Josef Cejka
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_PRINTF_CORE_H_
-#define _LIBC_PRINTF_CORE_H_
-
-#include <stddef.h>
-#include <stdarg.h>
-#include <uchar.h>
-
-/** Structure for specifying output methods for different printf clones. */
-typedef struct {
-	/* String output function, returns number of printed characters or EOF */
-	int (*str_write)(const char *, size_t, void *);
-
-	/* Wide string output function, returns number of printed characters or EOF */
-	int (*wstr_write)(const char32_t *, size_t, void *);
-
-	/* User data - output stream specification, state, locks, etc. */
-	void *data;
-} printf_spec_t;
-
-extern int printf_core(const char *, printf_spec_t *, va_list);
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/ipc/clipboard.h
===================================================================
--- uspace/lib/c/include/ipc/clipboard.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,54 +1,0 @@
-/*
- * Copyright (c) 2009 Martin Decky
- * 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 libcipc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_IPC_CLIPBOARD_H_
-#define _LIBC_IPC_CLIPBOARD_H_
-
-#include <ipc/common.h>
-
-typedef enum {
-	CLIPBOARD_PUT_DATA = IPC_FIRST_USER_METHOD,
-	CLIPBOARD_GET_DATA,
-	CLIPBOARD_CONTENT
-} clipboard_request_t;
-
-typedef enum {
-	CLIPBOARD_TAG_NONE,
-	CLIPBOARD_TAG_DATA
-} clipboard_tag_t;
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/ipc/console.h
===================================================================
--- uspace/lib/c/include/ipc/console.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,60 +1,0 @@
-/*
- * Copyright (c) 2021 Jiri Svoboda
- * Copyright (c) 2006 Josef Cejka
- * 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 libcipc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_IPC_CONSOLE_H_
-#define _LIBC_IPC_CONSOLE_H_
-
-#include <ipc/vfs.h>
-
-typedef enum {
-	CONSOLE_GET_SIZE = VFS_OUT_LAST,
-	CONSOLE_GET_COLOR_CAP,
-	CONSOLE_GET_EVENT,
-	CONSOLE_GET_POS,
-	CONSOLE_SET_POS,
-	CONSOLE_CLEAR,
-	CONSOLE_SET_STYLE,
-	CONSOLE_SET_COLOR,
-	CONSOLE_SET_RGB_COLOR,
-	CONSOLE_SET_CURSOR_VISIBILITY,
-	CONSOLE_MAP,
-	CONSOLE_UNMAP,
-	CONSOLE_UPDATE
-} console_request_t;
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/ipc/corecfg.h
===================================================================
--- uspace/lib/c/include/ipc/corecfg.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,46 +1,0 @@
-/*
- * Copyright (c) 2013 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 libcipc
- * @{
- */
-
-#ifndef _LIBC_IPC_CORECFG_H_
-#define _LIBC_IPC_CORECFG_H_
-
-#include <ipc/common.h>
-
-typedef enum {
-	CORECFG_GET_ENABLE = IPC_FIRST_USER_METHOD,
-	CORECFG_SET_ENABLE
-} corecfg_request_t;
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/ipc/input.h
===================================================================
--- uspace/lib/c/include/ipc/input.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,57 +1,0 @@
-/*
- * Copyright (c) 2011 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 libcipc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_IPC_INPUT_H_
-#define _LIBC_IPC_INPUT_H_
-
-#include <ipc/common.h>
-
-typedef enum {
-	INPUT_ACTIVATE = IPC_FIRST_USER_METHOD
-} input_request_t;
-
-typedef enum {
-	INPUT_EVENT_ACTIVE = IPC_FIRST_USER_METHOD,
-	INPUT_EVENT_DEACTIVE,
-	INPUT_EVENT_KEY,
-	INPUT_EVENT_MOVE,
-	INPUT_EVENT_ABS_MOVE,
-	INPUT_EVENT_BUTTON
-} input_notif_t;
-
-#endif
-
-/**
- * @}
- */
Index: pace/lib/c/include/ipc/ipc_test.h
===================================================================
--- uspace/lib/c/include/ipc/ipc_test.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,51 +1,0 @@
-/*
- * Copyright (c) 2018 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 libcipc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_IPC_IPC_TEST_H_
-#define _LIBC_IPC_IPC_TEST_H_
-
-#include <ipc/common.h>
-
-typedef enum {
-	IPC_TEST_PING = IPC_FIRST_USER_METHOD,
-	IPC_TEST_GET_RO_AREA_SIZE,
-	IPC_TEST_GET_RW_AREA_SIZE,
-	IPC_TEST_SHARE_IN_RO,
-	IPC_TEST_SHARE_IN_RW
-} ipc_test_request_t;
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/ipc/kbdev.h
===================================================================
--- uspace/lib/c/include/ipc/kbdev.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,55 +1,0 @@
-/*
- * Copyright (c) 2011 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 libcipc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_IPC_KBDEV_H_
-#define _LIBC_IPC_KBDEV_H_
-
-#include <ipc/common.h>
-#include <ipc/dev_iface.h>
-
-typedef enum {
-	KBDEV_YIELD = DEV_FIRST_CUSTOM_METHOD,
-	KBDEV_RECLAIM,
-	KBDEV_SET_IND
-} kbdev_request_t;
-
-typedef enum {
-	KBDEV_EVENT = IPC_FIRST_USER_METHOD
-} kbdev_notif_t;
-
-#endif
-
-/**
- * @}
- */
Index: pace/lib/c/include/ipc/mouseev.h
===================================================================
--- uspace/lib/c/include/ipc/mouseev.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,57 +1,0 @@
-/*
- * Copyright (c) 2009 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @brief
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_IPC_MOUSEEV_H_
-#define _LIBC_IPC_MOUSEEV_H_
-
-#include <ipc/common.h>
-#include <ipc/dev_iface.h>
-
-typedef enum {
-	MOUSEEV_YIELD = DEV_FIRST_CUSTOM_METHOD,
-	MOUSEEV_RECLAIM
-} mouseev_request_t;
-
-typedef enum {
-	MOUSEEV_MOVE_EVENT = IPC_FIRST_USER_METHOD,
-	MOUSEEV_ABS_MOVE_EVENT,
-	MOUSEEV_BUTTON_EVENT
-} mouseev_notif_t;
-
-#endif
-
-/**
- * @}
- */
Index: pace/lib/c/include/ipc/output.h
===================================================================
--- uspace/lib/c/include/ipc/output.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,64 +1,0 @@
-/*
- * Copyright (c) 2012 Martin Decky
- * 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 libcipc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_IPC_OUTPUT_H_
-#define _LIBC_IPC_OUTPUT_H_
-
-#include <ipc/common.h>
-
-typedef sysarg_t frontbuf_handle_t;
-
-typedef enum {
-	OUTPUT_YIELD = IPC_FIRST_USER_METHOD,
-	OUTPUT_CLAIM,
-	OUTPUT_GET_DIMENSIONS,
-	OUTPUT_GET_CAPS,
-
-	OUTPUT_FRONTBUF_CREATE,
-	OUTPUT_FRONTBUF_DESTROY,
-
-	OUTPUT_CURSOR_UPDATE,
-	OUTPUT_SET_STYLE,
-	OUTPUT_SET_COLOR,
-	OUTPUT_SET_RGB_COLOR,
-
-	OUTPUT_UPDATE,
-	OUTPUT_DAMAGE
-} output_request_t;
-
-#endif
-
-/**
- * @}
- */
Index: uspace/lib/c/include/ipc/services.h
===================================================================
--- uspace/lib/c/include/ipc/services.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/include/ipc/services.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2006 Jakub Jermar
  * All rights reserved.
@@ -54,5 +55,7 @@
 #define SERVICE_NAME_CLIPBOARD "clipboard"
 #define SERVICE_NAME_CORECFG  "corecfg"
+#define SERVICE_NAME_DISPCFG  "hid/display"
 #define SERVICE_NAME_DISPLAY  "hid/display"
+#define SERVICE_NAME_WNDMGT   "hid/display"
 #define SERVICE_NAME_DHCP     "net/dhcp"
 #define SERVICE_NAME_DNSR     "net/dnsr"
Index: pace/lib/c/include/ipc_test.h
===================================================================
--- uspace/lib/c/include/ipc_test.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,56 +1,0 @@
-/*
- * Copyright (c) 2018 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_IPC_TEST_H_
-#define _LIBC_IPC_TEST_H_
-
-#include <async.h>
-#include <errno.h>
-
-typedef struct {
-	async_sess_t *sess;
-} ipc_test_t;
-
-extern errno_t ipc_test_create(ipc_test_t **);
-extern void ipc_test_destroy(ipc_test_t *);
-extern errno_t ipc_test_ping(ipc_test_t *);
-extern errno_t ipc_test_get_ro_area_size(ipc_test_t *, size_t *);
-extern errno_t ipc_test_get_rw_area_size(ipc_test_t *, size_t *);
-extern errno_t ipc_test_share_in_ro(ipc_test_t *, size_t, const void **);
-extern errno_t ipc_test_share_in_rw(ipc_test_t *, size_t, void **);
-
-#endif
-
-/** @}
- */
Index: uspace/lib/c/include/loc.h
===================================================================
--- uspace/lib/c/include/loc.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/include/loc.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2009 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,15 +39,17 @@
 #include <async.h>
 #include <stdbool.h>
+#include <types/loc.h>
 
 typedef void (*loc_cat_change_cb_t)(void *);
 
-extern async_exch_t *loc_exchange_begin_blocking(iface_t);
-extern async_exch_t *loc_exchange_begin(iface_t);
+extern async_exch_t *loc_exchange_begin_blocking(void);
+extern async_exch_t *loc_exchange_begin(void);
 extern void loc_exchange_end(async_exch_t *);
 
-extern errno_t loc_server_register(const char *);
-extern errno_t loc_service_register(const char *, service_id_t *);
-extern errno_t loc_service_unregister(service_id_t);
-extern errno_t loc_service_add_to_cat(service_id_t, category_id_t);
+extern errno_t loc_server_register(const char *, loc_srv_t **);
+extern void loc_server_unregister(loc_srv_t *);
+extern errno_t loc_service_register(loc_srv_t *, const char *, service_id_t *);
+extern errno_t loc_service_unregister(loc_srv_t *, service_id_t);
+extern errno_t loc_service_add_to_cat(loc_srv_t *, service_id_t, category_id_t);
 
 extern errno_t loc_service_get_id(const char *, service_id_t *,
Index: uspace/lib/c/include/malloc.h
===================================================================
--- uspace/lib/c/include/malloc.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/include/malloc.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -39,21 +39,7 @@
 #include <_bits/decls.h>
 
-__C_DECLS_BEGIN;
-
-extern void *malloc(size_t size)
-    __attribute__((malloc));
-extern void *calloc(size_t nmemb, size_t size)
-    __attribute__((malloc));
-extern void *realloc(void *addr, size_t size)
-    __attribute__((warn_unused_result));
-extern void free(void *addr);
-
-__C_DECLS_END;
-
 #ifdef _HELENOS_SOURCE
 __HELENOS_DECLS_BEGIN;
 
-extern void *memalign(size_t align, size_t size)
-    __attribute__((malloc));
 extern void *heap_check(void);
 
Index: pace/lib/c/include/mem.h
===================================================================
--- uspace/lib/c/include/mem.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,60 +1,0 @@
-/*
- * Copyright (c) 2005 Martin Decky
- * Copyright (c) 2018 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_MEM_H_
-#define _LIBC_MEM_H_
-
-#include <stddef.h>
-#include <_bits/decls.h>
-
-__C_DECLS_BEGIN;
-
-extern void *memset(void *, int, size_t)
-    __attribute__((nonnull(1)));
-extern void *memcpy(void *, const void *, size_t)
-    __attribute__((nonnull(1, 2)));
-extern void *memmove(void *, const void *, size_t)
-    __attribute__((nonnull(1, 2)));
-extern int memcmp(const void *, const void *, size_t)
-    __attribute__((nonnull(1, 2)));
-extern void *memchr(const void *, int, size_t)
-    __attribute__((nonnull(1)));
-
-__C_DECLS_END;
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/qsort.h
===================================================================
--- uspace/lib/c/include/qsort.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,56 +1,0 @@
-/*
- * Copyright (c) 2017 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_QSORT_H_
-#define _LIBC_QSORT_H_
-
-#include <stddef.h>
-#include <_bits/decls.h>
-
-__C_DECLS_BEGIN;
-extern void qsort(void *, size_t, size_t, int (*)(const void *,
-    const void *));
-__C_DECLS_END;
-
-#ifdef _HELENOS_SOURCE
-__HELENOS_DECLS_BEGIN;
-extern void qsort_r(void *, size_t, size_t, int (*)(const void *,
-    const void *, void *), void *);
-__HELENOS_DECLS_END;
-#endif
-
-#endif
-
-/** @}
- */
Index: uspace/lib/c/include/rndgen.h
===================================================================
--- uspace/lib/c/include/rndgen.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/include/rndgen.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -47,4 +47,5 @@
 extern void rndgen_destroy(rndgen_t *);
 extern errno_t rndgen_uint8(rndgen_t *, uint8_t *);
+extern errno_t rndgen_uint16(rndgen_t *, uint16_t *);
 extern errno_t rndgen_uint32(rndgen_t *, uint32_t *);
 
Index: pace/lib/c/include/stdlib.h
===================================================================
--- uspace/lib/c/include/stdlib.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,116 +1,0 @@
-/*
- * Copyright (c) 2005 Martin Decky
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_STDLIB_H_
-#define _LIBC_STDLIB_H_
-
-#include <_bits/size_t.h>
-#include <_bits/wchar_t.h>
-#include <_bits/uchar.h>
-#include <_bits/decls.h>
-#include <bsearch.h>
-#include <malloc.h>
-#include <qsort.h>
-
-#define EXIT_SUCCESS 0
-#define EXIT_FAILURE 1
-
-#define RAND_MAX  714025
-
-#define MB_CUR_MAX 4
-
-__C_DECLS_BEGIN;
-
-/** Type returned by the div function */
-typedef struct {
-	/** Quotient */
-	int quot;
-	/** Remainder */
-	int rem;
-} div_t;
-
-/** Type returned by the ldiv function */
-typedef struct {
-	/** Quotient */
-	long quot;
-	/** Remainder */
-	long rem;
-} ldiv_t;
-
-/** Type returned by the lldiv function */
-typedef struct {
-	/** Quotient */
-	long long quot;
-	/** Remainder */
-	long long rem;
-} lldiv_t;
-
-extern long double strtold(const char *, char **);
-
-extern int rand(void);
-extern void srand(unsigned int);
-
-extern void abort(void) __attribute__((noreturn));
-extern int atexit(void (*)(void));
-extern void exit(int) __attribute__((noreturn));
-extern void _Exit(int) __attribute__((noreturn));
-extern int at_quick_exit(void (*)(void));
-extern void quick_exit(int);
-
-extern char *getenv(const char *);
-extern int system(const char *);
-
-extern int abs(int);
-extern long labs(long);
-extern long long llabs(long long);
-
-extern int atoi(const char *);
-extern long atol(const char *);
-extern long long atoll(const char *);
-
-extern long strtol(const char *__restrict__, char **__restrict__, int);
-extern long long strtoll(const char *__restrict__, char **__restrict__, int);
-extern unsigned long strtoul(const char *__restrict__, char **__restrict__, int);
-extern unsigned long long strtoull(const char *__restrict__, char **__restrict__, int);
-
-extern div_t div(int, int);
-extern ldiv_t ldiv(long, long);
-extern lldiv_t lldiv(long long, long long);
-
-__C_DECLS_END;
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/str.h
===================================================================
--- uspace/lib/c/include/str.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,158 +1,0 @@
-/*
- * Copyright (c) 2001-2004 Jakub Jermar
- * Copyright (c) 2005 Martin Decky
- * Copyright (c) 2011 Oleg Romanenko
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_STR_H_
-#define _LIBC_STR_H_
-
-#include <errno.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-
-#include <mem.h>
-#include <_bits/decls.h>
-
-#ifndef __cplusplus
-
-/* Common Unicode characters */
-#define U_SPECIAL      '?'
-
-/** No size limit constant */
-#define STR_NO_LIMIT  ((size_t) -1)
-
-/** Maximum size of a string containing @c length characters */
-#define STR_BOUNDS(length)  ((length) << 2)
-
-/**
- * Maximum size of a buffer needed to a string converted from space-padded
- * ASCII of size @a spa_size using spascii_to_str().
- */
-#define SPASCII_STR_BUFSIZE(spa_size) ((spa_size) + 1)
-
-#endif
-
-__HELENOS_DECLS_BEGIN;
-
-extern char32_t str_decode(const char *str, size_t *offset, size_t sz);
-extern char32_t str_decode_reverse(const char *str, size_t *offset, size_t sz);
-extern errno_t chr_encode(char32_t ch, char *str, size_t *offset, size_t sz);
-
-extern size_t str_size(const char *str);
-extern size_t wstr_size(const char32_t *str);
-
-extern size_t str_nsize(const char *str, size_t max_size);
-extern size_t wstr_nsize(const char32_t *str, size_t max_size);
-
-extern size_t str_lsize(const char *str, size_t max_len);
-extern size_t wstr_lsize(const char32_t *str, size_t max_len);
-
-extern size_t str_length(const char *str);
-extern size_t wstr_length(const char32_t *wstr);
-
-extern size_t str_nlength(const char *str, size_t size);
-extern size_t wstr_nlength(const char32_t *str, size_t size);
-
-extern size_t chr_width(char32_t ch);
-extern size_t str_width(const char *str);
-
-extern bool ascii_check(char32_t ch);
-extern bool chr_check(char32_t ch);
-
-extern int str_cmp(const char *s1, const char *s2);
-extern int str_lcmp(const char *s1, const char *s2, size_t max_len);
-extern int str_casecmp(const char *s1, const char *s2);
-extern int str_lcasecmp(const char *s1, const char *s2, size_t max_len);
-
-extern bool str_test_prefix(const char *s, const char *p);
-extern const char *str_suffix(const char *s, size_t prefix_length);
-
-extern void str_cpy(char *dest, size_t size, const char *src);
-extern void str_ncpy(char *dest, size_t size, const char *src, size_t n);
-extern void str_append(char *dest, size_t size, const char *src);
-
-extern errno_t spascii_to_str(char *dest, size_t size, const uint8_t *src, size_t n);
-extern void wstr_to_str(char *dest, size_t size, const char32_t *src);
-extern char *wstr_to_astr(const char32_t *src);
-extern void str_to_wstr(char32_t *dest, size_t dlen, const char *src);
-extern char32_t *str_to_awstr(const char *src);
-extern errno_t utf16_to_str(char *dest, size_t size, const uint16_t *src);
-extern errno_t str_to_utf16(uint16_t *dest, size_t dlen, const char *src);
-extern size_t utf16_wsize(const uint16_t *ustr);
-
-extern char *str_chr(const char *str, char32_t ch);
-extern char *str_rchr(const char *str, char32_t ch);
-extern char *str_str(const char *hs, const char *n);
-
-extern void str_rtrim(char *str, char32_t ch);
-extern void str_ltrim(char *str, char32_t ch);
-
-extern bool wstr_linsert(char32_t *str, char32_t ch, size_t pos, size_t max_pos);
-extern bool wstr_remove(char32_t *str, size_t pos);
-
-extern char *str_dup(const char *src);
-extern char *str_ndup(const char *src, size_t n);
-
-extern char *str_tok(char *, const char *, char **);
-
-extern errno_t str_uint8_t(const char *, const char **, unsigned int, bool,
-    uint8_t *);
-extern errno_t str_uint16_t(const char *, const char **, unsigned int, bool,
-    uint16_t *);
-extern errno_t str_uint32_t(const char *, const char **, unsigned int, bool,
-    uint32_t *);
-extern errno_t str_uint64_t(const char *, const char **, unsigned int, bool,
-    uint64_t *);
-extern errno_t str_size_t(const char *, const char **, unsigned int, bool,
-    size_t *);
-extern errno_t str_int64_t(const char *, const char **, unsigned int, bool,
-    int64_t *);
-
-extern void order_suffix(const uint64_t, uint64_t *, char *);
-extern void bin_order_suffix(const uint64_t, uint64_t *, const char **, bool);
-
-/*
- * TODO: Get rid of this.
- */
-
-extern long int strtol(const char *, char **, int);
-extern unsigned long strtoul(const char *, char **, int);
-
-__HELENOS_DECLS_END;
-
-#endif
-
-/** @}
- */
Index: pace/lib/c/include/str_error.h
===================================================================
--- uspace/lib/c/include/str_error.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,46 +1,0 @@
-/*
- * Copyright (c) 2010 Martin Decky
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup libc
- * @{
- */
-/** @file
- */
-
-#ifndef _LIBC_STRERROR_H_
-#define _LIBC_STRERROR_H_
-
-#include <errno.h>
-
-const char *str_error(errno_t);
-const char *str_error_name(errno_t);
-
-#endif
-
-/** @}
- */
Index: uspace/lib/c/include/types/loc.h
===================================================================
--- uspace/lib/c/include/types/loc.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/c/include/types/loc.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,48 @@
+/*
+ * 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 libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBC_TYPES_LOC_H_
+#define _LIBC_TYPES_LOC_H_
+
+#include <async.h>
+
+/** Server register with location service */
+typedef struct {
+	async_sess_t *sess;
+} loc_srv_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/c/include/types/uuid.h
===================================================================
--- uspace/lib/c/include/types/uuid.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/include/types/uuid.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -38,11 +38,15 @@
 #include <stdint.h>
 
-enum {
-	uuid_bytes = 16
-};
+#define _UUID_NODE_LEN 6
 
 /** Universally Unique Identifier */
+
 typedef struct {
-	uint8_t b[uuid_bytes];
+	uint32_t time_low;
+	uint16_t time_mid;
+	uint16_t time_hi_and_version;
+	uint8_t clock_seq_hi_and_reserved;
+	uint8_t clock_seq_low;
+	uint8_t node[_UUID_NODE_LEN];
 } uuid_t;
 
Index: uspace/lib/c/include/uuid.h
===================================================================
--- uspace/lib/c/include/uuid.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/include/uuid.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -43,4 +43,6 @@
 extern void uuid_encode(uuid_t *, uint8_t *);
 extern void uuid_decode(uint8_t *, uuid_t *);
+extern void uuid_encode_le(uuid_t *, uint8_t *);
+extern void uuid_decode_le(uint8_t *, uuid_t *);
 extern errno_t uuid_parse(const char *, uuid_t *, const char **);
 extern errno_t uuid_format(uuid_t *, char **, bool);
Index: uspace/lib/c/meson.build
===================================================================
--- uspace/lib/c/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 #
+# Copyright (c) 2024 Jiri Svoboda
 # Copyright (c) 2005 Martin Decky
 # Copyright (c) 2007 Jakub Jermar
@@ -41,4 +42,5 @@
 	root_path / 'abi' / 'arch' / UARCH / 'include',
 	root_path / 'abi' / 'include',
+	root_path / 'common' / 'include',
 ]
 
@@ -46,7 +48,4 @@
 
 allow_shared = true
-
-# FIXME: symlinks from uspace to kernel will break in future Meson version
-#        we should instead move the duplicated library parts into a shared location.
 
 uspace_lib_devel_install_script_text += 'mkdir -p "${DESTDIR}include/libc"'
@@ -54,4 +53,5 @@
 	_sdir = meson.current_source_dir() / idir
 	uspace_lib_devel_install_script_text += 'cp -R -L -T "@0@" "${DESTDIR}include/libc"'.format(_sdir)
+	exported_devel_files += ['include', _sdir, 'libc']
 endforeach
 
@@ -59,14 +59,29 @@
 
 src += files(
+	'common/adt/checksum.c',
+	'common/adt/circ_buf.c',
+	'common/adt/list.c',
+	'common/adt/hash_table.c',
+	'common/adt/odict.c',
+	'common/printf/printf_core.c',
+	'common/stdc/ctype.c',
+	'common/stdc/mem.c',
+	'common/stdc/bsearch.c',
+	'common/stdc/qsort.c',
+	'common/stdc/calloc.c',
+	'common/gsort.c',
+	'common/str.c',
+	'common/str_error.c',
+	'common/strtol.c',
+
 	'generic/libc.c',
+	'generic/adt/prodcons.c',
 	'generic/as.c',
 	'generic/ddi.c',
 	'generic/perm.c',
 	'generic/capa.c',
-	'generic/clipboard.c',
 	'generic/config.c',
 	'generic/context.c',
-	'generic/corecfg.c',
-	'generic/ctype.c',
+	'generic/dbgcon.c',
 	'generic/device/clock_dev.c',
 	'generic/device/hw_res.c',
@@ -80,13 +95,7 @@
 	'generic/event.c',
 	'generic/errno.c',
-	'generic/gsort.c',
 	'generic/inttypes.c',
-	'generic/ipc_test.c',
 	'generic/loc.c',
-	'generic/mem.c',
-	'generic/str.c',
 	'generic/string.c',
-	'generic/str_error.c',
-	'generic/strtol.c',
 	'generic/l18n/langs.c',
 	'generic/pcb.c',
@@ -96,8 +105,5 @@
 	'generic/imath.c',
 	'generic/io/asprintf.c',
-	'generic/io/input.c',
 	'generic/io/io.c',
-	'generic/io/chargrid.c',
-	'generic/io/output.c',
 	'generic/io/printf.c',
 	'generic/io/log.c',
@@ -108,7 +114,4 @@
 	'generic/io/vprintf.c',
 	'generic/io/vsnprintf.c',
-	'generic/io/printf_core.c',
-	'generic/io/con_srv.c',
-	'generic/io/console.c',
 	'generic/io/table.c',
 	'generic/irq.c',
@@ -137,10 +140,4 @@
 	'generic/loader.c',
 	'generic/getopt.c',
-	'generic/adt/checksum.c',
-	'generic/adt/circ_buf.c',
-	'generic/adt/list.c',
-	'generic/adt/hash_table.c',
-	'generic/adt/odict.c',
-	'generic/adt/prodcons.c',
 	'generic/time.c',
 	'generic/tmpfile.c',
@@ -158,6 +155,4 @@
 	'generic/stats.c',
 	'generic/assert.c',
-	'generic/bsearch.c',
-	'generic/qsort.c',
 	'generic/ubsan.c',
 	'generic/uuid.c',
@@ -186,4 +181,5 @@
 	'test/inttypes.c',
 	'test/io/table.c',
+	'test/loc.c',
 	'test/main.c',
 	'test/mem.c',
@@ -215,4 +211,5 @@
 	pic: false,
 )
+exported_devel_files += ['staticlib', libstartfiles, 'libstartfiles.a']
 
 if CONFIG_DEVEL_FILES
Index: uspace/lib/c/test/loc.c
===================================================================
--- uspace/lib/c/test/loc.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/c/test/loc.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <loc.h>
+#include <pcut/pcut.h>
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(loc);
+
+/** loc_server_register() can be called multiple times */
+PCUT_TEST(server_register)
+{
+	errno_t rc;
+	loc_srv_t *sa, *sb;
+	service_id_t svca, svcb;
+	char *na, *nb;
+	char *sna, *snb;
+
+	rc = loc_server_register("test-a", &sa);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_server_register("test-b", &sb);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	// XXX Without a unique name this is not reentrant
+	rc = loc_service_register(sa, "test/libc-service-a", &svca);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	// XXX Without a unique name this is not reentrant
+	rc = loc_service_register(sb, "test/libc-service-b", &svcb);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_get_name(svca, &na);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_STR_EQUALS("test/libc-service-a", na);
+
+	rc = loc_service_get_server_name(svca, &sna);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_STR_EQUALS("test-a", sna);
+
+	rc = loc_service_get_name(svcb, &nb);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_STR_EQUALS("test/libc-service-b", nb);
+
+	rc = loc_service_get_server_name(svcb, &snb);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_STR_EQUALS("test-b", snb);
+
+	rc = loc_service_unregister(sa, svca);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(sb, svcb);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	loc_server_unregister(sa);
+	loc_server_unregister(sb);
+}
+
+PCUT_EXPORT(loc);
Index: uspace/lib/c/test/main.c
===================================================================
--- uspace/lib/c/test/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2014 Vojtech Horky
  * All rights reserved.
@@ -42,4 +43,5 @@
 PCUT_IMPORT(imath);
 PCUT_IMPORT(inttypes);
+PCUT_IMPORT(loc);
 PCUT_IMPORT(mem);
 PCUT_IMPORT(odict);
Index: uspace/lib/c/test/string.c
===================================================================
--- uspace/lib/c/test/string.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/test/string.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -799,5 +799,11 @@
 PCUT_TEST(strndup_nonempty_short)
 {
+#pragma GCC diagnostic push
+	// Intentionally checking it works with _longer_ size than actual
+#if defined(__GNUC__) && (__GNUC__ >= 11)
+#pragma GCC diagnostic ignored "-Wstringop-overread"
+#endif
 	char *d = strndup("abc", 5);
+#pragma GCC diagnostic pop
 	PCUT_ASSERT_NOT_NULL(d);
 	PCUT_ASSERT_TRUE(d[0] == 'a');
Index: uspace/lib/c/test/uuid.c
===================================================================
--- uspace/lib/c/test/uuid.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/c/test/uuid.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -50,10 +50,10 @@
 static bool uuid_valid(uuid_t uuid)
 {
-	if (!(uuid.b[6] & 0x40)) {
+	if (!((uuid.time_hi_and_version & 0xf000) & 0x4000)) {
 		return false;
 	}
 
-	int f = (uuid.b[8] & 0x80) || (uuid.b[8] & 0x90);
-	f = f || (uuid.b[8] & 0xA0) || (uuid.b[8] & 0xB0);
+	int f = (uuid.clock_seq_hi_and_reserved & 0x80) || (uuid.clock_seq_hi_and_reserved & 0x90);
+	f = f || (uuid.clock_seq_hi_and_reserved & 0xA0) || (uuid.clock_seq_hi_and_reserved & 0xB0);
 	if (!f) {
 		return false;
Index: uspace/lib/clipboard/doc/doxygroups.h
===================================================================
--- uspace/lib/clipboard/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/clipboard/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,3 @@
+/** @addtogroup libclipboard libclipboard
+ * @ingroup libs
+ */
Index: uspace/lib/clipboard/include/clipboard.h
===================================================================
--- uspace/lib/clipboard/include/clipboard.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/clipboard/include/clipboard.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,46 @@
+/*
+ * 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 libclipboard
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBCLIPBOARD_CLIPBOARD_H_
+#define _LIBCLIPBOARD_CLIPBOARD_H_
+
+#include <errno.h>
+
+extern errno_t clipboard_put_str(const char *);
+extern errno_t clipboard_get_str(char **);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/clipboard/include/ipc/clipboard.h
===================================================================
--- uspace/lib/clipboard/include/ipc/clipboard.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/clipboard/include/ipc/clipboard.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009 Martin Decky
+ * 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 libcipc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBCLIPBOARD_IPC_CLIPBOARD_H_
+#define _LIBCLIPBOARD_IPC_CLIPBOARD_H_
+
+#include <ipc/common.h>
+
+typedef enum {
+	CLIPBOARD_PUT_DATA = IPC_FIRST_USER_METHOD,
+	CLIPBOARD_GET_DATA,
+	CLIPBOARD_CONTENT
+} clipboard_request_t;
+
+typedef enum {
+	CLIPBOARD_TAG_NONE,
+	CLIPBOARD_TAG_DATA
+} clipboard_tag_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/clipboard/meson.build
===================================================================
--- uspace/lib/clipboard/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/clipboard/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,29 @@
+#
+# 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.
+#
+
+src = files('src/clipboard.c')
Index: uspace/lib/clipboard/src/clipboard.c
===================================================================
--- uspace/lib/clipboard/src/clipboard.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/clipboard/src/clipboard.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,210 @@
+/*
+ * 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 libclipboard
+ * @{
+ */
+/** @file
+ * @brief System clipboard API.
+ *
+ * The clipboard data is managed by the clipboard service and it is shared by
+ * the entire system.
+ *
+ */
+
+#include <async.h>
+#include <clipboard.h>
+#include <errno.h>
+#include <fibril_synch.h>
+#include <ipc/clipboard.h>
+#include <ipc/services.h>
+#include <loc.h>
+#include <stdlib.h>
+#include <str.h>
+
+static FIBRIL_MUTEX_INITIALIZE(clip_mutex);
+static async_sess_t *clip_sess = NULL;
+
+/** Start an async exchange on the clipboard session.
+ *
+ * @return New exchange.
+ *
+ */
+static async_exch_t *clip_exchange_begin(void)
+{
+	service_id_t sid;
+	errno_t rc;
+
+	fibril_mutex_lock(&clip_mutex);
+
+	while (clip_sess == NULL) {
+		rc = loc_service_get_id(SERVICE_NAME_CLIPBOARD, &sid,
+		    IPC_FLAG_BLOCKING);
+		if (rc != EOK)
+			continue;
+
+		clip_sess = loc_service_connect(sid, INTERFACE_CLIPBOARD,
+		    IPC_FLAG_BLOCKING);
+	}
+
+	fibril_mutex_unlock(&clip_mutex);
+
+	return async_exchange_begin(clip_sess);
+}
+
+/** Finish an async exchange on the clipboard session.
+ *
+ * @param exch Exchange to be finished.
+ *
+ */
+static void clip_exchange_end(async_exch_t *exch)
+{
+	async_exchange_end(exch);
+}
+
+/** Copy string to clipboard.
+ *
+ * Sets the clipboard contents to @a str. Passing an empty string or NULL
+ * makes the clipboard empty.
+ *
+ * @param str String to put to clipboard or NULL.
+ *
+ * @return Zero on success or an error code.
+ *
+ */
+errno_t clipboard_put_str(const char *str)
+{
+	size_t size = str_size(str);
+
+	if (size == 0) {
+		async_exch_t *exch = clip_exchange_begin();
+		errno_t rc = async_req_1_0(exch, CLIPBOARD_PUT_DATA,
+		    CLIPBOARD_TAG_NONE);
+		clip_exchange_end(exch);
+
+		return (errno_t) rc;
+	} else {
+		async_exch_t *exch = clip_exchange_begin();
+		aid_t req = async_send_1(exch, CLIPBOARD_PUT_DATA, CLIPBOARD_TAG_DATA,
+		    NULL);
+		errno_t rc = async_data_write_start(exch, (void *) str, size);
+		clip_exchange_end(exch);
+
+		if (rc != EOK) {
+			errno_t rc_orig;
+			async_wait_for(req, &rc_orig);
+			if (rc_orig == EOK)
+				return (errno_t) rc;
+			else
+				return (errno_t) rc_orig;
+		}
+
+		async_wait_for(req, &rc);
+
+		return (errno_t) rc;
+	}
+}
+
+/** Get a copy of clipboard contents.
+ *
+ * Returns a new string that can be deallocated with free().
+ *
+ * @param str Here pointer to the newly allocated string is stored.
+ *
+ * @return Zero on success or an error code.
+ *
+ */
+errno_t clipboard_get_str(char **str)
+{
+	/* Loop until clipboard read succesful */
+	while (true) {
+		async_exch_t *exch = clip_exchange_begin();
+
+		sysarg_t size;
+		sysarg_t tag;
+		errno_t rc = async_req_0_2(exch, CLIPBOARD_CONTENT, &size, &tag);
+
+		clip_exchange_end(exch);
+
+		if (rc != EOK)
+			return (errno_t) rc;
+
+		char *sbuf;
+
+		switch (tag) {
+		case CLIPBOARD_TAG_NONE:
+			sbuf = malloc(1);
+			if (sbuf == NULL)
+				return ENOMEM;
+
+			sbuf[0] = 0;
+			*str = sbuf;
+			return EOK;
+		case CLIPBOARD_TAG_DATA:
+			sbuf = malloc(size + 1);
+			if (sbuf == NULL)
+				return ENOMEM;
+
+			exch = clip_exchange_begin();
+			aid_t req = async_send_1(exch, CLIPBOARD_GET_DATA, tag, NULL);
+			rc = async_data_read_start(exch, (void *) sbuf, size);
+			clip_exchange_end(exch);
+
+			if ((errno_t) rc == EOVERFLOW) {
+				/*
+				 * The data in the clipboard has changed since
+				 * the last call of CLIPBOARD_CONTENT
+				 */
+				break;
+			}
+
+			if (rc != EOK) {
+				errno_t rc_orig;
+				async_wait_for(req, &rc_orig);
+				if (rc_orig == EOK)
+					return (errno_t) rc;
+				else
+					return (errno_t) rc_orig;
+			}
+
+			async_wait_for(req, &rc);
+
+			if (rc == EOK) {
+				sbuf[size] = 0;
+				*str = sbuf;
+			}
+
+			return rc;
+		default:
+			return EINVAL;
+		}
+	}
+}
+
+/** @}
+ */
Index: uspace/lib/clui/include/nchoice.h
===================================================================
--- uspace/lib/clui/include/nchoice.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/clui/include/nchoice.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2015 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 libclui
+ * @{
+ */
+/**
+ * @file Numerical choice
+ */
+
+#ifndef LIBCLUI_NCHOICE_H_
+#define LIBCLUI_NCHOICE_H_
+
+#include <adt/list.h>
+#include <tinput.h>
+
+typedef enum {
+	/** This is the default option */
+	ncf_default = 1
+} nchoice_flag_t;
+
+typedef struct {
+	/** Link to nchoice_t.opts */
+	link_t lchoice;
+	/** Option text */
+	char *text;
+	/** User argument */
+	void *arg;
+} nchoice_opt_t;
+
+typedef struct {
+	/** Prompt text */
+	char *prompt;
+	/** Options */
+	list_t opts; /* of nchoice_opt_t */
+	/** Text input */
+	tinput_t *tinput;
+	/** Default option */
+	nchoice_opt_t *def_opt;
+} nchoice_t;
+
+extern errno_t nchoice_create(nchoice_t **);
+extern void nchoice_destroy(nchoice_t *);
+extern errno_t nchoice_set_prompt(nchoice_t *, const char *);
+extern errno_t nchoice_add(nchoice_t *, const char *, void *, nchoice_flag_t);
+extern errno_t nchoice_get(nchoice_t *, void **);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/clui/include/tinput.h
===================================================================
--- uspace/lib/clui/include/tinput.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/clui/include/tinput.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2011 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 libclui
+ * @{
+ */
+/**
+ * @file
+ */
+
+#ifndef LIBCLUI_TINPUT_H_
+#define LIBCLUI_TINPUT_H_
+
+#include <inttypes.h>
+#include <io/console.h>
+#include <stdio.h>
+
+#define HISTORY_LEN     10
+#define INPUT_MAX_SIZE  1024
+
+/** Begin enumeration of text completions.
+ *
+ * When user requests text completion, tinput will call this function to start
+ * text completion operation. @a *cstart should be set to the position
+ * (character index) of the first character of the 'word' that is being
+ * completed. The resulting text is obtained by replacing the range of text
+ * starting at @a *cstart and ending at @a pos with the completion text.
+ *
+ * The function can pass information to the get_next and fini functions
+ * via @a state. The init function allocates the state object and stores
+ * a pointer to it to @a *state. The fini function destroys the state object.
+ *
+ * @param text		Current contents of edit buffer (null-terminated).
+ * @param pos		Current caret position.
+ * @param cstart	Output, position in text where completion begins from.
+ * @param state		Output, pointer to a client state object.
+ *
+ * @return		EOK on success, error code on failure.
+ */
+typedef errno_t (*tinput_compl_init_fn)(char32_t *text, size_t pos, size_t *cstart,
+    void **state);
+
+/** Obtain one text completion alternative.
+ *
+ * Upon success the function sets @a *compl to point to a string, the
+ * completion text. The caller (Tinput) should not modify or free the text.
+ * The pointer is only valid until the next invocation of any completion
+ * function.
+ *
+ * @param state		Pointer to state object created by the init funtion.
+ * @param compl		Output, the completion text, ownership retained.
+ *
+ * @return		EOK on success, error code on failure.
+ */
+typedef errno_t (*tinput_compl_get_next_fn)(void *state, char **compl);
+
+/** Finish enumeration of text completions.
+ *
+ * The function must deallocate any state information allocated by the init
+ * function or temporary data allocated by the get_next function.
+ *
+ * @param state		Pointer to state object created by the init funtion.
+ */
+typedef void (*tinput_compl_fini_fn)(void *state);
+
+/** Text completion ops. */
+typedef struct {
+	tinput_compl_init_fn init;
+	tinput_compl_get_next_fn get_next;
+	tinput_compl_fini_fn fini;
+} tinput_compl_ops_t;
+
+/** Text input field (command line).
+ *
+ * Applications should treat this structure as opaque.
+ */
+typedef struct {
+	/** Console */
+	console_ctrl_t *console;
+
+	/** Prompt string */
+	char *prompt;
+
+	/** Completion ops. */
+	tinput_compl_ops_t *compl_ops;
+
+	/** Buffer holding text currently being edited */
+	char32_t buffer[INPUT_MAX_SIZE + 1];
+
+	/** Linear position on screen where the prompt starts */
+	unsigned prompt_coord;
+	/** Linear position on screen where the text field starts */
+	unsigned text_coord;
+
+	/** Screen dimensions */
+	sysarg_t con_cols;
+	sysarg_t con_rows;
+
+	/** Number of characters in @c buffer */
+	size_t nc;
+
+	/** Caret position within buffer */
+	size_t pos;
+
+	/** Selection mark position within buffer */
+	size_t sel_start;
+
+	/** History (dynamically allocated strings) */
+	char *history[HISTORY_LEN + 1];
+
+	/** Number of entries in @c history, not counting [0] */
+	size_t hnum;
+
+	/** Current position in history */
+	size_t hpos;
+
+	/** @c true if finished with this line (return to caller) */
+	bool done;
+
+	/** @c true if user requested to abort interactive loop */
+	bool exit_clui;
+
+	/** @c true if left shift key is currently held */
+	bool lshift_held;
+
+	/** @c true if right shift key is currently held */
+	bool rshift_held;
+} tinput_t;
+
+extern tinput_t *tinput_new(void);
+extern errno_t tinput_set_prompt(tinput_t *, const char *);
+extern void tinput_set_compl_ops(tinput_t *, tinput_compl_ops_t *);
+extern void tinput_destroy(tinput_t *);
+extern errno_t tinput_read(tinput_t *, char **);
+extern errno_t tinput_read_i(tinput_t *, const char *, char **);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/clui/meson.build
===================================================================
--- uspace/lib/clui/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/clui/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 #
-# Copyright (c) 2010 Jiri Svoboda
+# Copyright (c) 2023 Jiri Svoboda
 # All rights reserved.
 #
@@ -27,6 +27,7 @@
 #
 
+deps = [ 'clipboard', 'console' ]
 src = files(
-	'nchoice.c',
-	'tinput.c',
+	'src/nchoice.c',
+	'src/tinput.c',
 )
Index: pace/lib/clui/nchoice.c
===================================================================
--- uspace/lib/clui/nchoice.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,200 +1,0 @@
-/*
- * Copyright (c) 2015 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 libclui
- * @{
- */
-/**
- * @file Numerical choice
- */
-
-#include <errno.h>
-#include <nchoice.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <str.h>
-#include <tinput.h>
-
-/** Create numerical choice
- *
- */
-errno_t nchoice_create(nchoice_t **rchoice)
-{
-	nchoice_t *choice = NULL;
-	errno_t rc;
-
-	choice = calloc(1, sizeof(nchoice_t));
-	if (choice == NULL)
-		goto error;
-
-	list_initialize(&choice->opts);
-
-	choice->tinput = tinput_new();
-	if (choice->tinput == NULL)
-		goto error;
-
-	rc = tinput_set_prompt(choice->tinput, "Choice> ");
-	if (rc != EOK) {
-		assert(rc == ENOMEM);
-		goto error;
-	}
-
-	*rchoice = choice;
-	return EOK;
-error:
-	nchoice_destroy(choice);
-	return ENOMEM;
-}
-
-/** Destroy numerical choice */
-void nchoice_destroy(nchoice_t *choice)
-{
-	if (choice == NULL)
-		return;
-
-	tinput_destroy(choice->tinput);
-	free(choice);
-}
-
-/** Set numerica choice prompt text */
-errno_t nchoice_set_prompt(nchoice_t *choice, const char *prompt)
-{
-	char *pstr;
-
-	pstr = str_dup(prompt);
-	if (pstr == NULL)
-		return ENOMEM;
-
-	free(choice->prompt);
-	choice->prompt = pstr;
-	return EOK;
-}
-
-/** Add option to numerical choice */
-errno_t nchoice_add(nchoice_t *choice, const char *opttext, void *arg,
-    nchoice_flag_t flags)
-{
-	nchoice_opt_t *opt;
-	char *ptext;
-
-	opt = calloc(1, sizeof(nchoice_opt_t));
-	if (opt == NULL)
-		return ENOMEM;
-
-	ptext = str_dup(opttext);
-	if (ptext == NULL) {
-		free(opt);
-		return ENOMEM;
-	}
-
-	opt->text = ptext;
-	opt->arg = arg;
-	list_append(&opt->lchoice, &choice->opts);
-
-	if ((flags & ncf_default) != 0) {
-		/* Set this option as the default */
-		assert(choice->def_opt == NULL);
-
-		choice->def_opt = opt;
-	}
-
-	return EOK;
-}
-
-/** Get numerical choice from user */
-errno_t nchoice_get(nchoice_t *choice, void **rarg)
-{
-	int i;
-	errno_t rc;
-	int ret;
-	char *str;
-	unsigned long c;
-	char *eptr;
-	char *istr;
-	int def_i;
-
-again:
-	printf("%s\n", choice->prompt);
-
-	def_i = 0;
-	i = 1;
-	list_foreach(choice->opts, lchoice, nchoice_opt_t, opt) {
-		printf("%d: %s%s\n", i, opt->text, opt == choice->def_opt ?
-		    " [default]" : "");
-		if (opt == choice->def_opt)
-			def_i = i;
-		++i;
-	}
-
-	if (def_i > 0) {
-		ret = asprintf(&istr, "%d", def_i);
-		if (ret < 0)
-			return ENOMEM;
-	} else {
-		istr = str_dup("");
-		if (istr == NULL)
-			return ENOMEM;
-	}
-
-	rc = tinput_read_i(choice->tinput, istr, &str);
-	free(istr);
-
-	if (rc != EOK) {
-		assert(rc == EIO || rc == ENOENT);
-
-		if (rc == ENOENT) {
-			return ENOENT;
-		} else {
-			/* rc == EIO */
-			return EIO;
-		}
-	}
-
-	c = strtoul(str, &eptr, 10);
-	if (*eptr != '\0' || c < 1 || c > (unsigned long)i - 1) {
-		printf("Invalid choice. Try again.\n");
-		free(str);
-		goto again;
-	}
-
-	free(str);
-
-	i = 1;
-	list_foreach(choice->opts, lchoice, nchoice_opt_t, opt) {
-		if ((unsigned long)i == c) {
-			*rarg = opt->arg;
-			return EOK;
-		}
-		++i;
-	}
-
-	assert(false);
-	return ENOENT;
-}
-
-/** @}
- */
Index: pace/lib/clui/nchoice.h
===================================================================
--- uspace/lib/clui/nchoice.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,76 +1,0 @@
-/*
- * Copyright (c) 2015 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 libclui
- * @{
- */
-/**
- * @file Numerical choice
- */
-
-#ifndef LIBCLUI_NCHOICE_H_
-#define LIBCLUI_NCHOICE_H_
-
-#include <adt/list.h>
-#include <tinput.h>
-
-typedef enum {
-	/** This is the default option */
-	ncf_default = 1
-} nchoice_flag_t;
-
-typedef struct {
-	/** Link to nchoice_t.opts */
-	link_t lchoice;
-	/** Option text */
-	char *text;
-	/** User argument */
-	void *arg;
-} nchoice_opt_t;
-
-typedef struct {
-	/** Prompt text */
-	char *prompt;
-	/** Options */
-	list_t opts; /* of nchoice_opt_t */
-	/** Text input */
-	tinput_t *tinput;
-	/** Default option */
-	nchoice_opt_t *def_opt;
-} nchoice_t;
-
-extern errno_t nchoice_create(nchoice_t **);
-extern void nchoice_destroy(nchoice_t *);
-extern errno_t nchoice_set_prompt(nchoice_t *, const char *);
-extern errno_t nchoice_add(nchoice_t *, const char *, void *, nchoice_flag_t);
-extern errno_t nchoice_get(nchoice_t *, void **);
-
-#endif
-
-/** @}
- */
Index: uspace/lib/clui/src/nchoice.c
===================================================================
--- uspace/lib/clui/src/nchoice.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/clui/src/nchoice.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2015 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 libclui
+ * @{
+ */
+/**
+ * @file Numerical choice
+ */
+
+#include <errno.h>
+#include <nchoice.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <str.h>
+#include <tinput.h>
+
+/** Create numerical choice
+ *
+ */
+errno_t nchoice_create(nchoice_t **rchoice)
+{
+	nchoice_t *choice = NULL;
+	errno_t rc;
+
+	choice = calloc(1, sizeof(nchoice_t));
+	if (choice == NULL)
+		goto error;
+
+	list_initialize(&choice->opts);
+
+	choice->tinput = tinput_new();
+	if (choice->tinput == NULL)
+		goto error;
+
+	rc = tinput_set_prompt(choice->tinput, "Choice> ");
+	if (rc != EOK) {
+		assert(rc == ENOMEM);
+		goto error;
+	}
+
+	*rchoice = choice;
+	return EOK;
+error:
+	nchoice_destroy(choice);
+	return ENOMEM;
+}
+
+/** Destroy numerical choice */
+void nchoice_destroy(nchoice_t *choice)
+{
+	if (choice == NULL)
+		return;
+
+	tinput_destroy(choice->tinput);
+	free(choice);
+}
+
+/** Set numerica choice prompt text */
+errno_t nchoice_set_prompt(nchoice_t *choice, const char *prompt)
+{
+	char *pstr;
+
+	pstr = str_dup(prompt);
+	if (pstr == NULL)
+		return ENOMEM;
+
+	free(choice->prompt);
+	choice->prompt = pstr;
+	return EOK;
+}
+
+/** Add option to numerical choice */
+errno_t nchoice_add(nchoice_t *choice, const char *opttext, void *arg,
+    nchoice_flag_t flags)
+{
+	nchoice_opt_t *opt;
+	char *ptext;
+
+	opt = calloc(1, sizeof(nchoice_opt_t));
+	if (opt == NULL)
+		return ENOMEM;
+
+	ptext = str_dup(opttext);
+	if (ptext == NULL) {
+		free(opt);
+		return ENOMEM;
+	}
+
+	opt->text = ptext;
+	opt->arg = arg;
+	list_append(&opt->lchoice, &choice->opts);
+
+	if ((flags & ncf_default) != 0) {
+		/* Set this option as the default */
+		assert(choice->def_opt == NULL);
+
+		choice->def_opt = opt;
+	}
+
+	return EOK;
+}
+
+/** Get numerical choice from user */
+errno_t nchoice_get(nchoice_t *choice, void **rarg)
+{
+	int i;
+	errno_t rc;
+	int ret;
+	char *str;
+	unsigned long c;
+	char *eptr;
+	char *istr;
+	int def_i;
+
+again:
+	printf("%s\n", choice->prompt);
+
+	def_i = 0;
+	i = 1;
+	list_foreach(choice->opts, lchoice, nchoice_opt_t, opt) {
+		printf("%d: %s%s\n", i, opt->text, opt == choice->def_opt ?
+		    " [default]" : "");
+		if (opt == choice->def_opt)
+			def_i = i;
+		++i;
+	}
+
+	if (def_i > 0) {
+		ret = asprintf(&istr, "%d", def_i);
+		if (ret < 0)
+			return ENOMEM;
+	} else {
+		istr = str_dup("");
+		if (istr == NULL)
+			return ENOMEM;
+	}
+
+	rc = tinput_read_i(choice->tinput, istr, &str);
+	free(istr);
+
+	if (rc != EOK) {
+		assert(rc == EIO || rc == ENOENT);
+
+		if (rc == ENOENT) {
+			return ENOENT;
+		} else {
+			/* rc == EIO */
+			return EIO;
+		}
+	}
+
+	c = strtoul(str, &eptr, 10);
+	if (*eptr != '\0' || c < 1 || c > (unsigned long)i - 1) {
+		printf("Invalid choice. Try again.\n");
+		free(str);
+		goto again;
+	}
+
+	free(str);
+
+	i = 1;
+	list_foreach(choice->opts, lchoice, nchoice_opt_t, opt) {
+		if ((unsigned long)i == c) {
+			*rarg = opt->arg;
+			return EOK;
+		}
+		++i;
+	}
+
+	assert(false);
+	return ENOENT;
+}
+
+/** @}
+ */
Index: uspace/lib/clui/src/tinput.c
===================================================================
--- uspace/lib/clui/src/tinput.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/clui/src/tinput.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,1090 @@
+/*
+ * Copyright (c) 2011 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 libclui
+ * @{
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <str.h>
+#include <io/console.h>
+#include <io/keycode.h>
+#include <io/style.h>
+#include <io/color.h>
+#include <vfs/vfs.h>
+#include <clipboard.h>
+#include <macros.h>
+#include <errno.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <tinput.h>
+
+#define LIN_TO_COL(ti, lpos) ((lpos) % ((ti)->con_cols))
+#define LIN_TO_ROW(ti, lpos) ((lpos) / ((ti)->con_cols))
+#define LIN_POS(ti, col, row) ((col) + (row) * (ti)->con_cols)
+
+/** Seek direction */
+typedef enum {
+	seek_backward = -1,
+	seek_forward = 1
+} seek_dir_t;
+
+static void tinput_update_origin(tinput_t *);
+static void tinput_init(tinput_t *);
+static void tinput_insert_string(tinput_t *, const char *);
+static void tinput_sel_get_bounds(tinput_t *, size_t *, size_t *);
+static bool tinput_sel_active(tinput_t *);
+static void tinput_sel_all(tinput_t *);
+static void tinput_sel_delete(tinput_t *);
+static void tinput_key_ctrl(tinput_t *, kbd_event_t *);
+static void tinput_key_shift(tinput_t *, kbd_event_t *);
+static void tinput_key_ctrl_shift(tinput_t *, kbd_event_t *);
+static void tinput_key_unmod(tinput_t *, kbd_event_t *);
+static void tinput_pre_seek(tinput_t *, bool);
+static void tinput_post_seek(tinput_t *, bool);
+
+static void tinput_console_set_lpos(tinput_t *ti, unsigned lpos)
+{
+	unsigned col = LIN_TO_COL(ti, lpos);
+	unsigned row = LIN_TO_ROW(ti, lpos);
+
+	assert(col < ti->con_cols);
+	assert(row < ti->con_rows);
+	console_set_pos(ti->console, col, row);
+}
+
+/** Create a new text input field. */
+tinput_t *tinput_new(void)
+{
+	tinput_t *ti;
+
+	ti = calloc(1, sizeof(tinput_t));
+	if (ti == NULL)
+		return NULL;
+
+	tinput_init(ti);
+	return ti;
+}
+
+/** Destroy text input field. */
+void tinput_destroy(tinput_t *ti)
+{
+	if (ti->prompt != NULL)
+		free(ti->prompt);
+	free(ti);
+}
+
+static void tinput_display_prompt(tinput_t *ti)
+{
+	tinput_console_set_lpos(ti, ti->prompt_coord);
+
+	console_set_style(ti->console, STYLE_EMPHASIS);
+	printf("%s", ti->prompt);
+	console_flush(ti->console);
+	console_set_style(ti->console, STYLE_NORMAL);
+}
+
+static void tinput_display_tail(tinput_t *ti, size_t start, size_t pad)
+{
+	char32_t *dbuf = malloc((INPUT_MAX_SIZE + 1) * sizeof(char32_t));
+	if (!dbuf)
+		return;
+
+	size_t sa;
+	size_t sb;
+	tinput_sel_get_bounds(ti, &sa, &sb);
+
+	tinput_console_set_lpos(ti, ti->text_coord + start);
+	console_set_style(ti->console, STYLE_NORMAL);
+
+	size_t p = start;
+	if (p < sa) {
+		memcpy(dbuf, ti->buffer + p, (sa - p) * sizeof(char32_t));
+		dbuf[sa - p] = '\0';
+		printf("%ls", dbuf);
+		p = sa;
+	}
+
+	if (p < sb) {
+		console_flush(ti->console);
+		console_set_style(ti->console, STYLE_SELECTED);
+
+		memcpy(dbuf, ti->buffer + p,
+		    (sb - p) * sizeof(char32_t));
+		dbuf[sb - p] = '\0';
+		printf("%ls", dbuf);
+		p = sb;
+	}
+
+	console_flush(ti->console);
+	console_set_style(ti->console, STYLE_NORMAL);
+
+	if (p < ti->nc) {
+		memcpy(dbuf, ti->buffer + p,
+		    (ti->nc - p) * sizeof(char32_t));
+		dbuf[ti->nc - p] = '\0';
+		printf("%ls", dbuf);
+	}
+
+	for (p = 0; p < pad; p++)
+		putuchar(' ');
+
+	console_flush(ti->console);
+
+	free(dbuf);
+}
+
+static char *tinput_get_str(tinput_t *ti)
+{
+	return wstr_to_astr(ti->buffer);
+}
+
+static void tinput_position_caret(tinput_t *ti)
+{
+	tinput_update_origin(ti);
+	tinput_console_set_lpos(ti, ti->text_coord + ti->pos);
+}
+
+/** Update text_coord, prompt_coord in case the screen would scroll
+ * due to @a end_coord being beyond the end of the screen
+ *
+ * @param ti Text input
+ * @param end_coord Linear screen coordinate to which the cursor would
+ *                  be moved if the screen would not have scrolled
+ */
+static void tinput_update_origin_coord(tinput_t *ti, unsigned end_coord)
+{
+	unsigned end_row = LIN_TO_ROW(ti, end_coord);
+
+	unsigned scroll_rows;
+
+	/* Update coords if the screen scrolled. */
+	if (end_row >= ti->con_rows) {
+		scroll_rows = end_row - ti->con_rows + 1;
+		ti->text_coord -= ti->con_cols * scroll_rows;
+		ti->prompt_coord -= ti->con_cols * scroll_rows;
+	}
+}
+
+/** Update text_coord, prompt_coord in case the screen could have scrolled. */
+static void tinput_update_origin(tinput_t *ti)
+{
+	/* Account for scrolling until the end of the input text */
+	tinput_update_origin_coord(ti, ti->text_coord + ti->nc);
+}
+
+static void tinput_jump_after(tinput_t *ti)
+{
+	tinput_console_set_lpos(ti, ti->text_coord + ti->nc);
+	console_flush(ti->console);
+	putuchar('\n');
+}
+
+static errno_t tinput_display(tinput_t *ti)
+{
+	sysarg_t col0, row0;
+
+	if (console_get_pos(ti->console, &col0, &row0) != EOK)
+		return EIO;
+
+	ti->prompt_coord = row0 * ti->con_cols + col0;
+	ti->text_coord = ti->prompt_coord;
+	tinput_display_prompt(ti);
+
+	/* The screen might have scrolled after priting the prompt */
+	tinput_update_origin_coord(ti, ti->prompt_coord + str_width(ti->prompt));
+
+	ti->text_coord = ti->prompt_coord + str_length(ti->prompt);
+	tinput_display_tail(ti, 0, 0);
+
+	/* The screen might have scrolled after priting the text */
+	tinput_update_origin(ti);
+
+	tinput_position_caret(ti);
+
+	return EOK;
+}
+
+static void tinput_insert_char(tinput_t *ti, char32_t c)
+{
+	if (ti->nc == INPUT_MAX_SIZE)
+		return;
+
+	unsigned new_width = LIN_TO_COL(ti, ti->text_coord) + ti->nc + 1;
+	if (new_width % ti->con_cols == 0) {
+		/* Advancing to new line. */
+		sysarg_t new_height = (new_width / ti->con_cols) + 1;
+		if (new_height >= ti->con_rows) {
+			/* Disallow text longer than 1 page for now. */
+			return;
+		}
+	}
+
+	size_t i;
+	for (i = ti->nc; i > ti->pos; i--)
+		ti->buffer[i] = ti->buffer[i - 1];
+
+	ti->buffer[ti->pos] = c;
+	ti->pos += 1;
+	ti->nc += 1;
+	ti->buffer[ti->nc] = '\0';
+	ti->sel_start = ti->pos;
+
+	tinput_display_tail(ti, ti->pos - 1, 0);
+	tinput_position_caret(ti);
+}
+
+static void tinput_insert_string(tinput_t *ti, const char *str)
+{
+	size_t ilen = min(str_length(str), INPUT_MAX_SIZE - ti->nc);
+	if (ilen == 0)
+		return;
+
+	unsigned new_width = LIN_TO_COL(ti, ti->text_coord) + ti->nc + ilen;
+	unsigned new_height = (new_width / ti->con_cols) + 1;
+	if (new_height >= ti->con_rows) {
+		/* Disallow text longer than 1 page for now. */
+		return;
+	}
+
+	if (ti->nc > 0) {
+		size_t i;
+		for (i = ti->nc; i > ti->pos; i--)
+			ti->buffer[i + ilen - 1] = ti->buffer[i - 1];
+	}
+
+	size_t off = 0;
+	size_t i = 0;
+	while (i < ilen) {
+		char32_t c = str_decode(str, &off, STR_NO_LIMIT);
+		if (c == '\0')
+			break;
+
+		/* Filter out non-printable chars. */
+		if (c < 32)
+			c = 32;
+
+		ti->buffer[ti->pos + i] = c;
+		i++;
+	}
+
+	ti->pos += ilen;
+	ti->nc += ilen;
+	ti->buffer[ti->nc] = '\0';
+	ti->sel_start = ti->pos;
+
+	tinput_display_tail(ti, ti->pos - ilen, 0);
+	tinput_position_caret(ti);
+}
+
+static void tinput_backspace(tinput_t *ti)
+{
+	if (tinput_sel_active(ti)) {
+		tinput_sel_delete(ti);
+		return;
+	}
+
+	if (ti->pos == 0)
+		return;
+
+	size_t i;
+	for (i = ti->pos; i < ti->nc; i++)
+		ti->buffer[i - 1] = ti->buffer[i];
+
+	ti->pos -= 1;
+	ti->nc -= 1;
+	ti->buffer[ti->nc] = '\0';
+	ti->sel_start = ti->pos;
+
+	tinput_display_tail(ti, ti->pos, 1);
+	tinput_position_caret(ti);
+}
+
+static void tinput_delete(tinput_t *ti)
+{
+	if (tinput_sel_active(ti)) {
+		tinput_sel_delete(ti);
+		return;
+	}
+
+	if (ti->pos == ti->nc)
+		return;
+
+	ti->pos += 1;
+	ti->sel_start = ti->pos;
+
+	tinput_backspace(ti);
+}
+
+static void tinput_seek_cell(tinput_t *ti, seek_dir_t dir, bool shift_held)
+{
+	tinput_pre_seek(ti, shift_held);
+
+	if (dir == seek_forward) {
+		if (ti->pos < ti->nc)
+			ti->pos += 1;
+	} else {
+		if (ti->pos > 0)
+			ti->pos -= 1;
+	}
+
+	tinput_post_seek(ti, shift_held);
+}
+
+static void tinput_seek_word(tinput_t *ti, seek_dir_t dir, bool shift_held)
+{
+	tinput_pre_seek(ti, shift_held);
+
+	if (dir == seek_forward) {
+		if (ti->pos == ti->nc)
+			return;
+
+		while (true) {
+			ti->pos += 1;
+
+			if (ti->pos == ti->nc)
+				break;
+
+			if ((ti->buffer[ti->pos - 1] == ' ') &&
+			    (ti->buffer[ti->pos] != ' '))
+				break;
+		}
+	} else {
+		if (ti->pos == 0)
+			return;
+
+		while (true) {
+			ti->pos -= 1;
+
+			if (ti->pos == 0)
+				break;
+
+			if (ti->buffer[ti->pos - 1] == ' ' &&
+			    ti->buffer[ti->pos] != ' ')
+				break;
+		}
+
+	}
+
+	tinput_post_seek(ti, shift_held);
+}
+
+static void tinput_seek_vertical(tinput_t *ti, seek_dir_t dir, bool shift_held)
+{
+	tinput_pre_seek(ti, shift_held);
+
+	if (dir == seek_forward) {
+		if (ti->pos + ti->con_cols <= ti->nc)
+			ti->pos = ti->pos + ti->con_cols;
+	} else {
+		if (ti->pos >= ti->con_cols)
+			ti->pos = ti->pos - ti->con_cols;
+	}
+
+	tinput_post_seek(ti, shift_held);
+}
+
+static void tinput_seek_scrpos(tinput_t *ti, int col, int line, bool shift_held)
+{
+	unsigned lpos;
+	tinput_pre_seek(ti, shift_held);
+
+	lpos = LIN_POS(ti, col, line);
+
+	if (lpos > ti->text_coord)
+		ti->pos = lpos -  ti->text_coord;
+	else
+		ti->pos = 0;
+	if (ti->pos > ti->nc)
+		ti->pos = ti->nc;
+
+	tinput_post_seek(ti, shift_held);
+}
+
+static void tinput_seek_max(tinput_t *ti, seek_dir_t dir, bool shift_held)
+{
+	tinput_pre_seek(ti, shift_held);
+
+	if (dir == seek_backward)
+		ti->pos = 0;
+	else
+		ti->pos = ti->nc;
+
+	tinput_post_seek(ti, shift_held);
+}
+
+static void tinput_pre_seek(tinput_t *ti, bool shift_held)
+{
+	if ((tinput_sel_active(ti)) && (!shift_held)) {
+		/* Unselect and redraw. */
+		ti->sel_start = ti->pos;
+		tinput_display_tail(ti, 0, 0);
+		tinput_position_caret(ti);
+	}
+}
+
+static void tinput_post_seek(tinput_t *ti, bool shift_held)
+{
+	if (shift_held) {
+		/* Selecting text. Need redraw. */
+		tinput_display_tail(ti, 0, 0);
+	} else {
+		/* Shift not held. Keep selection empty. */
+		ti->sel_start = ti->pos;
+	}
+
+	tinput_position_caret(ti);
+}
+
+static void tinput_history_insert(tinput_t *ti, char *str)
+{
+	if (ti->hnum < HISTORY_LEN) {
+		ti->hnum += 1;
+	} else {
+		if (ti->history[HISTORY_LEN] != NULL)
+			free(ti->history[HISTORY_LEN]);
+	}
+
+	size_t i;
+	for (i = ti->hnum; i > 1; i--)
+		ti->history[i] = ti->history[i - 1];
+
+	ti->history[1] = str_dup(str);
+
+	if (ti->history[0] != NULL) {
+		free(ti->history[0]);
+		ti->history[0] = NULL;
+	}
+}
+
+static void tinput_set_str(tinput_t *ti, const char *str)
+{
+	str_to_wstr(ti->buffer, INPUT_MAX_SIZE, str);
+	ti->nc = wstr_length(ti->buffer);
+	ti->pos = ti->nc;
+	ti->sel_start = ti->pos;
+}
+
+static void tinput_sel_get_bounds(tinput_t *ti, size_t *sa, size_t *sb)
+{
+	if (ti->sel_start < ti->pos) {
+		*sa = ti->sel_start;
+		*sb = ti->pos;
+	} else {
+		*sa = ti->pos;
+		*sb = ti->sel_start;
+	}
+}
+
+static bool tinput_sel_active(tinput_t *ti)
+{
+	return (ti->sel_start != ti->pos);
+}
+
+static void tinput_sel_all(tinput_t *ti)
+{
+	ti->sel_start = 0;
+	ti->pos = ti->nc;
+	tinput_display_tail(ti, 0, 0);
+	tinput_position_caret(ti);
+}
+
+static void tinput_sel_delete(tinput_t *ti)
+{
+	size_t sa;
+	size_t sb;
+
+	tinput_sel_get_bounds(ti, &sa, &sb);
+	if (sa == sb)
+		return;
+
+	memmove(ti->buffer + sa, ti->buffer + sb,
+	    (ti->nc - sb) * sizeof(char32_t));
+
+	ti->pos = ti->sel_start = sa;
+	ti->nc -= (sb - sa);
+	ti->buffer[ti->nc] = '\0';
+
+	tinput_display_tail(ti, sa, sb - sa);
+	tinput_position_caret(ti);
+}
+
+static void tinput_sel_copy_to_cb(tinput_t *ti)
+{
+	size_t sa;
+	size_t sb;
+
+	tinput_sel_get_bounds(ti, &sa, &sb);
+
+	char *str;
+
+	if (sb < ti->nc) {
+		char32_t tmp_c = ti->buffer[sb];
+		ti->buffer[sb] = '\0';
+		str = wstr_to_astr(ti->buffer + sa);
+		ti->buffer[sb] = tmp_c;
+	} else
+		str = wstr_to_astr(ti->buffer + sa);
+
+	if (str == NULL)
+		goto error;
+
+	if (clipboard_put_str(str) != EOK)
+		goto error;
+
+	free(str);
+	return;
+
+error:
+	/* TODO: Give the user some kind of warning. */
+	return;
+}
+
+static void tinput_paste_from_cb(tinput_t *ti)
+{
+	char *str;
+	errno_t rc = clipboard_get_str(&str);
+
+	if ((rc != EOK) || (str == NULL)) {
+		/* TODO: Give the user some kind of warning. */
+		return;
+	}
+
+	tinput_insert_string(ti, str);
+	free(str);
+}
+
+static void tinput_history_seek(tinput_t *ti, int offs)
+{
+	if (offs >= 0) {
+		if (ti->hpos + offs > ti->hnum)
+			return;
+	} else {
+		if (ti->hpos < (size_t) -offs)
+			return;
+	}
+
+	if (ti->history[ti->hpos] != NULL) {
+		free(ti->history[ti->hpos]);
+		ti->history[ti->hpos] = NULL;
+	}
+
+	ti->history[ti->hpos] = tinput_get_str(ti);
+	ti->hpos += offs;
+
+	int pad = (int) ti->nc - str_length(ti->history[ti->hpos]);
+	if (pad < 0)
+		pad = 0;
+
+	tinput_set_str(ti, ti->history[ti->hpos]);
+	tinput_display_tail(ti, 0, pad);
+	tinput_update_origin(ti);
+	tinput_position_caret(ti);
+}
+
+/** Compare two entries in array of completions. */
+static int compl_cmp(const void *va, const void *vb)
+{
+	const char *a = *(const char **) va;
+	const char *b = *(const char **) vb;
+
+	return str_cmp(a, b);
+}
+
+static size_t common_pref_len(const char *a, const char *b)
+{
+	size_t i;
+	size_t a_off, b_off;
+	char32_t ca, cb;
+
+	i = 0;
+	a_off = 0;
+	b_off = 0;
+
+	while (true) {
+		ca = str_decode(a, &a_off, STR_NO_LIMIT);
+		cb = str_decode(b, &b_off, STR_NO_LIMIT);
+
+		if (ca == '\0' || cb == '\0' || ca != cb)
+			break;
+		++i;
+	}
+
+	return i;
+}
+
+/* Print a list of completions */
+static void tinput_show_completions(tinput_t *ti, char **compl, size_t cnum)
+{
+	unsigned int i;
+	/* Determine the maximum width of the completion in chars */
+	size_t max_width = 0;
+	for (i = 0; i < cnum; i++)
+		max_width = max(max_width, str_width(compl[i]));
+
+	unsigned int cols = max(1, (ti->con_cols + 1) / (max_width + 1));
+	unsigned int padding = 0;
+	if (cols * max_width + (cols - 1) < ti->con_cols) {
+		padding = ti->con_cols - cols * max_width - (cols - 1);
+	}
+	unsigned int col_width = max_width + padding / cols;
+	unsigned int rows = cnum / cols + ((cnum % cols) != 0);
+
+	unsigned int row, col;
+
+	for (row = 0; row < rows; row++) {
+		unsigned int display_col = 0;
+		for (col = 0; col < cols; col++) {
+			size_t compl_idx = col * rows + row;
+			if (compl_idx >= cnum)
+				break;
+			if (col) {
+				printf(" ");
+				display_col++;
+			}
+			printf("%s", compl[compl_idx]);
+			size_t compl_width = str_width(compl[compl_idx]);
+			display_col += compl_width;
+			if (col < cols - 1) {
+				for (i = compl_width; i < col_width; i++) {
+					printf(" ");
+					display_col++;
+				}
+			}
+		}
+		if ((display_col % ti->con_cols) > 0)
+			printf("\n");
+	}
+	fflush(stdout);
+}
+
+static void tinput_text_complete(tinput_t *ti)
+{
+	void *state;
+	size_t cstart;
+	char *ctmp;
+	char **compl;     	/* Array of completions */
+	size_t compl_len;	/* Current length of @c compl array */
+	size_t cnum;
+	size_t i;
+	errno_t rc;
+
+	if (ti->compl_ops == NULL)
+		return;
+
+	/*
+	 * Obtain list of all possible completions (growing array).
+	 */
+
+	rc = (*ti->compl_ops->init)(ti->buffer, ti->pos, &cstart, &state);
+	if (rc != EOK)
+		return;
+
+	cnum = 0;
+
+	compl_len = 1;
+	compl = malloc(compl_len * sizeof(char *));
+	if (compl == NULL) {
+		printf("Error: Out of memory.\n");
+		return;
+	}
+
+	while (true) {
+		rc = (*ti->compl_ops->get_next)(state, &ctmp);
+		if (rc != EOK)
+			break;
+
+		if (cnum >= compl_len) {
+			/* Extend array */
+			compl_len = 2 * compl_len;
+			char **temp = realloc(compl, compl_len * sizeof(char *));
+			if (temp == NULL) {
+				free(compl);
+				printf("Error: Out of memory.\n");
+				break;
+			}
+			compl = temp;
+		}
+
+		compl[cnum] = str_dup(ctmp);
+		if (compl[cnum] == NULL) {
+			printf("Error: Out of memory.\n");
+			break;
+		}
+		cnum++;
+	}
+
+	(*ti->compl_ops->fini)(state);
+
+	if (cnum > 1) {
+		/*
+		 * More than one match. Determine maximum common prefix.
+		 */
+		size_t cplen;
+
+		cplen = str_length(compl[0]);
+		for (i = 1; i < cnum; i++)
+			cplen = min(cplen, common_pref_len(compl[0], compl[i]));
+
+		/* Compute how many bytes we should skip. */
+		size_t istart = str_lsize(compl[0], ti->pos - cstart);
+
+		if (cplen > istart) {
+			/* Insert common prefix. */
+
+			/* Copy remainder of common prefix. */
+			char *cpref = str_ndup(compl[0] + istart,
+			    str_lsize(compl[0], cplen - istart));
+
+			/* Insert it. */
+			tinput_insert_string(ti, cpref);
+			free(cpref);
+		} else {
+			/* No common prefix. Sort and display all entries. */
+
+			qsort(compl, cnum, sizeof(char *), compl_cmp);
+
+			tinput_jump_after(ti);
+			tinput_show_completions(ti, compl, cnum);
+			tinput_display(ti);
+		}
+	} else if (cnum == 1) {
+		/*
+		 * We have exactly one match. Insert it.
+		 */
+
+		/* Compute how many bytes of completion string we should skip. */
+		size_t istart = str_lsize(compl[0], ti->pos - cstart);
+
+		/* Insert remainder of completion string at current position. */
+		tinput_insert_string(ti, compl[0] + istart);
+	}
+
+	for (i = 0; i < cnum; i++)
+		free(compl[i]);
+	free(compl);
+}
+
+/** Initialize text input field.
+ *
+ * Must be called before using the field. It clears the history.
+ */
+static void tinput_init(tinput_t *ti)
+{
+	ti->console = console_init(stdin, stdout);
+	ti->hnum = 0;
+	ti->hpos = 0;
+	ti->history[0] = NULL;
+}
+
+/** Set prompt string.
+ *
+ * @param ti		Text input
+ * @param prompt	Prompt string
+ *
+ * @return		EOK on success, ENOMEM if out of memory.
+ */
+errno_t tinput_set_prompt(tinput_t *ti, const char *prompt)
+{
+	if (ti->prompt != NULL)
+		free(ti->prompt);
+
+	ti->prompt = str_dup(prompt);
+	if (ti->prompt == NULL)
+		return ENOMEM;
+
+	return EOK;
+}
+
+/** Set completion ops.
+ *
+ * Set pointer to completion ops structure that will be used for text
+ * completion.
+ */
+void tinput_set_compl_ops(tinput_t *ti, tinput_compl_ops_t *compl_ops)
+{
+	ti->compl_ops = compl_ops;
+}
+
+/** Handle key press event. */
+static void tinput_key_press(tinput_t *ti, kbd_event_t *kev)
+{
+	if (kev->key == KC_LSHIFT)
+		ti->lshift_held = true;
+	if (kev->key == KC_RSHIFT)
+		ti->rshift_held = true;
+
+	if (((kev->mods & KM_CTRL) != 0) &&
+	    ((kev->mods & (KM_ALT | KM_SHIFT)) == 0))
+		tinput_key_ctrl(ti, kev);
+
+	if (((kev->mods & KM_SHIFT) != 0) &&
+	    ((kev->mods & (KM_CTRL | KM_ALT)) == 0))
+		tinput_key_shift(ti, kev);
+
+	if (((kev->mods & KM_CTRL) != 0) &&
+	    ((kev->mods & KM_SHIFT) != 0) &&
+	    ((kev->mods & KM_ALT) == 0))
+		tinput_key_ctrl_shift(ti, kev);
+
+	if ((kev->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0)
+		tinput_key_unmod(ti, kev);
+
+	if (((kev->mods & (KM_CTRL | KM_ALT)) == 0) && kev->c >= ' ') {
+		tinput_sel_delete(ti);
+		tinput_insert_char(ti, kev->c);
+	}
+}
+
+/** Handle key release event. */
+static void tinput_key_release(tinput_t *ti, kbd_event_t *kev)
+{
+	if (kev->key == KC_LSHIFT)
+		ti->lshift_held = false;
+	if (kev->key == KC_RSHIFT)
+		ti->rshift_held = false;
+}
+
+/** Position event */
+static void tinput_pos(tinput_t *ti, pos_event_t *ev)
+{
+	if (ev->type == POS_PRESS) {
+		tinput_seek_scrpos(ti, ev->hpos, ev->vpos,
+		    ti->lshift_held || ti->rshift_held);
+	}
+}
+
+/** Read in one line of input with initial text provided.
+ *
+ * @param ti   Text input
+ * @param istr Initial string
+ * @param dstr Place to save pointer to new string
+ *
+ * @return EOK on success
+ * @return ENOENT if user requested abort
+ * @return EIO if communication with console failed
+ *
+ */
+errno_t tinput_read_i(tinput_t *ti, const char *istr, char **dstr)
+{
+	errno_t rc;
+
+	console_flush(ti->console);
+	if (console_get_size(ti->console, &ti->con_cols, &ti->con_rows) != EOK)
+		return EIO;
+
+	tinput_set_str(ti, istr);
+
+	ti->sel_start = 0;
+	ti->done = false;
+	ti->exit_clui = false;
+
+	if (tinput_display(ti) != EOK)
+		return EIO;
+
+	while (!ti->done) {
+		console_flush(ti->console);
+
+		cons_event_t ev;
+		rc = console_get_event(ti->console, &ev);
+		if (rc != EOK)
+			return EIO;
+
+		switch (ev.type) {
+		case CEV_KEY:
+			if (ev.ev.key.type == KEY_PRESS)
+				tinput_key_press(ti, &ev.ev.key);
+			else
+				tinput_key_release(ti, &ev.ev.key);
+			break;
+		case CEV_POS:
+			tinput_pos(ti, &ev.ev.pos);
+			break;
+		}
+	}
+
+	if (ti->exit_clui)
+		return ENOENT;
+
+	ti->pos = ti->nc;
+	tinput_position_caret(ti);
+	putchar('\n');
+
+	char *str = tinput_get_str(ti);
+	if (str_cmp(str, "") != 0)
+		tinput_history_insert(ti, str);
+
+	ti->hpos = 0;
+
+	*dstr = str;
+	return EOK;
+}
+
+/** Read in one line of input.
+ *
+ * @param ti   Text input
+ * @param dstr Place to save pointer to new string.
+ *
+ * @return EOK on success
+ * @return ENOENT if user requested abort
+ * @return EIO if communication with console failed
+ *
+ */
+errno_t tinput_read(tinput_t *ti, char **dstr)
+{
+	return tinput_read_i(ti, "", dstr);
+}
+
+static void tinput_key_ctrl(tinput_t *ti, kbd_event_t *ev)
+{
+	switch (ev->key) {
+	case KC_LEFT:
+		tinput_seek_word(ti, seek_backward, false);
+		break;
+	case KC_RIGHT:
+		tinput_seek_word(ti, seek_forward, false);
+		break;
+	case KC_UP:
+		tinput_seek_vertical(ti, seek_backward, false);
+		break;
+	case KC_DOWN:
+		tinput_seek_vertical(ti, seek_forward, false);
+		break;
+	case KC_X:
+		tinput_sel_copy_to_cb(ti);
+		tinput_sel_delete(ti);
+		break;
+	case KC_C:
+		tinput_sel_copy_to_cb(ti);
+		break;
+	case KC_V:
+		tinput_sel_delete(ti);
+		tinput_paste_from_cb(ti);
+		break;
+	case KC_A:
+		tinput_sel_all(ti);
+		break;
+	case KC_Q:
+		/* Signal libary client to quit interactive loop. */
+		ti->done = true;
+		ti->exit_clui = true;
+		break;
+	default:
+		break;
+	}
+}
+
+static void tinput_key_ctrl_shift(tinput_t *ti, kbd_event_t *ev)
+{
+	switch (ev->key) {
+	case KC_LEFT:
+		tinput_seek_word(ti, seek_backward, true);
+		break;
+	case KC_RIGHT:
+		tinput_seek_word(ti, seek_forward, true);
+		break;
+	case KC_UP:
+		tinput_seek_vertical(ti, seek_backward, true);
+		break;
+	case KC_DOWN:
+		tinput_seek_vertical(ti, seek_forward, true);
+		break;
+	default:
+		break;
+	}
+}
+
+static void tinput_key_shift(tinput_t *ti, kbd_event_t *ev)
+{
+	switch (ev->key) {
+	case KC_LEFT:
+		tinput_seek_cell(ti, seek_backward, true);
+		break;
+	case KC_RIGHT:
+		tinput_seek_cell(ti, seek_forward, true);
+		break;
+	case KC_UP:
+		tinput_seek_vertical(ti, seek_backward, true);
+		break;
+	case KC_DOWN:
+		tinput_seek_vertical(ti, seek_forward, true);
+		break;
+	case KC_HOME:
+		tinput_seek_max(ti, seek_backward, true);
+		break;
+	case KC_END:
+		tinput_seek_max(ti, seek_forward, true);
+		break;
+	default:
+		break;
+	}
+}
+
+static void tinput_key_unmod(tinput_t *ti, kbd_event_t *ev)
+{
+	switch (ev->key) {
+	case KC_ENTER:
+	case KC_NENTER:
+		ti->done = true;
+		break;
+	case KC_BACKSPACE:
+		tinput_backspace(ti);
+		break;
+	case KC_DELETE:
+		tinput_delete(ti);
+		break;
+	case KC_LEFT:
+		tinput_seek_cell(ti, seek_backward, false);
+		break;
+	case KC_RIGHT:
+		tinput_seek_cell(ti, seek_forward, false);
+		break;
+	case KC_HOME:
+		tinput_seek_max(ti, seek_backward, false);
+		break;
+	case KC_END:
+		tinput_seek_max(ti, seek_forward, false);
+		break;
+	case KC_UP:
+		tinput_history_seek(ti, 1);
+		break;
+	case KC_DOWN:
+		tinput_history_seek(ti, -1);
+		break;
+	case KC_TAB:
+		tinput_text_complete(ti);
+		break;
+	default:
+		break;
+	}
+}
+
+/**
+ * @}
+ */
Index: pace/lib/clui/tinput.c
===================================================================
--- uspace/lib/clui/tinput.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,1070 +1,0 @@
-/*
- * Copyright (c) 2011 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 libclui
- * @{
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <str.h>
-#include <io/console.h>
-#include <io/keycode.h>
-#include <io/style.h>
-#include <io/color.h>
-#include <vfs/vfs.h>
-#include <clipboard.h>
-#include <macros.h>
-#include <errno.h>
-#include <assert.h>
-#include <stdbool.h>
-#include <tinput.h>
-
-#define LIN_TO_COL(ti, lpos) ((lpos) % ((ti)->con_cols))
-#define LIN_TO_ROW(ti, lpos) ((lpos) / ((ti)->con_cols))
-#define LIN_POS(ti, col, row) ((col) + (row) * (ti)->con_cols)
-
-/** Seek direction */
-typedef enum {
-	seek_backward = -1,
-	seek_forward = 1
-} seek_dir_t;
-
-static void tinput_update_origin(tinput_t *);
-static void tinput_init(tinput_t *);
-static void tinput_insert_string(tinput_t *, const char *);
-static void tinput_sel_get_bounds(tinput_t *, size_t *, size_t *);
-static bool tinput_sel_active(tinput_t *);
-static void tinput_sel_all(tinput_t *);
-static void tinput_sel_delete(tinput_t *);
-static void tinput_key_ctrl(tinput_t *, kbd_event_t *);
-static void tinput_key_shift(tinput_t *, kbd_event_t *);
-static void tinput_key_ctrl_shift(tinput_t *, kbd_event_t *);
-static void tinput_key_unmod(tinput_t *, kbd_event_t *);
-static void tinput_pre_seek(tinput_t *, bool);
-static void tinput_post_seek(tinput_t *, bool);
-
-static void tinput_console_set_lpos(tinput_t *ti, unsigned lpos)
-{
-	unsigned col = LIN_TO_COL(ti, lpos);
-	unsigned row = LIN_TO_ROW(ti, lpos);
-
-	assert(col < ti->con_cols);
-	assert(row < ti->con_rows);
-	console_set_pos(ti->console, col, row);
-}
-
-/** Create a new text input field. */
-tinput_t *tinput_new(void)
-{
-	tinput_t *ti;
-
-	ti = calloc(1, sizeof(tinput_t));
-	if (ti == NULL)
-		return NULL;
-
-	tinput_init(ti);
-	return ti;
-}
-
-/** Destroy text input field. */
-void tinput_destroy(tinput_t *ti)
-{
-	if (ti->prompt != NULL)
-		free(ti->prompt);
-	free(ti);
-}
-
-static void tinput_display_prompt(tinput_t *ti)
-{
-	tinput_console_set_lpos(ti, ti->prompt_coord);
-
-	console_set_style(ti->console, STYLE_EMPHASIS);
-	printf("%s", ti->prompt);
-	console_flush(ti->console);
-	console_set_style(ti->console, STYLE_NORMAL);
-}
-
-static void tinput_display_tail(tinput_t *ti, size_t start, size_t pad)
-{
-	char32_t *dbuf = malloc((INPUT_MAX_SIZE + 1) * sizeof(char32_t));
-	if (!dbuf)
-		return;
-
-	size_t sa;
-	size_t sb;
-	tinput_sel_get_bounds(ti, &sa, &sb);
-
-	tinput_console_set_lpos(ti, ti->text_coord + start);
-	console_set_style(ti->console, STYLE_NORMAL);
-
-	size_t p = start;
-	if (p < sa) {
-		memcpy(dbuf, ti->buffer + p, (sa - p) * sizeof(char32_t));
-		dbuf[sa - p] = '\0';
-		printf("%ls", dbuf);
-		p = sa;
-	}
-
-	if (p < sb) {
-		console_flush(ti->console);
-		console_set_style(ti->console, STYLE_SELECTED);
-
-		memcpy(dbuf, ti->buffer + p,
-		    (sb - p) * sizeof(char32_t));
-		dbuf[sb - p] = '\0';
-		printf("%ls", dbuf);
-		p = sb;
-	}
-
-	console_flush(ti->console);
-	console_set_style(ti->console, STYLE_NORMAL);
-
-	if (p < ti->nc) {
-		memcpy(dbuf, ti->buffer + p,
-		    (ti->nc - p) * sizeof(char32_t));
-		dbuf[ti->nc - p] = '\0';
-		printf("%ls", dbuf);
-	}
-
-	for (p = 0; p < pad; p++)
-		putuchar(' ');
-
-	console_flush(ti->console);
-
-	free(dbuf);
-}
-
-static char *tinput_get_str(tinput_t *ti)
-{
-	return wstr_to_astr(ti->buffer);
-}
-
-static void tinput_position_caret(tinput_t *ti)
-{
-	tinput_update_origin(ti);
-	tinput_console_set_lpos(ti, ti->text_coord + ti->pos);
-}
-
-/** Update text_coord, prompt_coord in case the screen could have scrolled. */
-static void tinput_update_origin(tinput_t *ti)
-{
-	unsigned end_coord = ti->text_coord + ti->nc;
-	unsigned end_row = LIN_TO_ROW(ti, end_coord);
-
-	unsigned scroll_rows;
-
-	/* Update coords if the screen scrolled. */
-	if (end_row >= ti->con_rows) {
-		scroll_rows = end_row - ti->con_rows + 1;
-		ti->text_coord -= ti->con_cols * scroll_rows;
-		ti->prompt_coord -= ti->con_cols * scroll_rows;
-	}
-}
-
-static void tinput_jump_after(tinput_t *ti)
-{
-	tinput_console_set_lpos(ti, ti->text_coord + ti->nc);
-	console_flush(ti->console);
-	putuchar('\n');
-}
-
-static errno_t tinput_display(tinput_t *ti)
-{
-	sysarg_t col0, row0;
-
-	if (console_get_pos(ti->console, &col0, &row0) != EOK)
-		return EIO;
-
-	ti->prompt_coord = row0 * ti->con_cols + col0;
-	ti->text_coord = ti->prompt_coord + str_length(ti->prompt);
-
-	tinput_display_prompt(ti);
-	tinput_display_tail(ti, 0, 0);
-	tinput_position_caret(ti);
-
-	return EOK;
-}
-
-static void tinput_insert_char(tinput_t *ti, char32_t c)
-{
-	if (ti->nc == INPUT_MAX_SIZE)
-		return;
-
-	unsigned new_width = LIN_TO_COL(ti, ti->text_coord) + ti->nc + 1;
-	if (new_width % ti->con_cols == 0) {
-		/* Advancing to new line. */
-		sysarg_t new_height = (new_width / ti->con_cols) + 1;
-		if (new_height >= ti->con_rows) {
-			/* Disallow text longer than 1 page for now. */
-			return;
-		}
-	}
-
-	size_t i;
-	for (i = ti->nc; i > ti->pos; i--)
-		ti->buffer[i] = ti->buffer[i - 1];
-
-	ti->buffer[ti->pos] = c;
-	ti->pos += 1;
-	ti->nc += 1;
-	ti->buffer[ti->nc] = '\0';
-	ti->sel_start = ti->pos;
-
-	tinput_display_tail(ti, ti->pos - 1, 0);
-	tinput_position_caret(ti);
-}
-
-static void tinput_insert_string(tinput_t *ti, const char *str)
-{
-	size_t ilen = min(str_length(str), INPUT_MAX_SIZE - ti->nc);
-	if (ilen == 0)
-		return;
-
-	unsigned new_width = LIN_TO_COL(ti, ti->text_coord) + ti->nc + ilen;
-	unsigned new_height = (new_width / ti->con_cols) + 1;
-	if (new_height >= ti->con_rows) {
-		/* Disallow text longer than 1 page for now. */
-		return;
-	}
-
-	if (ti->nc > 0) {
-		size_t i;
-		for (i = ti->nc; i > ti->pos; i--)
-			ti->buffer[i + ilen - 1] = ti->buffer[i - 1];
-	}
-
-	size_t off = 0;
-	size_t i = 0;
-	while (i < ilen) {
-		char32_t c = str_decode(str, &off, STR_NO_LIMIT);
-		if (c == '\0')
-			break;
-
-		/* Filter out non-printable chars. */
-		if (c < 32)
-			c = 32;
-
-		ti->buffer[ti->pos + i] = c;
-		i++;
-	}
-
-	ti->pos += ilen;
-	ti->nc += ilen;
-	ti->buffer[ti->nc] = '\0';
-	ti->sel_start = ti->pos;
-
-	tinput_display_tail(ti, ti->pos - ilen, 0);
-	tinput_position_caret(ti);
-}
-
-static void tinput_backspace(tinput_t *ti)
-{
-	if (tinput_sel_active(ti)) {
-		tinput_sel_delete(ti);
-		return;
-	}
-
-	if (ti->pos == 0)
-		return;
-
-	size_t i;
-	for (i = ti->pos; i < ti->nc; i++)
-		ti->buffer[i - 1] = ti->buffer[i];
-
-	ti->pos -= 1;
-	ti->nc -= 1;
-	ti->buffer[ti->nc] = '\0';
-	ti->sel_start = ti->pos;
-
-	tinput_display_tail(ti, ti->pos, 1);
-	tinput_position_caret(ti);
-}
-
-static void tinput_delete(tinput_t *ti)
-{
-	if (tinput_sel_active(ti)) {
-		tinput_sel_delete(ti);
-		return;
-	}
-
-	if (ti->pos == ti->nc)
-		return;
-
-	ti->pos += 1;
-	ti->sel_start = ti->pos;
-
-	tinput_backspace(ti);
-}
-
-static void tinput_seek_cell(tinput_t *ti, seek_dir_t dir, bool shift_held)
-{
-	tinput_pre_seek(ti, shift_held);
-
-	if (dir == seek_forward) {
-		if (ti->pos < ti->nc)
-			ti->pos += 1;
-	} else {
-		if (ti->pos > 0)
-			ti->pos -= 1;
-	}
-
-	tinput_post_seek(ti, shift_held);
-}
-
-static void tinput_seek_word(tinput_t *ti, seek_dir_t dir, bool shift_held)
-{
-	tinput_pre_seek(ti, shift_held);
-
-	if (dir == seek_forward) {
-		if (ti->pos == ti->nc)
-			return;
-
-		while (true) {
-			ti->pos += 1;
-
-			if (ti->pos == ti->nc)
-				break;
-
-			if ((ti->buffer[ti->pos - 1] == ' ') &&
-			    (ti->buffer[ti->pos] != ' '))
-				break;
-		}
-	} else {
-		if (ti->pos == 0)
-			return;
-
-		while (true) {
-			ti->pos -= 1;
-
-			if (ti->pos == 0)
-				break;
-
-			if (ti->buffer[ti->pos - 1] == ' ' &&
-			    ti->buffer[ti->pos] != ' ')
-				break;
-		}
-
-	}
-
-	tinput_post_seek(ti, shift_held);
-}
-
-static void tinput_seek_vertical(tinput_t *ti, seek_dir_t dir, bool shift_held)
-{
-	tinput_pre_seek(ti, shift_held);
-
-	if (dir == seek_forward) {
-		if (ti->pos + ti->con_cols <= ti->nc)
-			ti->pos = ti->pos + ti->con_cols;
-	} else {
-		if (ti->pos >= ti->con_cols)
-			ti->pos = ti->pos - ti->con_cols;
-	}
-
-	tinput_post_seek(ti, shift_held);
-}
-
-static void tinput_seek_scrpos(tinput_t *ti, int col, int line, bool shift_held)
-{
-	unsigned lpos;
-	tinput_pre_seek(ti, shift_held);
-
-	lpos = LIN_POS(ti, col, line);
-
-	if (lpos > ti->text_coord)
-		ti->pos = lpos -  ti->text_coord;
-	else
-		ti->pos = 0;
-	if (ti->pos > ti->nc)
-		ti->pos = ti->nc;
-
-	tinput_post_seek(ti, shift_held);
-}
-
-static void tinput_seek_max(tinput_t *ti, seek_dir_t dir, bool shift_held)
-{
-	tinput_pre_seek(ti, shift_held);
-
-	if (dir == seek_backward)
-		ti->pos = 0;
-	else
-		ti->pos = ti->nc;
-
-	tinput_post_seek(ti, shift_held);
-}
-
-static void tinput_pre_seek(tinput_t *ti, bool shift_held)
-{
-	if ((tinput_sel_active(ti)) && (!shift_held)) {
-		/* Unselect and redraw. */
-		ti->sel_start = ti->pos;
-		tinput_display_tail(ti, 0, 0);
-		tinput_position_caret(ti);
-	}
-}
-
-static void tinput_post_seek(tinput_t *ti, bool shift_held)
-{
-	if (shift_held) {
-		/* Selecting text. Need redraw. */
-		tinput_display_tail(ti, 0, 0);
-	} else {
-		/* Shift not held. Keep selection empty. */
-		ti->sel_start = ti->pos;
-	}
-
-	tinput_position_caret(ti);
-}
-
-static void tinput_history_insert(tinput_t *ti, char *str)
-{
-	if (ti->hnum < HISTORY_LEN) {
-		ti->hnum += 1;
-	} else {
-		if (ti->history[HISTORY_LEN] != NULL)
-			free(ti->history[HISTORY_LEN]);
-	}
-
-	size_t i;
-	for (i = ti->hnum; i > 1; i--)
-		ti->history[i] = ti->history[i - 1];
-
-	ti->history[1] = str_dup(str);
-
-	if (ti->history[0] != NULL) {
-		free(ti->history[0]);
-		ti->history[0] = NULL;
-	}
-}
-
-static void tinput_set_str(tinput_t *ti, const char *str)
-{
-	str_to_wstr(ti->buffer, INPUT_MAX_SIZE, str);
-	ti->nc = wstr_length(ti->buffer);
-	ti->pos = ti->nc;
-	ti->sel_start = ti->pos;
-}
-
-static void tinput_sel_get_bounds(tinput_t *ti, size_t *sa, size_t *sb)
-{
-	if (ti->sel_start < ti->pos) {
-		*sa = ti->sel_start;
-		*sb = ti->pos;
-	} else {
-		*sa = ti->pos;
-		*sb = ti->sel_start;
-	}
-}
-
-static bool tinput_sel_active(tinput_t *ti)
-{
-	return (ti->sel_start != ti->pos);
-}
-
-static void tinput_sel_all(tinput_t *ti)
-{
-	ti->sel_start = 0;
-	ti->pos = ti->nc;
-	tinput_display_tail(ti, 0, 0);
-	tinput_position_caret(ti);
-}
-
-static void tinput_sel_delete(tinput_t *ti)
-{
-	size_t sa;
-	size_t sb;
-
-	tinput_sel_get_bounds(ti, &sa, &sb);
-	if (sa == sb)
-		return;
-
-	memmove(ti->buffer + sa, ti->buffer + sb,
-	    (ti->nc - sb) * sizeof(char32_t));
-
-	ti->pos = ti->sel_start = sa;
-	ti->nc -= (sb - sa);
-	ti->buffer[ti->nc] = '\0';
-
-	tinput_display_tail(ti, sa, sb - sa);
-	tinput_position_caret(ti);
-}
-
-static void tinput_sel_copy_to_cb(tinput_t *ti)
-{
-	size_t sa;
-	size_t sb;
-
-	tinput_sel_get_bounds(ti, &sa, &sb);
-
-	char *str;
-
-	if (sb < ti->nc) {
-		char32_t tmp_c = ti->buffer[sb];
-		ti->buffer[sb] = '\0';
-		str = wstr_to_astr(ti->buffer + sa);
-		ti->buffer[sb] = tmp_c;
-	} else
-		str = wstr_to_astr(ti->buffer + sa);
-
-	if (str == NULL)
-		goto error;
-
-	if (clipboard_put_str(str) != EOK)
-		goto error;
-
-	free(str);
-	return;
-
-error:
-	/* TODO: Give the user some kind of warning. */
-	return;
-}
-
-static void tinput_paste_from_cb(tinput_t *ti)
-{
-	char *str;
-	errno_t rc = clipboard_get_str(&str);
-
-	if ((rc != EOK) || (str == NULL)) {
-		/* TODO: Give the user some kind of warning. */
-		return;
-	}
-
-	tinput_insert_string(ti, str);
-	free(str);
-}
-
-static void tinput_history_seek(tinput_t *ti, int offs)
-{
-	if (offs >= 0) {
-		if (ti->hpos + offs > ti->hnum)
-			return;
-	} else {
-		if (ti->hpos < (size_t) -offs)
-			return;
-	}
-
-	if (ti->history[ti->hpos] != NULL) {
-		free(ti->history[ti->hpos]);
-		ti->history[ti->hpos] = NULL;
-	}
-
-	ti->history[ti->hpos] = tinput_get_str(ti);
-	ti->hpos += offs;
-
-	int pad = (int) ti->nc - str_length(ti->history[ti->hpos]);
-	if (pad < 0)
-		pad = 0;
-
-	tinput_set_str(ti, ti->history[ti->hpos]);
-	tinput_display_tail(ti, 0, pad);
-	tinput_update_origin(ti);
-	tinput_position_caret(ti);
-}
-
-/** Compare two entries in array of completions. */
-static int compl_cmp(const void *va, const void *vb)
-{
-	const char *a = *(const char **) va;
-	const char *b = *(const char **) vb;
-
-	return str_cmp(a, b);
-}
-
-static size_t common_pref_len(const char *a, const char *b)
-{
-	size_t i;
-	size_t a_off, b_off;
-	char32_t ca, cb;
-
-	i = 0;
-	a_off = 0;
-	b_off = 0;
-
-	while (true) {
-		ca = str_decode(a, &a_off, STR_NO_LIMIT);
-		cb = str_decode(b, &b_off, STR_NO_LIMIT);
-
-		if (ca == '\0' || cb == '\0' || ca != cb)
-			break;
-		++i;
-	}
-
-	return i;
-}
-
-/* Print a list of completions */
-static void tinput_show_completions(tinput_t *ti, char **compl, size_t cnum)
-{
-	unsigned int i;
-	/* Determine the maximum width of the completion in chars */
-	size_t max_width = 0;
-	for (i = 0; i < cnum; i++)
-		max_width = max(max_width, str_width(compl[i]));
-
-	unsigned int cols = max(1, (ti->con_cols + 1) / (max_width + 1));
-	unsigned int padding = 0;
-	if (cols * max_width + (cols - 1) < ti->con_cols) {
-		padding = ti->con_cols - cols * max_width - (cols - 1);
-	}
-	unsigned int col_width = max_width + padding / cols;
-	unsigned int rows = cnum / cols + ((cnum % cols) != 0);
-
-	unsigned int row, col;
-
-	for (row = 0; row < rows; row++) {
-		unsigned int display_col = 0;
-		for (col = 0; col < cols; col++) {
-			size_t compl_idx = col * rows + row;
-			if (compl_idx >= cnum)
-				break;
-			if (col) {
-				printf(" ");
-				display_col++;
-			}
-			printf("%s", compl[compl_idx]);
-			size_t compl_width = str_width(compl[compl_idx]);
-			display_col += compl_width;
-			if (col < cols - 1) {
-				for (i = compl_width; i < col_width; i++) {
-					printf(" ");
-					display_col++;
-				}
-			}
-		}
-		if ((display_col % ti->con_cols) > 0)
-			printf("\n");
-	}
-	fflush(stdout);
-}
-
-static void tinput_text_complete(tinput_t *ti)
-{
-	void *state;
-	size_t cstart;
-	char *ctmp;
-	char **compl;     	/* Array of completions */
-	size_t compl_len;	/* Current length of @c compl array */
-	size_t cnum;
-	size_t i;
-	errno_t rc;
-
-	if (ti->compl_ops == NULL)
-		return;
-
-	/*
-	 * Obtain list of all possible completions (growing array).
-	 */
-
-	rc = (*ti->compl_ops->init)(ti->buffer, ti->pos, &cstart, &state);
-	if (rc != EOK)
-		return;
-
-	cnum = 0;
-
-	compl_len = 1;
-	compl = malloc(compl_len * sizeof(char *));
-	if (compl == NULL) {
-		printf("Error: Out of memory.\n");
-		return;
-	}
-
-	while (true) {
-		rc = (*ti->compl_ops->get_next)(state, &ctmp);
-		if (rc != EOK)
-			break;
-
-		if (cnum >= compl_len) {
-			/* Extend array */
-			compl_len = 2 * compl_len;
-			char **temp = realloc(compl, compl_len * sizeof(char *));
-			if (temp == NULL) {
-				free(compl);
-				printf("Error: Out of memory.\n");
-				break;
-			}
-			compl = temp;
-		}
-
-		compl[cnum] = str_dup(ctmp);
-		if (compl[cnum] == NULL) {
-			printf("Error: Out of memory.\n");
-			break;
-		}
-		cnum++;
-	}
-
-	(*ti->compl_ops->fini)(state);
-
-	if (cnum > 1) {
-		/*
-		 * More than one match. Determine maximum common prefix.
-		 */
-		size_t cplen;
-
-		cplen = str_length(compl[0]);
-		for (i = 1; i < cnum; i++)
-			cplen = min(cplen, common_pref_len(compl[0], compl[i]));
-
-		/* Compute how many bytes we should skip. */
-		size_t istart = str_lsize(compl[0], ti->pos - cstart);
-
-		if (cplen > istart) {
-			/* Insert common prefix. */
-
-			/* Copy remainder of common prefix. */
-			char *cpref = str_ndup(compl[0] + istart,
-			    str_lsize(compl[0], cplen - istart));
-
-			/* Insert it. */
-			tinput_insert_string(ti, cpref);
-			free(cpref);
-		} else {
-			/* No common prefix. Sort and display all entries. */
-
-			qsort(compl, cnum, sizeof(char *), compl_cmp);
-
-			tinput_jump_after(ti);
-			tinput_show_completions(ti, compl, cnum);
-			tinput_display(ti);
-		}
-	} else if (cnum == 1) {
-		/*
-		 * We have exactly one match. Insert it.
-		 */
-
-		/* Compute how many bytes of completion string we should skip. */
-		size_t istart = str_lsize(compl[0], ti->pos - cstart);
-
-		/* Insert remainder of completion string at current position. */
-		tinput_insert_string(ti, compl[0] + istart);
-	}
-
-	for (i = 0; i < cnum; i++)
-		free(compl[i]);
-	free(compl);
-}
-
-/** Initialize text input field.
- *
- * Must be called before using the field. It clears the history.
- */
-static void tinput_init(tinput_t *ti)
-{
-	ti->console = console_init(stdin, stdout);
-	ti->hnum = 0;
-	ti->hpos = 0;
-	ti->history[0] = NULL;
-}
-
-/** Set prompt string.
- *
- * @param ti		Text input
- * @param prompt	Prompt string
- *
- * @return		EOK on success, ENOMEM if out of memory.
- */
-errno_t tinput_set_prompt(tinput_t *ti, const char *prompt)
-{
-	if (ti->prompt != NULL)
-		free(ti->prompt);
-
-	ti->prompt = str_dup(prompt);
-	if (ti->prompt == NULL)
-		return ENOMEM;
-
-	return EOK;
-}
-
-/** Set completion ops.
- *
- * Set pointer to completion ops structure that will be used for text
- * completion.
- */
-void tinput_set_compl_ops(tinput_t *ti, tinput_compl_ops_t *compl_ops)
-{
-	ti->compl_ops = compl_ops;
-}
-
-/** Handle key press event. */
-static void tinput_key_press(tinput_t *ti, kbd_event_t *kev)
-{
-	if (kev->key == KC_LSHIFT)
-		ti->lshift_held = true;
-	if (kev->key == KC_RSHIFT)
-		ti->rshift_held = true;
-
-	if (((kev->mods & KM_CTRL) != 0) &&
-	    ((kev->mods & (KM_ALT | KM_SHIFT)) == 0))
-		tinput_key_ctrl(ti, kev);
-
-	if (((kev->mods & KM_SHIFT) != 0) &&
-	    ((kev->mods & (KM_CTRL | KM_ALT)) == 0))
-		tinput_key_shift(ti, kev);
-
-	if (((kev->mods & KM_CTRL) != 0) &&
-	    ((kev->mods & KM_SHIFT) != 0) &&
-	    ((kev->mods & KM_ALT) == 0))
-		tinput_key_ctrl_shift(ti, kev);
-
-	if ((kev->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0)
-		tinput_key_unmod(ti, kev);
-
-	if (kev->c >= ' ') {
-		tinput_sel_delete(ti);
-		tinput_insert_char(ti, kev->c);
-	}
-}
-
-/** Handle key release event. */
-static void tinput_key_release(tinput_t *ti, kbd_event_t *kev)
-{
-	if (kev->key == KC_LSHIFT)
-		ti->lshift_held = false;
-	if (kev->key == KC_RSHIFT)
-		ti->rshift_held = false;
-}
-
-/** Position event */
-static void tinput_pos(tinput_t *ti, pos_event_t *ev)
-{
-	if (ev->type == POS_PRESS) {
-		tinput_seek_scrpos(ti, ev->hpos, ev->vpos,
-		    ti->lshift_held || ti->rshift_held);
-	}
-}
-
-/** Read in one line of input with initial text provided.
- *
- * @param ti   Text input
- * @param istr Initial string
- * @param dstr Place to save pointer to new string
- *
- * @return EOK on success
- * @return ENOENT if user requested abort
- * @return EIO if communication with console failed
- *
- */
-errno_t tinput_read_i(tinput_t *ti, const char *istr, char **dstr)
-{
-	errno_t rc;
-
-	console_flush(ti->console);
-	if (console_get_size(ti->console, &ti->con_cols, &ti->con_rows) != EOK)
-		return EIO;
-
-	tinput_set_str(ti, istr);
-
-	ti->sel_start = 0;
-	ti->done = false;
-	ti->exit_clui = false;
-
-	if (tinput_display(ti) != EOK)
-		return EIO;
-
-	while (!ti->done) {
-		console_flush(ti->console);
-
-		cons_event_t ev;
-		rc = console_get_event(ti->console, &ev);
-		if (rc != EOK)
-			return EIO;
-
-		switch (ev.type) {
-		case CEV_KEY:
-			if (ev.ev.key.type == KEY_PRESS)
-				tinput_key_press(ti, &ev.ev.key);
-			else
-				tinput_key_release(ti, &ev.ev.key);
-			break;
-		case CEV_POS:
-			tinput_pos(ti, &ev.ev.pos);
-			break;
-		}
-	}
-
-	if (ti->exit_clui)
-		return ENOENT;
-
-	ti->pos = ti->nc;
-	tinput_position_caret(ti);
-	putchar('\n');
-
-	char *str = tinput_get_str(ti);
-	if (str_cmp(str, "") != 0)
-		tinput_history_insert(ti, str);
-
-	ti->hpos = 0;
-
-	*dstr = str;
-	return EOK;
-}
-
-/** Read in one line of input.
- *
- * @param ti   Text input
- * @param dstr Place to save pointer to new string.
- *
- * @return EOK on success
- * @return ENOENT if user requested abort
- * @return EIO if communication with console failed
- *
- */
-errno_t tinput_read(tinput_t *ti, char **dstr)
-{
-	return tinput_read_i(ti, "", dstr);
-}
-
-static void tinput_key_ctrl(tinput_t *ti, kbd_event_t *ev)
-{
-	switch (ev->key) {
-	case KC_LEFT:
-		tinput_seek_word(ti, seek_backward, false);
-		break;
-	case KC_RIGHT:
-		tinput_seek_word(ti, seek_forward, false);
-		break;
-	case KC_UP:
-		tinput_seek_vertical(ti, seek_backward, false);
-		break;
-	case KC_DOWN:
-		tinput_seek_vertical(ti, seek_forward, false);
-		break;
-	case KC_X:
-		tinput_sel_copy_to_cb(ti);
-		tinput_sel_delete(ti);
-		break;
-	case KC_C:
-		tinput_sel_copy_to_cb(ti);
-		break;
-	case KC_V:
-		tinput_sel_delete(ti);
-		tinput_paste_from_cb(ti);
-		break;
-	case KC_A:
-		tinput_sel_all(ti);
-		break;
-	case KC_Q:
-		/* Signal libary client to quit interactive loop. */
-		ti->done = true;
-		ti->exit_clui = true;
-		break;
-	default:
-		break;
-	}
-}
-
-static void tinput_key_ctrl_shift(tinput_t *ti, kbd_event_t *ev)
-{
-	switch (ev->key) {
-	case KC_LEFT:
-		tinput_seek_word(ti, seek_backward, true);
-		break;
-	case KC_RIGHT:
-		tinput_seek_word(ti, seek_forward, true);
-		break;
-	case KC_UP:
-		tinput_seek_vertical(ti, seek_backward, true);
-		break;
-	case KC_DOWN:
-		tinput_seek_vertical(ti, seek_forward, true);
-		break;
-	default:
-		break;
-	}
-}
-
-static void tinput_key_shift(tinput_t *ti, kbd_event_t *ev)
-{
-	switch (ev->key) {
-	case KC_LEFT:
-		tinput_seek_cell(ti, seek_backward, true);
-		break;
-	case KC_RIGHT:
-		tinput_seek_cell(ti, seek_forward, true);
-		break;
-	case KC_UP:
-		tinput_seek_vertical(ti, seek_backward, true);
-		break;
-	case KC_DOWN:
-		tinput_seek_vertical(ti, seek_forward, true);
-		break;
-	case KC_HOME:
-		tinput_seek_max(ti, seek_backward, true);
-		break;
-	case KC_END:
-		tinput_seek_max(ti, seek_forward, true);
-		break;
-	default:
-		break;
-	}
-}
-
-static void tinput_key_unmod(tinput_t *ti, kbd_event_t *ev)
-{
-	switch (ev->key) {
-	case KC_ENTER:
-	case KC_NENTER:
-		ti->done = true;
-		break;
-	case KC_BACKSPACE:
-		tinput_backspace(ti);
-		break;
-	case KC_DELETE:
-		tinput_delete(ti);
-		break;
-	case KC_LEFT:
-		tinput_seek_cell(ti, seek_backward, false);
-		break;
-	case KC_RIGHT:
-		tinput_seek_cell(ti, seek_forward, false);
-		break;
-	case KC_HOME:
-		tinput_seek_max(ti, seek_backward, false);
-		break;
-	case KC_END:
-		tinput_seek_max(ti, seek_forward, false);
-		break;
-	case KC_UP:
-		tinput_history_seek(ti, 1);
-		break;
-	case KC_DOWN:
-		tinput_history_seek(ti, -1);
-		break;
-	case KC_TAB:
-		tinput_text_complete(ti);
-		break;
-	default:
-		break;
-	}
-}
-
-/**
- * @}
- */
Index: pace/lib/clui/tinput.h
===================================================================
--- uspace/lib/clui/tinput.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ 	(revision )
@@ -1,165 +1,0 @@
-/*
- * Copyright (c) 2011 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 libclui
- * @{
- */
-/**
- * @file
- */
-
-#ifndef LIBCLUI_TINPUT_H_
-#define LIBCLUI_TINPUT_H_
-
-#include <inttypes.h>
-#include <io/console.h>
-#include <stdio.h>
-
-#define HISTORY_LEN     10
-#define INPUT_MAX_SIZE  1024
-
-/** Begin enumeration of text completions.
- *
- * When user requests text completion, tinput will call this function to start
- * text completion operation. @a *cstart should be set to the position
- * (character index) of the first character of the 'word' that is being
- * completed. The resulting text is obtained by replacing the range of text
- * starting at @a *cstart and ending at @a pos with the completion text.
- *
- * The function can pass information to the get_next and fini functions
- * via @a state. The init function allocates the state object and stores
- * a pointer to it to @a *state. The fini function destroys the state object.
- *
- * @param text		Current contents of edit buffer (null-terminated).
- * @param pos		Current caret position.
- * @param cstart	Output, position in text where completion begins from.
- * @param state		Output, pointer to a client state object.
- *
- * @return		EOK on success, error code on failure.
- */
-typedef errno_t (*tinput_compl_init_fn)(char32_t *text, size_t pos, size_t *cstart,
-    void **state);
-
-/** Obtain one text completion alternative.
- *
- * Upon success the function sets @a *compl to point to a string, the
- * completion text. The caller (Tinput) should not modify or free the text.
- * The pointer is only valid until the next invocation of any completion
- * function.
- *
- * @param state		Pointer to state object created by the init funtion.
- * @param compl		Output, the completion text, ownership retained.
- *
- * @return		EOK on success, error code on failure.
- */
-typedef errno_t (*tinput_compl_get_next_fn)(void *state, char **compl);
-
-/** Finish enumeration of text completions.
- *
- * The function must deallocate any state information allocated by the init
- * function or temporary data allocated by the get_next function.
- *
- * @param state		Pointer to state object created by the init funtion.
- */
-typedef void (*tinput_compl_fini_fn)(void *state);
-
-/** Text completion ops. */
-typedef struct {
-	tinput_compl_init_fn init;
-	tinput_compl_get_next_fn get_next;
-	tinput_compl_fini_fn fini;
-} tinput_compl_ops_t;
-
-/** Text input field (command line).
- *
- * Applications should treat this structure as opaque.
- */
-typedef struct {
-	/** Console */
-	console_ctrl_t *console;
-
-	/** Prompt string */
-	char *prompt;
-
-	/** Completion ops. */
-	tinput_compl_ops_t *compl_ops;
-
-	/** Buffer holding text currently being edited */
-	char32_t buffer[INPUT_MAX_SIZE + 1];
-
-	/** Linear position on screen where the prompt starts */
-	unsigned prompt_coord;
-	/** Linear position on screen where the text field starts */
-	unsigned text_coord;
-
-	/** Screen dimensions */
-	sysarg_t con_cols;
-	sysarg_t con_rows;
-
-	/** Number of characters in @c buffer */
-	size_t nc;
-
-	/** Caret position within buffer */
-	size_t pos;
-
-	/** Selection mark position within buffer */
-	size_t sel_start;
-
-	/** History (dynamically allocated strings) */
-	char *history[HISTORY_LEN + 1];
-
-	/** Number of entries in @c history, not counting [0] */
-	size_t hnum;
-
-	/** Current position in history */
-	size_t hpos;
-
-	/** @c true if finished with this line (return to caller) */
-	bool done;
-
-	/** @c true if user requested to abort interactive loop */
-	bool exit_clui;
-
-	/** @c true if left shift key is currently held */
-	bool lshift_held;
-
-	/** @c true if right shift key is currently held */
-	bool rshift_held;
-} tinput_t;
-
-extern tinput_t *tinput_new(void);
-extern errno_t tinput_set_prompt(tinput_t *, const char *);
-extern void tinput_set_compl_ops(tinput_t *, tinput_compl_ops_t *);
-extern void tinput_destroy(tinput_t *);
-extern errno_t tinput_read(tinput_t *, char **);
-extern errno_t tinput_read_i(tinput_t *, const char *, char **);
-
-#endif
-
-/** @}
- */
Index: uspace/lib/congfx/include/congfx/console.h
===================================================================
--- uspace/lib/congfx/include/congfx/console.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/congfx/include/congfx/console.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -47,4 +47,6 @@
 extern errno_t console_gc_create(console_ctrl_t *, FILE *, console_gc_t **);
 extern errno_t console_gc_delete(console_gc_t *);
+extern errno_t console_gc_suspend(console_gc_t *);
+extern errno_t console_gc_resume(console_gc_t *);
 extern gfx_context_t *console_gc_get_ctx(console_gc_t *);
 
Index: uspace/lib/congfx/meson.build
===================================================================
--- uspace/lib/congfx/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/congfx/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -27,5 +27,5 @@
 #
 
-deps = [ 'gfx' ]
+deps = [ 'gfx', 'console' ]
 src = files(
 	'src/console.c',
Index: uspace/lib/congfx/src/console.c
===================================================================
--- uspace/lib/congfx/src/console.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/congfx/src/console.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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 {
@@ -281,4 +281,37 @@
 }
 
+/** Free up console for other users, suspending GC operation.
+ *
+ * @param cgc Console GC
+ * @return EOK on success or an error code
+ */
+errno_t console_gc_suspend(console_gc_t *cgc)
+{
+	console_unmap(cgc->con, cgc->buf);
+	cgc->buf = NULL;
+
+	console_clear(cgc->con);
+	console_cursor_visibility(cgc->con, true);
+	return EOK;
+}
+
+/** Resume GC operation after suspend.
+ *
+ * @param cgc Console GC
+ * @return EOK on success or an error code
+ */
+errno_t console_gc_resume(console_gc_t *cgc)
+{
+	errno_t rc;
+
+	console_clear(cgc->con);
+
+	rc = console_map(cgc->con, cgc->rect.p1.x, cgc->rect.p1.y, &cgc->buf);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
 /** Get generic graphic context from console GC.
  *
Index: uspace/lib/console/doc/doxygroups.h
===================================================================
--- uspace/lib/console/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/console/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,3 @@
+/** @addtogroup libconsole libconsole
+ * @ingroup libs
+ */
Index: uspace/lib/console/include/io/con_srv.h
===================================================================
--- uspace/lib/console/include/io/con_srv.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/console/include/io/con_srv.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,101 @@
+/*
+ * 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 libconsole
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBCONSOLE_IO_CON_SRV_H_
+#define _LIBCONSOLE_IO_CON_SRV_H_
+
+#include <adt/list.h>
+#include <async.h>
+#include <fibril_synch.h>
+#include <io/charfield.h>
+#include <io/color.h>
+#include <io/concaps.h>
+#include <io/cons_event.h>
+#include <io/pixel.h>
+#include <io/style.h>
+#include <stdbool.h>
+#include <time.h>
+#include <stddef.h>
+
+typedef struct con_ops con_ops_t;
+
+#define CON_CAPTION_MAXLEN 255
+
+/** Service setup (per sevice) */
+typedef struct {
+	con_ops_t *ops;
+	void *sarg;
+	/** Period to check for abort */
+	usec_t abort_timeout;
+	bool aborted;
+} con_srvs_t;
+
+/** Server structure (per client session) */
+typedef struct {
+	con_srvs_t *srvs;
+	async_sess_t *client_sess;
+	void *carg;
+} con_srv_t;
+
+struct con_ops {
+	errno_t (*open)(con_srvs_t *, con_srv_t *);
+	errno_t (*close)(con_srv_t *);
+	errno_t (*read)(con_srv_t *, void *, size_t, size_t *);
+	errno_t (*write)(con_srv_t *, void *, size_t, size_t *);
+	void (*sync)(con_srv_t *);
+	void (*clear)(con_srv_t *);
+	void (*set_pos)(con_srv_t *, sysarg_t col, sysarg_t row);
+	errno_t (*get_pos)(con_srv_t *, sysarg_t *, sysarg_t *);
+	errno_t (*get_size)(con_srv_t *, sysarg_t *, sysarg_t *);
+	errno_t (*get_color_cap)(con_srv_t *, console_caps_t *);
+	void (*set_style)(con_srv_t *, console_style_t);
+	void (*set_color)(con_srv_t *, console_color_t, console_color_t,
+	    console_color_attr_t);
+	void (*set_rgb_color)(con_srv_t *, pixel_t, pixel_t);
+	void (*set_cursor_visibility)(con_srv_t *, bool);
+	errno_t (*set_caption)(con_srv_t *, const char *);
+	errno_t (*get_event)(con_srv_t *, cons_event_t *);
+	errno_t (*map)(con_srv_t *, sysarg_t, sysarg_t, charfield_t **);
+	void (*unmap)(con_srv_t *);
+	void (*update)(con_srv_t *, sysarg_t, sysarg_t, sysarg_t, sysarg_t);
+};
+
+extern void con_srvs_init(con_srvs_t *);
+
+extern errno_t con_conn(ipc_call_t *, con_srvs_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/console/include/io/cons_event.h
===================================================================
--- uspace/lib/console/include/io/cons_event.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/console/include/io/cons_event.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,66 @@
+/*
+ * 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 libconsole
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBCONSOLE_IO_CONS_EVENT_H_
+#define _LIBCONSOLE_IO_CONS_EVENT_H_
+
+#include <adt/list.h>
+#include <io/kbd_event.h>
+#include <io/pos_event.h>
+
+typedef enum {
+	/** Key event */
+	CEV_KEY,
+	/** Position event */
+	CEV_POS
+} cons_event_type_t;
+
+/** Console event structure. */
+typedef struct {
+	/** List handle */
+	link_t link;
+
+	/** Event type */
+	cons_event_type_t type;
+
+	union {
+		kbd_event_t key;
+		pos_event_t pos;
+	} ev;
+} cons_event_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/console/include/io/console.h
===================================================================
--- uspace/lib/console/include/io/console.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/console/include/io/console.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,99 @@
+/*
+ * 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 libconsole
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBCONSOLE_IO_CONSOLE_H_
+#define _LIBCONSOLE_IO_CONSOLE_H_
+
+#include <time.h>
+#include <io/charfield.h>
+#include <io/concaps.h>
+#include <io/kbd_event.h>
+#include <io/cons_event.h>
+#include <io/keycode.h>
+#include <async.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+/** Console control structure. */
+typedef struct {
+	/** Console input file */
+	FILE *input;
+
+	/** Console output file */
+	FILE *output;
+
+	/** Console input session */
+	async_sess_t *input_sess;
+
+	/** Console output session */
+	async_sess_t *output_sess;
+
+	/** Input request call with timeout */
+	ipc_call_t input_call;
+
+	/** Input response with timeout */
+	aid_t input_aid;
+} console_ctrl_t;
+
+extern console_ctrl_t *console_init(FILE *, FILE *);
+extern void console_done(console_ctrl_t *);
+extern bool console_kcon(void);
+
+extern void console_flush(console_ctrl_t *);
+extern void console_clear(console_ctrl_t *);
+
+extern errno_t console_get_size(console_ctrl_t *, sysarg_t *, sysarg_t *);
+extern errno_t console_get_pos(console_ctrl_t *, sysarg_t *, sysarg_t *);
+extern void console_set_pos(console_ctrl_t *, sysarg_t, sysarg_t);
+
+extern void console_set_style(console_ctrl_t *, uint8_t);
+extern void console_set_color(console_ctrl_t *, uint8_t, uint8_t, uint8_t);
+extern void console_set_rgb_color(console_ctrl_t *, uint32_t, uint32_t);
+
+extern void console_cursor_visibility(console_ctrl_t *, bool);
+extern errno_t console_set_caption(console_ctrl_t *, const char *);
+extern errno_t console_get_color_cap(console_ctrl_t *, sysarg_t *);
+extern errno_t console_get_event(console_ctrl_t *, cons_event_t *);
+extern errno_t console_get_event_timeout(console_ctrl_t *, cons_event_t *,
+    usec_t *);
+extern errno_t console_map(console_ctrl_t *, sysarg_t, sysarg_t,
+    charfield_t **);
+extern void console_unmap(console_ctrl_t *, charfield_t *);
+extern errno_t console_update(console_ctrl_t *, sysarg_t, sysarg_t, sysarg_t,
+    sysarg_t);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/console/include/ipc/console.h
===================================================================
--- uspace/lib/console/include/ipc/console.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/console/include/ipc/console.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2006 Josef Cejka
+ * 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 libconsole
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBCONSOLE_IPC_CONSOLE_H_
+#define _LIBCONSOLE_IPC_CONSOLE_H_
+
+#include <ipc/vfs.h>
+
+typedef enum {
+	CONSOLE_GET_SIZE = VFS_OUT_LAST,
+	CONSOLE_GET_COLOR_CAP,
+	CONSOLE_GET_EVENT,
+	CONSOLE_GET_POS,
+	CONSOLE_SET_POS,
+	CONSOLE_CLEAR,
+	CONSOLE_SET_STYLE,
+	CONSOLE_SET_COLOR,
+	CONSOLE_SET_RGB_COLOR,
+	CONSOLE_SET_CURSOR_VISIBILITY,
+	CONSOLE_SET_CAPTION,
+	CONSOLE_MAP,
+	CONSOLE_UNMAP,
+	CONSOLE_UPDATE
+} console_request_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/console/meson.build
===================================================================
--- uspace/lib/console/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/console/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,33 @@
+#
+# 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.
+#
+
+deps = [ 'input', 'output' ]
+src = files(
+	'src/con_srv.c',
+	'src/console.c',
+)
Index: uspace/lib/console/src/con_srv.c
===================================================================
--- uspace/lib/console/src/con_srv.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/console/src/con_srv.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,539 @@
+/*
+ * Copyright (c) 2021 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 libconsole
+ * @{
+ */
+/**
+ * @file
+ * @brief Console protocol server stub
+ */
+#include <as.h>
+#include <errno.h>
+#include <io/cons_event.h>
+#include <ipc/console.h>
+#include <stdlib.h>
+#include <stddef.h>
+
+#include <io/con_srv.h>
+
+static errno_t console_ev_encode(cons_event_t *event, ipc_call_t *icall)
+{
+	ipc_set_arg1(icall, event->type);
+
+	switch (event->type) {
+	case CEV_KEY:
+		ipc_set_arg2(icall, event->ev.key.type);
+		ipc_set_arg3(icall, event->ev.key.key);
+		ipc_set_arg4(icall, event->ev.key.mods);
+		ipc_set_arg5(icall, event->ev.key.c);
+		break;
+	case CEV_POS:
+		ipc_set_arg2(icall, (event->ev.pos.pos_id << 16) | (event->ev.pos.type & 0xffff));
+		ipc_set_arg3(icall, event->ev.pos.btn_num);
+		ipc_set_arg4(icall, event->ev.pos.hpos);
+		ipc_set_arg5(icall, event->ev.pos.vpos);
+		break;
+	default:
+		return EIO;
+	}
+
+	return EOK;
+}
+
+static void con_read_srv(con_srv_t *srv, ipc_call_t *icall)
+{
+	void *buf;
+	size_t size;
+	errno_t rc;
+
+	ipc_call_t call;
+	if (!async_data_read_receive(&call, &size)) {
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	buf = malloc(size);
+	if (buf == NULL) {
+		async_answer_0(&call, ENOMEM);
+		async_answer_0(icall, ENOMEM);
+		return;
+	}
+
+	if (srv->srvs->ops->read == NULL) {
+		async_answer_0(&call, ENOTSUP);
+		async_answer_0(icall, ENOTSUP);
+		free(buf);
+		return;
+	}
+
+	size_t nread;
+	rc = srv->srvs->ops->read(srv, buf, size, &nread);
+	if (rc != EOK) {
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		free(buf);
+		return;
+	}
+
+	async_data_read_finalize(&call, buf, nread);
+	free(buf);
+
+	async_answer_1(icall, EOK, nread);
+}
+
+static void con_write_srv(con_srv_t *srv, ipc_call_t *icall)
+{
+	void *data;
+	size_t size;
+	errno_t rc;
+
+	rc = async_data_write_accept(&data, false, 0, 0, 0, &size);
+	if (rc != EOK) {
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	if (srv->srvs->ops->write == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	size_t nwritten = 0;
+	rc = srv->srvs->ops->write(srv, data, size, &nwritten);
+	free(data);
+
+	async_answer_1(icall, rc, nwritten);
+}
+
+static void con_sync_srv(con_srv_t *srv, ipc_call_t *icall)
+{
+	if (srv->srvs->ops->sync == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	srv->srvs->ops->sync(srv);
+	async_answer_0(icall, EOK);
+}
+
+static void con_clear_srv(con_srv_t *srv, ipc_call_t *icall)
+{
+	if (srv->srvs->ops->clear == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	srv->srvs->ops->clear(srv);
+	async_answer_0(icall, EOK);
+}
+
+static void con_set_pos_srv(con_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t col;
+	sysarg_t row;
+
+	col = ipc_get_arg1(icall);
+	row = ipc_get_arg2(icall);
+
+	if (srv->srvs->ops->set_pos == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	srv->srvs->ops->set_pos(srv, col, row);
+	async_answer_0(icall, EOK);
+}
+
+static void con_get_pos_srv(con_srv_t *srv, ipc_call_t *icall)
+{
+	errno_t rc;
+	sysarg_t col;
+	sysarg_t row;
+
+	if (srv->srvs->ops->get_pos == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->srvs->ops->get_pos(srv, &col, &row);
+	async_answer_2(icall, rc, col, row);
+}
+
+static void con_get_size_srv(con_srv_t *srv, ipc_call_t *icall)
+{
+	errno_t rc;
+	sysarg_t cols;
+	sysarg_t rows;
+
+	if (srv->srvs->ops->get_size == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->srvs->ops->get_size(srv, &cols, &rows);
+	async_answer_2(icall, rc, cols, rows);
+}
+
+static void con_get_color_cap_srv(con_srv_t *srv, ipc_call_t *icall)
+{
+	errno_t rc;
+	console_caps_t ccap;
+
+	if (srv->srvs->ops->get_color_cap == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->srvs->ops->get_color_cap(srv, &ccap);
+	async_answer_1(icall, rc, (sysarg_t)ccap);
+}
+
+static void con_set_style_srv(con_srv_t *srv, ipc_call_t *icall)
+{
+	console_style_t style;
+
+	style = ipc_get_arg1(icall);
+
+	if (srv->srvs->ops->set_style == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	srv->srvs->ops->set_style(srv, style);
+	async_answer_0(icall, EOK);
+}
+
+static void con_set_color_srv(con_srv_t *srv, ipc_call_t *icall)
+{
+	console_color_t bgcolor;
+	console_color_t fgcolor;
+	console_color_attr_t flags;
+
+	bgcolor = ipc_get_arg1(icall);
+	fgcolor = ipc_get_arg2(icall);
+	flags = ipc_get_arg3(icall);
+
+	if (srv->srvs->ops->set_color == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	srv->srvs->ops->set_color(srv, bgcolor, fgcolor, flags);
+	async_answer_0(icall, EOK);
+}
+
+static void con_set_rgb_color_srv(con_srv_t *srv, ipc_call_t *icall)
+{
+	pixel_t bgcolor;
+	pixel_t fgcolor;
+
+	bgcolor = ipc_get_arg1(icall);
+	fgcolor = ipc_get_arg2(icall);
+
+	if (srv->srvs->ops->set_rgb_color == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	srv->srvs->ops->set_rgb_color(srv, bgcolor, fgcolor);
+	async_answer_0(icall, EOK);
+}
+
+static void con_set_cursor_visibility_srv(con_srv_t *srv, ipc_call_t *icall)
+{
+	bool show;
+
+	show = ipc_get_arg1(icall);
+
+	if (srv->srvs->ops->set_cursor_visibility == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	srv->srvs->ops->set_cursor_visibility(srv, show);
+	async_answer_0(icall, EOK);
+}
+
+static void con_set_caption_srv(con_srv_t *srv, ipc_call_t *icall)
+{
+	char *caption;
+	errno_t rc;
+
+	rc = async_data_write_accept((void **) &caption, true, 0,
+	    CON_CAPTION_MAXLEN, 0, NULL);
+	if (rc != EOK) {
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	if (srv->srvs->ops->set_caption == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	srv->srvs->ops->set_caption(srv, caption);
+	free(caption);
+	async_answer_0(icall, EOK);
+}
+
+static void con_get_event_srv(con_srv_t *srv, ipc_call_t *icall)
+{
+	errno_t rc;
+	cons_event_t event;
+	ipc_call_t result;
+
+	if (srv->srvs->ops->get_event == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->srvs->ops->get_event(srv, &event);
+	if (rc != EOK) {
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	rc = console_ev_encode(&event, &result);
+	if (rc != EOK) {
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	async_answer_5(icall, rc, ipc_get_arg1(&result), ipc_get_arg2(&result),
+	    ipc_get_arg3(&result), ipc_get_arg4(&result), ipc_get_arg5(&result));
+}
+
+/** Create shared buffer for efficient rendering */
+static void con_map_srv(con_srv_t *srv, ipc_call_t *icall)
+{
+	errno_t rc;
+	charfield_t *buf;
+	sysarg_t cols, rows;
+	ipc_call_t call;
+	size_t size;
+
+	if (srv->srvs->ops->map == NULL || srv->srvs->ops->unmap == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	cols = ipc_get_arg1(icall);
+	rows = ipc_get_arg2(icall);
+
+	if (!async_share_in_receive(&call, &size)) {
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	/* Check size */
+	if (size != PAGES2SIZE(SIZE2PAGES(cols * rows * sizeof(charfield_t)))) {
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	rc = srv->srvs->ops->map(srv, cols, rows, &buf);
+	if (rc != EOK) {
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	rc = async_share_in_finalize(&call, buf, AS_AREA_READ |
+	    AS_AREA_WRITE | AS_AREA_CACHEABLE);
+	if (rc != EOK) {
+		srv->srvs->ops->unmap(srv);
+		async_answer_0(icall, EIO);
+		return;
+	}
+
+	async_answer_0(icall, EOK);
+}
+
+/** Delete shared buffer */
+static void con_unmap_srv(con_srv_t *srv, ipc_call_t *icall)
+{
+	if (srv->srvs->ops->unmap == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	srv->srvs->ops->unmap(srv);
+	async_answer_0(icall, EOK);
+}
+
+/** Update console area from shared buffer */
+static void con_update_srv(con_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t c0, r0, c1, r1;
+
+	c0 = ipc_get_arg1(icall);
+	r0 = ipc_get_arg2(icall);
+	c1 = ipc_get_arg3(icall);
+	r1 = ipc_get_arg4(icall);
+
+	if (srv->srvs->ops->update == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	srv->srvs->ops->update(srv, c0, r0, c1, r1);
+	async_answer_0(icall, EOK);
+}
+
+static con_srv_t *con_srv_create(con_srvs_t *srvs)
+{
+	con_srv_t *srv;
+
+	srv = calloc(1, sizeof(*srv));
+	if (srv == NULL)
+		return NULL;
+
+	srv->srvs = srvs;
+	return srv;
+}
+
+void con_srvs_init(con_srvs_t *srvs)
+{
+	srvs->ops = NULL;
+	srvs->sarg = NULL;
+	srvs->abort_timeout = 0;
+	srvs->aborted = false;
+}
+
+errno_t con_conn(ipc_call_t *icall, con_srvs_t *srvs)
+{
+	con_srv_t *srv;
+	errno_t rc;
+
+	/* Accept the connection */
+	async_accept_0(icall);
+
+	srv = con_srv_create(srvs);
+	if (srv == NULL)
+		return ENOMEM;
+
+	srv->client_sess = NULL;
+
+	rc = srvs->ops->open(srvs, srv);
+	if (rc != EOK)
+		return rc;
+
+	while (true) {
+		ipc_call_t call;
+		bool received = false;
+
+		while (!received) {
+			/* XXX Need to be able to abort immediately */
+			received = async_get_call_timeout(&call,
+			    srvs->abort_timeout);
+
+			if (srv->srvs->aborted) {
+				if (received)
+					async_answer_0(&call, EINTR);
+				break;
+			}
+		}
+
+		if (!received)
+			break;
+
+		sysarg_t method = ipc_get_imethod(&call);
+
+		if (!method) {
+			/* The other side has hung up */
+			async_answer_0(&call, EOK);
+			break;
+		}
+
+		switch (method) {
+		case VFS_OUT_READ:
+			con_read_srv(srv, &call);
+			break;
+		case VFS_OUT_WRITE:
+			con_write_srv(srv, &call);
+			break;
+		case VFS_OUT_SYNC:
+			con_sync_srv(srv, &call);
+			break;
+		case CONSOLE_CLEAR:
+			con_clear_srv(srv, &call);
+			break;
+		case CONSOLE_SET_POS:
+			con_set_pos_srv(srv, &call);
+			break;
+		case CONSOLE_GET_POS:
+			con_get_pos_srv(srv, &call);
+			break;
+		case CONSOLE_GET_SIZE:
+			con_get_size_srv(srv, &call);
+			break;
+		case CONSOLE_GET_COLOR_CAP:
+			con_get_color_cap_srv(srv, &call);
+			break;
+		case CONSOLE_SET_STYLE:
+			con_set_style_srv(srv, &call);
+			break;
+		case CONSOLE_SET_COLOR:
+			con_set_color_srv(srv, &call);
+			break;
+		case CONSOLE_SET_RGB_COLOR:
+			con_set_rgb_color_srv(srv, &call);
+			break;
+		case CONSOLE_SET_CURSOR_VISIBILITY:
+			con_set_cursor_visibility_srv(srv, &call);
+			break;
+		case CONSOLE_SET_CAPTION:
+			con_set_caption_srv(srv, &call);
+			break;
+		case CONSOLE_GET_EVENT:
+			con_get_event_srv(srv, &call);
+			break;
+		case CONSOLE_MAP:
+			con_map_srv(srv, &call);
+			break;
+		case CONSOLE_UNMAP:
+			con_unmap_srv(srv, &call);
+			break;
+		case CONSOLE_UPDATE:
+			con_update_srv(srv, &call);
+			break;
+		default:
+			async_answer_0(&call, ENOTSUP);
+		}
+	}
+
+	rc = srvs->ops->close(srv);
+	free(srv);
+
+	return rc;
+}
+
+/** @}
+ */
Index: uspace/lib/console/src/console.c
===================================================================
--- uspace/lib/console/src/console.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/console/src/console.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,379 @@
+/*
+ * Copyright (c) 2024 Jiri Svoboda
+ * Copyright (c) 2006 Josef Cejka
+ * Copyright (c) 2006 Jakub Vana
+ * 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 libconsole
+ * @{
+ */
+/** @file
+ */
+
+#include <as.h>
+#include <dbgcon.h>
+#include <libc.h>
+#include <async.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <str.h>
+#include <vfs/vfs_sess.h>
+#include <io/console.h>
+#include <ipc/console.h>
+
+console_ctrl_t *console_init(FILE *ifile, FILE *ofile)
+{
+	console_ctrl_t *ctrl = malloc(sizeof(console_ctrl_t));
+	if (!ctrl)
+		return NULL;
+
+	ctrl->input_sess = vfs_fsession(ifile, INTERFACE_CONSOLE);
+	if (!ctrl->input_sess) {
+		free(ctrl);
+		return NULL;
+	}
+
+	ctrl->output_sess = vfs_fsession(ofile, INTERFACE_CONSOLE);
+	if (!ctrl->output_sess) {
+		free(ctrl);
+		return NULL;
+	}
+
+	ctrl->input = ifile;
+	ctrl->output = ofile;
+	ctrl->input_aid = 0;
+
+	return ctrl;
+}
+
+void console_done(console_ctrl_t *ctrl)
+{
+	free(ctrl);
+}
+
+bool console_kcon(void)
+{
+	return dbgcon_enable();
+}
+
+void console_flush(console_ctrl_t *ctrl)
+{
+	fflush(ctrl->output);
+}
+
+void console_clear(console_ctrl_t *ctrl)
+{
+	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
+	async_req_0_0(exch, CONSOLE_CLEAR);
+	async_exchange_end(exch);
+}
+
+errno_t console_get_size(console_ctrl_t *ctrl, sysarg_t *cols, sysarg_t *rows)
+{
+	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
+	errno_t rc = async_req_0_2(exch, CONSOLE_GET_SIZE, cols, rows);
+	async_exchange_end(exch);
+
+	return rc;
+}
+
+void console_set_style(console_ctrl_t *ctrl, uint8_t style)
+{
+	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
+	async_req_1_0(exch, CONSOLE_SET_STYLE, style);
+	async_exchange_end(exch);
+}
+
+void console_set_color(console_ctrl_t *ctrl, uint8_t bgcolor, uint8_t fgcolor,
+    uint8_t flags)
+{
+	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
+	async_req_3_0(exch, CONSOLE_SET_COLOR, bgcolor, fgcolor, flags);
+	async_exchange_end(exch);
+}
+
+void console_set_rgb_color(console_ctrl_t *ctrl, uint32_t bgcolor,
+    uint32_t fgcolor)
+{
+	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
+	async_req_2_0(exch, CONSOLE_SET_RGB_COLOR, bgcolor, fgcolor);
+	async_exchange_end(exch);
+}
+
+void console_cursor_visibility(console_ctrl_t *ctrl, bool show)
+{
+	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
+	async_req_1_0(exch, CONSOLE_SET_CURSOR_VISIBILITY, (show != false));
+	async_exchange_end(exch);
+}
+
+/** Set console caption.
+ *
+ * Set caption text for the console (if the console suports captions).
+ *
+ * @param ctrl Console
+ * @param caption Caption text
+ * @return EOK on success or an error code
+ */
+errno_t console_set_caption(console_ctrl_t *ctrl, const char *caption)
+{
+	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
+	ipc_call_t answer;
+	aid_t req = async_send_0(exch, CONSOLE_SET_CAPTION, &answer);
+	errno_t retval = async_data_write_start(exch, caption, str_size(caption));
+
+	if (retval != EOK) {
+		async_forget(req);
+		async_exchange_end(exch);
+		return retval;
+	}
+
+	async_wait_for(req, &retval);
+	async_exchange_end(exch);
+	return EOK;
+}
+
+errno_t console_get_color_cap(console_ctrl_t *ctrl, sysarg_t *ccap)
+{
+	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
+	errno_t rc = async_req_0_1(exch, CONSOLE_GET_COLOR_CAP, ccap);
+	async_exchange_end(exch);
+
+	return rc;
+}
+
+errno_t console_get_pos(console_ctrl_t *ctrl, sysarg_t *col, sysarg_t *row)
+{
+	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
+	errno_t rc = async_req_0_2(exch, CONSOLE_GET_POS, col, row);
+	async_exchange_end(exch);
+
+	return rc;
+}
+
+void console_set_pos(console_ctrl_t *ctrl, sysarg_t col, sysarg_t row)
+{
+	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
+	async_req_2_0(exch, CONSOLE_SET_POS, col, row);
+	async_exchange_end(exch);
+}
+
+static errno_t console_ev_decode(ipc_call_t *call, cons_event_t *event)
+{
+	event->type = ipc_get_arg1(call);
+
+	switch (event->type) {
+	case CEV_KEY:
+		event->ev.key.type = ipc_get_arg2(call);
+		event->ev.key.key = ipc_get_arg3(call);
+		event->ev.key.mods = ipc_get_arg4(call);
+		event->ev.key.c = ipc_get_arg5(call);
+		break;
+	case CEV_POS:
+		event->ev.pos.pos_id = ipc_get_arg2(call) >> 16;
+		event->ev.pos.type = ipc_get_arg2(call) & 0xffff;
+		event->ev.pos.btn_num = ipc_get_arg3(call);
+		event->ev.pos.hpos = ipc_get_arg4(call);
+		event->ev.pos.vpos = ipc_get_arg5(call);
+		break;
+	default:
+		return EIO;
+	}
+
+	return EOK;
+}
+
+/** Get console event.
+ *
+ * @param ctrl Console
+ * @param event Place to store event
+ * @return EOK on success, EIO on failure
+ */
+errno_t console_get_event(console_ctrl_t *ctrl, cons_event_t *event)
+{
+	if (ctrl->input_aid == 0) {
+		ipc_call_t result;
+
+		async_exch_t *exch = async_exchange_begin(ctrl->input_sess);
+		aid_t aid = async_send_0(exch, CONSOLE_GET_EVENT, &result);
+		async_exchange_end(exch);
+
+		errno_t rc;
+		async_wait_for(aid, &rc);
+
+		if (rc != EOK)
+			return EIO;
+
+		rc = console_ev_decode(&result, event);
+		if (rc != EOK)
+			return EIO;
+	} else {
+		errno_t retval;
+		async_wait_for(ctrl->input_aid, &retval);
+
+		ctrl->input_aid = 0;
+
+		if (retval != EOK)
+			return EIO;
+
+		errno_t rc = console_ev_decode(&ctrl->input_call, event);
+		if (rc != EOK)
+			return EIO;
+	}
+
+	return EOK;
+}
+
+/** Get console event with timeout.
+ *
+ * @param ctrl Console
+ * @param event Place to store event
+ * @param timeout Pointer to timeout. This will be updated to reflect
+ *                the remaining time in case of timeout.
+ * @return EOK on success (event received), ETIMEOUT on time out,
+ *         EIO on I/O error (e.g. lost console connection), ENOMEM
+ *         if out of memory
+ */
+errno_t console_get_event_timeout(console_ctrl_t *ctrl, cons_event_t *event,
+    usec_t *timeout)
+{
+	struct timespec t0;
+	getuptime(&t0);
+
+	if (ctrl->input_aid == 0) {
+		async_exch_t *exch = async_exchange_begin(ctrl->input_sess);
+		ctrl->input_aid = async_send_0(exch, CONSOLE_GET_EVENT,
+		    &ctrl->input_call);
+		async_exchange_end(exch);
+	}
+
+	errno_t retval;
+	errno_t rc = async_wait_timeout(ctrl->input_aid, &retval, *timeout);
+	if (rc != EOK) {
+		if (rc == ENOMEM)
+			return ENOMEM;
+		*timeout = 0;
+		return ETIMEOUT;
+	}
+
+	ctrl->input_aid = 0;
+
+	if (retval != EOK)
+		return EIO;
+
+	rc = console_ev_decode(&ctrl->input_call, event);
+	if (rc != EOK)
+		return EIO;
+
+	/* Update timeout */
+	struct timespec t1;
+	getuptime(&t1);
+	*timeout -= NSEC2USEC(ts_sub_diff(&t1, &t0));
+
+	return EOK;
+}
+
+/** Create a shared buffer for fast rendering to the console.
+ *
+ * @param ctrl Console
+ * @param cols Number of columns
+ * @param rows Number of rows
+ * @param rbuf Place to store pointer to the shared buffer
+ * @return EOK on success or an error code
+ */
+errno_t console_map(console_ctrl_t *ctrl, sysarg_t cols, sysarg_t rows,
+    charfield_t **rbuf)
+{
+	async_exch_t *exch = NULL;
+	void *buf;
+	aid_t req;
+	ipc_call_t answer;
+	size_t asize;
+	errno_t rc;
+
+	exch = async_exchange_begin(ctrl->output_sess);
+	req = async_send_2(exch, CONSOLE_MAP, cols, rows, &answer);
+
+	asize = PAGES2SIZE(SIZE2PAGES(cols * rows * sizeof(charfield_t)));
+
+	rc = async_share_in_start_0_0(exch, asize, &buf);
+	if (rc != EOK) {
+		async_forget(req);
+		goto error;
+	}
+
+	async_exchange_end(exch);
+	exch = NULL;
+
+	async_wait_for(req, &rc);
+	if (rc != EOK)
+		goto error;
+
+	*rbuf = (charfield_t *)buf;
+	return EOK;
+error:
+	if (exch != NULL)
+		async_exchange_end(exch);
+	return rc;
+}
+
+/** Unmap console shared buffer.
+ *
+ * @param ctrl Console
+ * @param buf Buffer
+ */
+void console_unmap(console_ctrl_t *ctrl, charfield_t *buf)
+{
+	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
+	(void) async_req_0_0(exch, CONSOLE_UNMAP);
+	async_exchange_end(exch);
+
+	as_area_destroy(buf);
+}
+
+/** Update console rectangle from shared buffer.
+ *
+ * @param ctrl Console
+ * @param c0 Column coordinate of top-left corner (inclusive)
+ * @param r0 Row coordinate of top-left corner (inclusive)
+ * @param c1 Column coordinate of bottom-right corner (exclusive)
+ * @param r1 Row coordinate of bottom-right corner (exclusive)
+ *
+ * @return EOK on sucess or an error code
+ */
+errno_t console_update(console_ctrl_t *ctrl, sysarg_t c0, sysarg_t r0,
+    sysarg_t c1, sysarg_t r1)
+{
+	async_exch_t *exch = async_exchange_begin(ctrl->output_sess);
+	errno_t rc = async_req_4_0(exch, CONSOLE_UPDATE, c0, r0, c1, r1);
+	async_exchange_end(exch);
+
+	return rc;
+}
+
+/** @}
+ */
Index: uspace/lib/corecfg/doc/doxygroups.h
===================================================================
--- uspace/lib/corecfg/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/corecfg/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,3 @@
+/** @addtogroup libcorecfg libcorecfg
+ * @ingroup libs
+ */
Index: uspace/lib/corecfg/include/corecfg.h
===================================================================
--- uspace/lib/corecfg/include/corecfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/corecfg/include/corecfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,48 @@
+/*
+ * 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 libcorecfg
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBCORECFG_CORECFG_H_
+#define _LIBCORECFG_CORECFG_H_
+
+#include <errno.h>
+#include <stdbool.h>
+
+extern errno_t corecfg_init(void);
+extern errno_t corecfg_set_enable(bool);
+extern errno_t corecfg_get_enable(bool *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/corecfg/include/ipc/corecfg.h
===================================================================
--- uspace/lib/corecfg/include/ipc/corecfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/corecfg/include/ipc/corecfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,46 @@
+/*
+ * 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 libcorecfg
+ * @{
+ */
+
+#ifndef _LIBCORECFG_IPC_CORECFG_H_
+#define _LIBCORECFG_IPC_CORECFG_H_
+
+#include <ipc/common.h>
+
+typedef enum {
+	CORECFG_GET_ENABLE = IPC_FIRST_USER_METHOD,
+	CORECFG_SET_ENABLE
+} corecfg_request_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/corecfg/meson.build
===================================================================
--- uspace/lib/corecfg/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/corecfg/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,29 @@
+#
+# 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.
+#
+
+src = files('src/corecfg.c')
Index: uspace/lib/corecfg/src/corecfg.c
===================================================================
--- uspace/lib/corecfg/src/corecfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/corecfg/src/corecfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2013 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <async.h>
+#include <assert.h>
+#include <corecfg.h>
+#include <errno.h>
+#include <ipc/corecfg.h>
+#include <ipc/services.h>
+#include <loc.h>
+#include <stdlib.h>
+
+static async_sess_t *corecfg_sess = NULL;
+
+errno_t corecfg_init(void)
+{
+	service_id_t corecfg_svc;
+	errno_t rc;
+
+	assert(corecfg_sess == NULL);
+
+	rc = loc_service_get_id(SERVICE_NAME_CORECFG, &corecfg_svc,
+	    IPC_FLAG_BLOCKING);
+	if (rc != EOK)
+		return ENOENT;
+
+	corecfg_sess = loc_service_connect(corecfg_svc, INTERFACE_CORECFG,
+	    IPC_FLAG_BLOCKING);
+	if (corecfg_sess == NULL)
+		return ENOENT;
+
+	return EOK;
+}
+
+/** Get core dump enable status. */
+errno_t corecfg_get_enable(bool *renable)
+{
+	async_exch_t *exch = async_exchange_begin(corecfg_sess);
+	sysarg_t enable;
+
+	errno_t rc = async_req_0_1(exch, CORECFG_GET_ENABLE, &enable);
+
+	async_exchange_end(exch);
+
+	if (rc != EOK)
+		return rc;
+
+	*renable = enable;
+	return EOK;
+}
+
+/** Enable or disable core dumps. */
+errno_t corecfg_set_enable(bool enable)
+{
+	async_exch_t *exch = async_exchange_begin(corecfg_sess);
+	errno_t rc = async_req_1_0(exch, CORECFG_SET_ENABLE, (sysarg_t) enable);
+
+	async_exchange_end(exch);
+
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** @}
+ */
Index: uspace/lib/cpp/include/__bits/adt/hash_table.hpp
===================================================================
--- uspace/lib/cpp/include/__bits/adt/hash_table.hpp	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/cpp/include/__bits/adt/hash_table.hpp	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -252,6 +252,6 @@
             void swap(hash_table& other)
                 noexcept(allocator_traits<allocator_type>::is_always_equal::value &&
-                         noexcept(swap(declval<Hasher&>(), declval<Hasher&>())) &&
-                         noexcept(swap(declval<KeyEq&>(), declval<KeyEq&>())))
+                         noexcept(std::swap(declval<Hasher&>(), declval<Hasher&>())) &&
+                         noexcept(std::swap(declval<KeyEq&>(), declval<KeyEq&>())))
             {
                 std::swap(table_, other.table_);
Index: uspace/lib/cpp/include/__bits/adt/rbtree.hpp
===================================================================
--- uspace/lib/cpp/include/__bits/adt/rbtree.hpp	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/cpp/include/__bits/adt/rbtree.hpp	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -233,5 +233,5 @@
             void swap(rbtree& other)
                 noexcept(allocator_traits<allocator_type>::is_always_equal::value &&
-                         noexcept(swap(declval<KeyComp&>(), declval<KeyComp&>())))
+                         noexcept(std::swap(declval<KeyComp&>(), declval<KeyComp&>())))
             {
                 std::swap(root_, other.root_);
Index: uspace/lib/cpp/include/__bits/algorithm.hpp
===================================================================
--- uspace/lib/cpp/include/__bits/algorithm.hpp	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/cpp/include/__bits/algorithm.hpp	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1110,12 +1110,20 @@
                                  InputIterator2 last2)
     {
+        while ((first1 != last1) && (first2 != last2))
+        {
+            if (*first1 < *first2)
+                return true;
+            if (*first2 < *first1)
+                return false;
+
+            ++first1;
+            ++first2;
+        }
+
         /**
-         * *first1 and *first2 can have different types
-         * so we use a transparent comparator.
+         * Up until now they are same, so we have to check
+         * if we reached the end on one.
          */
-        return lexicographical_compare(
-            first1, last1, first2, last2,
-            less<void>{}
-        );
+        return (first1 == last1) && (first2 != last2);
     }
 
Index: uspace/lib/cpp/include/__bits/string/string.hpp
===================================================================
--- uspace/lib/cpp/include/__bits/string/string.hpp	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/cpp/include/__bits/string/string.hpp	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -521,5 +521,5 @@
 
             basic_string(size_type n, value_type c, const allocator_type& alloc = allocator_type{})
-                : data_{}, size_{n}, capacity_{n}, allocator_{alloc}
+                : data_{}, size_{n}, capacity_{n + 1}, allocator_{alloc}
             {
                 data_ = allocator_.allocate(capacity_);
@@ -908,5 +908,5 @@
             {
                 // TODO: if (n > max_size()) throw length_error.
-                resize_without_copy_(n);
+                resize_without_copy_(n + 1);
                 traits_type::copy(begin(), str, n);
                 size_ = n;
Index: uspace/lib/cpp/include/__bits/thread/threading.hpp
===================================================================
--- uspace/lib/cpp/include/__bits/thread/threading.hpp	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/cpp/include/__bits/thread/threading.hpp	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -87,5 +87,5 @@
         struct mutex
         {
-            static void init(mutex_type& mtx)
+            static constexpr void init(mutex_type& mtx)
             {
                 ::helenos::fibril_mutex_initialize(&mtx);
Index: uspace/lib/cpp/src/__bits/unwind.cpp
===================================================================
--- uspace/lib/cpp/src/__bits/unwind.cpp	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/cpp/src/__bits/unwind.cpp	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -30,4 +30,5 @@
 #include <cstdint>
 #include <cstdlib>
+#include <typeinfo>
 
 namespace __cxxabiv1
Index: uspace/lib/cpp/src/future.cpp
===================================================================
--- uspace/lib/cpp/src/future.cpp	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/cpp/src/future.cpp	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -84,5 +84,14 @@
     const char* future_error::what() const noexcept
     {
-        return code().message().c_str();
+        /*
+         * FIXME
+         * Following code would be optimal but the message string is
+         * actually destroyed before the function returns so we would
+         * be returning a dangling pointer. No simple fix available, hence
+         * we use the ugly hack.
+         */
+        //return code().message().c_str();
+#define QUOTE_ARG(arg) #arg
+        return "future_error, see " __FILE__ ":" QUOTE_ARG(__LINE__);
     }
 }
Index: uspace/lib/ddev/src/ddev_srv.c
===================================================================
--- uspace/lib/ddev/src/ddev_srv.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ddev/src/ddev_srv.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -101,4 +101,5 @@
 	rc = async_data_read_finalize(&call, &info, sizeof(ddev_info_t));
 	if (rc != EOK) {
+		async_answer_0(&call, rc);
 		async_answer_0(icall, rc);
 		return;
Index: uspace/lib/ddev/test/ddev.c
===================================================================
--- uspace/lib/ddev/test/ddev.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ddev/test/ddev.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -78,21 +78,23 @@
 	ddev_t *ddev = NULL;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_ddev_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ddev_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ddev_svc, &sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ddev_open(test_ddev_svc, &ddev);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(ddev);
-
-	ddev_close(ddev);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ddev_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ddev_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ddev_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ddev_open(test_ddev_svc, &ddev);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(ddev);
+
+	ddev_close(ddev);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -105,12 +107,13 @@
 	test_response_t resp;
 	gfx_context_t *gc;
-
-	async_set_fallback_port_handler(test_ddev_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ddev_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ddev_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ddev_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ddev_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ddev_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -122,11 +125,11 @@
 	resp.rc = ENOMEM;
 	rc = ddev_get_gc(ddev, &gc);
-	/* async_connect_me_to() does not return specific error */
-	PCUT_ASSERT_ERRNO_VAL(EIO, rc);
+	PCUT_ASSERT_ERRNO_VAL(ENOMEM, rc);
 	PCUT_ASSERT_NULL(gc);
 
 	ddev_close(ddev);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -140,12 +143,13 @@
 	gfx_context_t *gc;
 	gfx_color_t *color;
-
-	async_set_fallback_port_handler(test_ddev_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ddev_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ddev_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ddev_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ddev_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ddev_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -171,6 +175,7 @@
 
 	ddev_close(ddev);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -183,12 +188,13 @@
 	test_response_t resp;
 	ddev_info_t info;
-
-	async_set_fallback_port_handler(test_ddev_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ddev_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ddev_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ddev_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ddev_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ddev_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -202,6 +208,7 @@
 
 	ddev_close(ddev);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -214,12 +221,13 @@
 	test_response_t resp;
 	ddev_info_t info;
-
-	async_set_fallback_port_handler(test_ddev_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ddev_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ddev_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ddev_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ddev_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ddev_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -245,6 +253,7 @@
 
 	ddev_close(ddev);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
Index: uspace/lib/device/src/bd_srv.c
===================================================================
--- uspace/lib/device/src/bd_srv.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/device/src/bd_srv.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2012 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -56,4 +56,5 @@
 	ipc_call_t rcall;
 	if (!async_data_read_receive(&rcall, &size)) {
+		async_answer_0(&rcall, EINVAL);
 		async_answer_0(call, EINVAL);
 		return;
@@ -99,4 +100,5 @@
 	ipc_call_t rcall;
 	if (!async_data_read_receive(&rcall, &size)) {
+		async_answer_0(&rcall, EINVAL);
 		async_answer_0(call, EINVAL);
 		return;
Index: uspace/lib/device/src/io/chardev_srv.c
===================================================================
--- uspace/lib/device/src/io/chardev_srv.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/device/src/io/chardev_srv.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2014 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -55,4 +55,5 @@
 	ipc_call_t call;
 	if (!async_data_read_receive(&call, &size)) {
+		async_answer_0(&call, EINVAL);
 		async_answer_0(icall, EINVAL);
 		return;
Index: uspace/lib/dispcfg/doc/doxygroups.h
===================================================================
--- uspace/lib/dispcfg/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/dispcfg/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,3 @@
+/** @addtogroup libdispcfg libdispcfg
+ * @ingroup libs
+ */
Index: uspace/lib/dispcfg/include/dispcfg.h
===================================================================
--- uspace/lib/dispcfg/include/dispcfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/dispcfg/include/dispcfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,60 @@
+/*
+ * 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 libdispcfg
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBDISPCFG_DISPCFG_H_
+#define _LIBDISPCFG_DISPCFG_H_
+
+#include <errno.h>
+#include <types/common.h>
+#include "types/dispcfg.h"
+
+extern errno_t dispcfg_open(const char *, dispcfg_cb_t *, void *, dispcfg_t **);
+extern void dispcfg_close(dispcfg_t *);
+extern errno_t dispcfg_get_seat_list(dispcfg_t *, dispcfg_seat_list_t **);
+extern void dispcfg_free_seat_list(dispcfg_seat_list_t *);
+extern errno_t dispcfg_get_seat_info(dispcfg_t *, sysarg_t,
+    dispcfg_seat_info_t **);
+extern void dispcfg_free_seat_info(dispcfg_seat_info_t *);
+extern errno_t dispcfg_seat_create(dispcfg_t *, const char *, sysarg_t *);
+extern errno_t dispcfg_seat_delete(dispcfg_t *, sysarg_t);
+extern errno_t dispcfg_dev_assign(dispcfg_t *, sysarg_t, sysarg_t);
+extern errno_t dispcfg_dev_unassign(dispcfg_t *, sysarg_t);
+extern errno_t dispcfg_get_asgn_dev_list(dispcfg_t *, sysarg_t,
+    dispcfg_dev_list_t **);
+extern void dispcfg_free_dev_list(dispcfg_dev_list_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/dispcfg/include/dispcfg_srv.h
===================================================================
--- uspace/lib/dispcfg/include/dispcfg_srv.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/dispcfg/include/dispcfg_srv.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,69 @@
+/*
+ * 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 libdispcfg
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBDISPCFG_DISPCFG_SRV_H_
+#define _LIBDISPCFG_DISPCFG_SRV_H_
+
+#include <async.h>
+#include <errno.h>
+#include "types/dispcfg.h"
+
+typedef struct dispcfg_ops dispcfg_ops_t;
+
+/** Display configuration server structure (per client session) */
+typedef struct {
+	async_sess_t *client_sess;
+	dispcfg_ops_t *ops;
+	void *arg;
+} dispcfg_srv_t;
+
+struct dispcfg_ops {
+	errno_t (*get_seat_list)(void *, dispcfg_seat_list_t **);
+	errno_t (*get_seat_info)(void *, sysarg_t, dispcfg_seat_info_t **);
+	errno_t (*seat_create)(void *, const char *, sysarg_t *);
+	errno_t (*seat_delete)(void *, sysarg_t);
+	errno_t (*dev_assign)(void *, sysarg_t, sysarg_t);
+	errno_t (*dev_unassign)(void *, sysarg_t);
+	errno_t (*get_asgn_dev_list)(void *, sysarg_t, dispcfg_dev_list_t **);
+	errno_t (*get_event)(void *, dispcfg_ev_t *);
+};
+
+extern void dispcfg_conn(ipc_call_t *, dispcfg_srv_t *);
+extern void dispcfg_srv_initialize(dispcfg_srv_t *);
+extern void dispcfg_srv_ev_pending(dispcfg_srv_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/dispcfg/include/ipc/dispcfg.h
===================================================================
--- uspace/lib/dispcfg/include/ipc/dispcfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/dispcfg/include/ipc/dispcfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,59 @@
+/*
+ * 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 libdispcfg
+ * @{
+ */
+/** @file
+ */
+
+#ifndef LIBDISPCFG_IPC_DISPCFG_H
+#define LIBDISPCFG_IPC_DISPCFG_H
+
+#include <ipc/common.h>
+
+typedef enum {
+	DISPCFG_CALLBACK_CREATE = IPC_FIRST_USER_METHOD,
+	DISPCFG_GET_SEAT_LIST,
+	DISPCFG_GET_SEAT_INFO,
+	DISPCFG_SEAT_CREATE,
+	DISPCFG_SEAT_DELETE,
+	DISPCFG_DEV_ASSIGN,
+	DISPCFG_DEV_UNASSIGN,
+	DISPCFG_GET_ASGN_DEV_LIST,
+	DISPCFG_GET_EVENT,
+} dispcfg_request_t;
+
+typedef enum {
+	DISPCFG_EV_PENDING = IPC_FIRST_USER_METHOD
+} dispcfg_event_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/dispcfg/include/testdc.h
===================================================================
--- uspace/lib/dispcfg/include/testdc.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/dispcfg/include/testdc.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,48 @@
+/*
+ * 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 libdispcfg
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBDISPCFG_TESTDC_H_
+#define _LIBDISPCFG_TESTDC_H_
+
+#include <async.h>
+#include "types/testdc.h"
+
+extern dispcfg_cb_t test_dispcfg_cb;
+
+extern void test_dispcfg_conn(ipc_call_t *, void *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/dispcfg/include/types/dispcfg.h
===================================================================
--- uspace/lib/dispcfg/include/types/dispcfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/dispcfg/include/types/dispcfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,98 @@
+/*
+ * 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 libdispcfg
+ * @{
+ */
+/** @file Display configuration protocol types
+ */
+
+#ifndef _LIBDISPCFG_TYPES_DISPCFG_H_
+#define _LIBDISPCFG_TYPES_DISPCFG_H_
+
+#include <ipc/services.h>
+#include <types/common.h>
+
+/** Use the default display configuration service (argument to dispcfg_open() */
+#define DISPCFG_DEFAULT SERVICE_NAME_DISPCFG
+
+struct dispcfg;
+
+/** Display configuration session */
+typedef struct dispcfg dispcfg_t;
+
+/** Display configuration callbacks */
+typedef struct {
+	/** Seat added */
+	void (*seat_added)(void *, sysarg_t);
+	/** Seat removed */
+	void (*seat_removed)(void *, sysarg_t);
+} dispcfg_cb_t;
+
+/** Display configuration event type */
+typedef enum {
+	/** Seat added */
+	dcev_seat_added,
+	/** Seat removed */
+	dcev_seat_removed,
+} dispcfg_ev_type_t;
+
+/** Display configuration event */
+typedef struct {
+	/** Event type */
+	dispcfg_ev_type_t etype;
+	/** Seat ID */
+	sysarg_t seat_id;
+} dispcfg_ev_t;
+
+/** Seat list */
+typedef struct {
+	/** Number of seats */
+	size_t nseats;
+	/** ID for each seat */
+	sysarg_t *seats;
+} dispcfg_seat_list_t;
+
+/** Seat information */
+typedef struct {
+	/** Seat name */
+	char *name;
+} dispcfg_seat_info_t;
+
+/** Assigned device list */
+typedef struct {
+	/** Number of devices */
+	size_t ndevs;
+	/** ID for each device */
+	sysarg_t *devs;
+} dispcfg_dev_list_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/dispcfg/include/types/testdc.h
===================================================================
--- uspace/lib/dispcfg/include/types/testdc.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/dispcfg/include/types/testdc.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,98 @@
+/*
+ * 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 libdispcfg
+ * @{
+ */
+/** @file Display configuration test service types
+ */
+
+#ifndef _LIBDISPCFG_TYPES_TESTDC_H_
+#define _LIBDISPCFG_TYPES_TESTDC_H_
+
+#include <errno.h>
+#include <fibril_synch.h>
+#include <stdbool.h>
+#include <types/common.h>
+#include <types/dispcfg.h>
+
+/** Describes to the server how to respond to our request and pass tracking
+ * data back to the client.
+ */
+typedef struct {
+	errno_t rc;
+	sysarg_t seat_id;
+	dispcfg_ev_t event;
+	dispcfg_ev_t revent;
+	int event_cnt;
+
+	bool get_seat_list_called;
+	dispcfg_seat_list_t *get_seat_list_rlist;
+
+	bool get_seat_info_called;
+	sysarg_t get_seat_info_seat_id;
+	dispcfg_seat_info_t *get_seat_info_rinfo;
+
+	bool seat_create_called;
+	char *seat_create_name;
+	sysarg_t seat_create_seat_id;
+
+	bool seat_delete_called;
+	sysarg_t seat_delete_seat_id;
+
+	bool dev_assign_called;
+	sysarg_t dev_assign_svc_id;
+	sysarg_t dev_assign_seat_id;
+
+	bool dev_unassign_called;
+	sysarg_t dev_unassign_svc_id;
+
+	bool get_asgn_dev_list_called;
+	sysarg_t get_asgn_dev_list_seat_id;
+	dispcfg_dev_list_t *get_asgn_dev_list_rlist;
+
+	bool get_event_called;
+
+	bool seat_added_called;
+	sysarg_t seat_added_seat_id;
+
+	bool seat_removed_called;
+	sysarg_t seat_removed_seat_id;
+
+	bool seat_changed_called;
+	sysarg_t seat_changed_seat_id;
+
+	fibril_condvar_t event_cv;
+	fibril_mutex_t event_lock;
+	dispcfg_srv_t *srv;
+} test_response_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/dispcfg/meson.build
===================================================================
--- uspace/lib/dispcfg/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/dispcfg/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,38 @@
+#
+# 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.
+#
+
+src = files(
+	'src/dispcfg.c',
+	'src/dispcfg_srv.c',
+	'src/testdc.c'
+)
+
+test_src = files(
+	'test/main.c',
+	'test/dispcfg.c'
+)
Index: uspace/lib/dispcfg/private/dispcfg.h
===================================================================
--- uspace/lib/dispcfg/private/dispcfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/dispcfg/private/dispcfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,61 @@
+/*
+ * 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 libdispcfg
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBDISPCFG_PRIVATE_DISPCFG_H_
+#define _LIBDISPCFG_PRIVATE_DISPCFG_H_
+
+#include <async.h>
+#include <fibril_synch.h>
+#include <stdbool.h>
+
+/** Display configuration session structure */
+struct dispcfg {
+	/** Session with display configuration service */
+	async_sess_t *sess;
+	/** Callbacks */
+	dispcfg_cb_t *cb;
+	/** Calback argument */
+	void *cb_arg;
+	/** Synchronize access to display configuration object */
+	fibril_mutex_t lock;
+	/** @c true if callback handler terminated */
+	bool cb_done;
+	/** Signalled when cb_done or ev_pending is changed */
+	fibril_condvar_t cv;
+};
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/dispcfg/src/dispcfg.c
===================================================================
--- uspace/lib/dispcfg/src/dispcfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/dispcfg/src/dispcfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,561 @@
+/*
+ * 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 libdispcfg
+ * @{
+ */
+/**
+ * @file
+ * @brief Display configuration protocol client
+ */
+
+#include <async.h>
+#include <dispcfg.h>
+#include <errno.h>
+#include <fibril_synch.h>
+#include <ipc/dispcfg.h>
+#include <ipc/services.h>
+#include <loc.h>
+#include <mem.h>
+#include <stdlib.h>
+#include <str.h>
+#include "../private/dispcfg.h"
+
+static errno_t dispcfg_callback_create(dispcfg_t *);
+static void dispcfg_cb_conn(ipc_call_t *, void *);
+
+/** Open display configuration service.
+ *
+ * @param wmname Display configuration service name or @c NULL to use default
+ * @param cb Display configuration callbacks
+ * @param cb_arg Callback argument
+ * @param rdispcfg Place to store pointer to display configuration session
+ * @return EOK on success or an error code
+ */
+errno_t dispcfg_open(const char *wmname, dispcfg_cb_t *cb, void *cb_arg,
+    dispcfg_t **rdispcfg)
+{
+	service_id_t dispcfg_svc;
+	dispcfg_t *dispcfg;
+	errno_t rc;
+
+	dispcfg = calloc(1, sizeof(dispcfg_t));
+	if (dispcfg == NULL)
+		return ENOMEM;
+
+	dispcfg->cb = cb;
+	dispcfg->cb_arg = cb_arg;
+
+	fibril_mutex_initialize(&dispcfg->lock);
+	fibril_condvar_initialize(&dispcfg->cv);
+
+	if (wmname == NULL)
+		wmname = SERVICE_NAME_DISPCFG;
+
+	rc = loc_service_get_id(wmname, &dispcfg_svc, 0);
+	if (rc != EOK) {
+		free(dispcfg);
+		return ENOENT;
+	}
+
+	dispcfg->sess = loc_service_connect(dispcfg_svc, INTERFACE_DISPCFG,
+	    0);
+	if (dispcfg->sess == NULL) {
+		free(dispcfg);
+		return ENOENT;
+	}
+
+	rc = dispcfg_callback_create(dispcfg);
+	if (rc != EOK) {
+		async_hangup(dispcfg->sess);
+		free(dispcfg);
+		return EIO;
+	}
+
+	*rdispcfg = dispcfg;
+	return EOK;
+}
+
+/** Create callback connection from display configuration service.
+ *
+ * @param dispcfg Display configuration
+ * @return EOK on success or an error code
+ */
+static errno_t dispcfg_callback_create(dispcfg_t *dispcfg)
+{
+	async_exch_t *exch = async_exchange_begin(dispcfg->sess);
+
+	aid_t req = async_send_0(exch, DISPCFG_CALLBACK_CREATE, NULL);
+
+	port_id_t port;
+	errno_t rc = async_create_callback_port(exch, INTERFACE_DISPCFG_CB, 0, 0,
+	    dispcfg_cb_conn, dispcfg, &port);
+
+	async_exchange_end(exch);
+
+	if (rc != EOK)
+		return rc;
+
+	errno_t retval;
+	async_wait_for(req, &retval);
+
+	return retval;
+}
+
+/** Close display configuration service.
+ *
+ * @param dispcfg Display configuration
+ */
+void dispcfg_close(dispcfg_t *dispcfg)
+{
+	fibril_mutex_lock(&dispcfg->lock);
+	async_hangup(dispcfg->sess);
+	dispcfg->sess = NULL;
+
+	/* Wait for callback handler to terminate */
+
+	while (!dispcfg->cb_done)
+		fibril_condvar_wait(&dispcfg->cv, &dispcfg->lock);
+	fibril_mutex_unlock(&dispcfg->lock);
+
+	free(dispcfg);
+}
+
+/** Get seat list.
+ *
+ * @param dispcfg Display configuration
+ * @param rlist Place to store pointer to new seat list structure
+ * @return EOK on success or an error code
+ */
+errno_t dispcfg_get_seat_list(dispcfg_t *dispcfg, dispcfg_seat_list_t **rlist)
+{
+	async_exch_t *exch;
+	aid_t req;
+	ipc_call_t answer;
+	dispcfg_seat_list_t *list;
+	sysarg_t nseats;
+	sysarg_t *seats;
+	errno_t rc;
+
+	exch = async_exchange_begin(dispcfg->sess);
+	req = async_send_0(exch, DISPCFG_GET_SEAT_LIST, &answer);
+
+	/* Receive seat list length */
+	rc = async_data_read_start(exch, &nseats, sizeof (nseats));
+	if (rc != EOK) {
+		async_exchange_end(exch);
+		async_wait_for(req, &rc);
+		return rc;
+	}
+
+	seats = calloc(nseats, sizeof(sysarg_t));
+	if (seats == NULL) {
+		async_exchange_end(exch);
+		async_forget(req);
+		return ENOMEM;
+	}
+
+	/* Receive seat list */
+	rc = async_data_read_start(exch, seats, nseats * sizeof (sysarg_t));
+	async_exchange_end(exch);
+
+	if (rc != EOK) {
+		async_forget(req);
+		return rc;
+	}
+
+	async_wait_for(req, &rc);
+	if (rc != EOK)
+		return rc;
+
+	list = calloc(1, sizeof(dispcfg_seat_list_t));
+	if (list == NULL)
+		return ENOMEM;
+
+	list->nseats = nseats;
+	list->seats = seats;
+	*rlist = list;
+	return EOK;
+}
+
+/** Free seat list.
+ *
+ * @param list Seat list
+ */
+void dispcfg_free_seat_list(dispcfg_seat_list_t *list)
+{
+	free(list->seats);
+	free(list);
+}
+
+/** Get seat information.
+ *
+ * @param dispcfg Display configuration
+ * @param seat_id Seat ID
+ * @param rinfo Place to store pointer to new seat information structure
+ * @return EOK on success or an error code
+ */
+errno_t dispcfg_get_seat_info(dispcfg_t *dispcfg, sysarg_t seat_id,
+    dispcfg_seat_info_t **rinfo)
+{
+	async_exch_t *exch;
+	aid_t req;
+	ipc_call_t answer;
+	dispcfg_seat_info_t *info;
+	sysarg_t namesize;
+	char *name;
+	errno_t rc;
+
+	exch = async_exchange_begin(dispcfg->sess);
+	req = async_send_1(exch, DISPCFG_GET_SEAT_INFO, seat_id, &answer);
+
+	/* Receive name size */
+	rc = async_data_read_start(exch, &namesize, sizeof (namesize));
+	if (rc != EOK) {
+		async_exchange_end(exch);
+		async_wait_for(req, &rc);
+		return rc;
+	}
+
+	name = calloc(namesize + 1, sizeof(char));
+	if (name == NULL) {
+		async_exchange_end(exch);
+		async_forget(req);
+		return ENOMEM;
+	}
+
+	/* Receive name */
+	rc = async_data_read_start(exch, name, namesize);
+	async_exchange_end(exch);
+
+	if (rc != EOK) {
+		async_forget(req);
+		return rc;
+	}
+
+	async_wait_for(req, &rc);
+	if (rc != EOK)
+		return rc;
+
+	/* Null-terminate the string */
+	name[namesize] = '\0';
+
+	info = calloc(1, sizeof(dispcfg_seat_info_t));
+	if (info == NULL)
+		return ENOMEM;
+
+	info->name = name;
+	*rinfo = info;
+	return EOK;
+}
+
+/** Free seat information.
+ *
+ * @param info Seat information
+ */
+void dispcfg_free_seat_info(dispcfg_seat_info_t *info)
+{
+	free(info->name);
+	free(info);
+}
+
+/** Create seat.
+ *
+ * @param dispcfg Display configuration session
+ * @param name Seat name
+ * @param rseat_id Place to store ID of the new seat
+ * @return EOK on success or an error code
+ */
+errno_t dispcfg_seat_create(dispcfg_t *dispcfg, const char *name,
+    sysarg_t *rseat_id)
+{
+	async_exch_t *exch;
+	aid_t req;
+	ipc_call_t answer;
+	size_t name_size;
+	errno_t rc;
+
+	name_size = str_size(name);
+
+	exch = async_exchange_begin(dispcfg->sess);
+	req = async_send_0(exch, DISPCFG_SEAT_CREATE, &answer);
+
+	/* Write name */
+	rc = async_data_write_start(exch, name, name_size);
+	async_exchange_end(exch);
+	if (rc != EOK) {
+		async_forget(req);
+		return rc;
+	}
+
+	async_wait_for(req, &rc);
+	if (rc != EOK)
+		return rc;
+
+	*rseat_id = ipc_get_arg1(&answer);
+	return EOK;
+}
+
+/** Delete seat.
+ *
+ * @param dispcfg Display configuration
+ * @param seat_id Seat ID
+ * @return EOK on success or an error code
+ */
+errno_t dispcfg_seat_delete(dispcfg_t *dispcfg, sysarg_t seat_id)
+{
+	async_exch_t *exch;
+	errno_t rc;
+
+	exch = async_exchange_begin(dispcfg->sess);
+	rc = async_req_1_0(exch, DISPCFG_SEAT_DELETE, seat_id);
+
+	async_exchange_end(exch);
+	return rc;
+}
+
+/** Assign device to seat.
+ *
+ * @param dispcfg Display configuration
+ * @param svc_id Device service ID
+ * @param seat_id Seat ID
+ * @return EOK on success or an error code
+ */
+errno_t dispcfg_dev_assign(dispcfg_t *dispcfg, sysarg_t svc_id,
+    sysarg_t seat_id)
+{
+	async_exch_t *exch;
+	errno_t rc;
+
+	exch = async_exchange_begin(dispcfg->sess);
+	rc = async_req_2_0(exch, DISPCFG_DEV_ASSIGN, svc_id, seat_id);
+
+	async_exchange_end(exch);
+	return rc;
+}
+
+/** Unassign device from any specific seat.
+ *
+ * The device will fall back to the default seat.
+ *
+ * @param dispcfg Display configuration
+ * @param svc_id Device service ID
+ * @return EOK on success or an error code
+ */
+errno_t dispcfg_dev_unassign(dispcfg_t *dispcfg, sysarg_t svc_id)
+{
+	async_exch_t *exch;
+	errno_t rc;
+
+	exch = async_exchange_begin(dispcfg->sess);
+	rc = async_req_1_0(exch, DISPCFG_DEV_UNASSIGN, svc_id);
+
+	async_exchange_end(exch);
+	return rc;
+}
+
+/** Get list of devices assigned to a seat.
+ *
+ * @param dispcfg Display configuration
+ * @param seat_id Seat ID
+ * @param rlist Place to store pointer to new device list structure
+ * @return EOK on success or an error code
+ */
+errno_t dispcfg_get_asgn_dev_list(dispcfg_t *dispcfg, sysarg_t seat_id,
+    dispcfg_dev_list_t **rlist)
+{
+	async_exch_t *exch;
+	aid_t req;
+	ipc_call_t answer;
+	dispcfg_dev_list_t *list;
+	sysarg_t ndevs;
+	sysarg_t *devs;
+	errno_t rc;
+
+	exch = async_exchange_begin(dispcfg->sess);
+	req = async_send_1(exch, DISPCFG_GET_ASGN_DEV_LIST, seat_id, &answer);
+
+	/* Receive device list length */
+	rc = async_data_read_start(exch, &ndevs, sizeof (ndevs));
+	if (rc != EOK) {
+		async_exchange_end(exch);
+		async_wait_for(req, &rc);
+		return rc;
+	}
+
+	devs = calloc(ndevs, sizeof(sysarg_t));
+	if (devs == NULL) {
+		async_exchange_end(exch);
+		async_forget(req);
+		return ENOMEM;
+	}
+
+	/* Receive device list */
+	rc = async_data_read_start(exch, devs, ndevs * sizeof (sysarg_t));
+	async_exchange_end(exch);
+
+	if (rc != EOK) {
+		async_forget(req);
+		return rc;
+	}
+
+	async_wait_for(req, &rc);
+	if (rc != EOK)
+		return rc;
+
+	list = calloc(1, sizeof(dispcfg_dev_list_t));
+	if (list == NULL)
+		return ENOMEM;
+
+	list->ndevs = ndevs;
+	list->devs = devs;
+	*rlist = list;
+	return EOK;
+}
+
+/** Free device list.
+ *
+ * @param list Device list
+ */
+void dispcfg_free_dev_list(dispcfg_dev_list_t *list)
+{
+	free(list->devs);
+	free(list);
+}
+
+/** Get display configuration event.
+ *
+ * @param dispcfg Display configuration
+ * @param event Place to store event
+ * @return EOK on success or an error code
+ */
+static errno_t dispcfg_get_event(dispcfg_t *dispcfg, dispcfg_ev_t *event)
+{
+	async_exch_t *exch;
+	ipc_call_t answer;
+	aid_t req;
+	errno_t rc;
+
+	exch = async_exchange_begin(dispcfg->sess);
+	req = async_send_0(exch, DISPCFG_GET_EVENT, &answer);
+	rc = async_data_read_start(exch, event, sizeof(*event));
+	async_exchange_end(exch);
+	if (rc != EOK) {
+		async_forget(req);
+		return rc;
+	}
+
+	async_wait_for(req, &rc);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Display configuration events are pending.
+ *
+ * @param dispcfg Display configuration
+ * @param icall Call data
+ */
+static void dispcfg_ev_pending(dispcfg_t *dispcfg, ipc_call_t *icall)
+{
+	errno_t rc;
+	dispcfg_ev_t event;
+
+	while (true) {
+		fibril_mutex_lock(&dispcfg->lock);
+
+		if (dispcfg->sess != NULL)
+			rc = dispcfg_get_event(dispcfg, &event);
+		else
+			rc = ENOENT;
+
+		fibril_mutex_unlock(&dispcfg->lock);
+
+		if (rc != EOK)
+			break;
+
+		switch (event.etype) {
+		case dcev_seat_added:
+			if (dispcfg->cb != NULL &&
+			    dispcfg->cb->seat_added != NULL) {
+				dispcfg->cb->seat_added(dispcfg->cb_arg,
+				    event.seat_id);
+			}
+			break;
+		case dcev_seat_removed:
+			if (dispcfg->cb != NULL &&
+			    dispcfg->cb->seat_removed != NULL) {
+				dispcfg->cb->seat_removed(dispcfg->cb_arg,
+				    event.seat_id);
+			}
+			break;
+		}
+	}
+
+	async_answer_0(icall, EOK);
+}
+
+/** Callback connection handler.
+ *
+ * @param icall Connect call data
+ * @param arg   Argument, dispcfg_t *
+ */
+static void dispcfg_cb_conn(ipc_call_t *icall, void *arg)
+{
+	dispcfg_t *dispcfg = (dispcfg_t *) arg;
+
+	while (true) {
+		ipc_call_t call;
+		async_get_call(&call);
+
+		if (!ipc_get_imethod(&call)) {
+			/* Hangup */
+			async_answer_0(&call, EOK);
+			goto out;
+		}
+
+		switch (ipc_get_imethod(&call)) {
+		case DISPCFG_EV_PENDING:
+			dispcfg_ev_pending(dispcfg, &call);
+			break;
+		default:
+			async_answer_0(&call, ENOTSUP);
+			break;
+		}
+	}
+
+out:
+	fibril_mutex_lock(&dispcfg->lock);
+	dispcfg->cb_done = true;
+	fibril_mutex_unlock(&dispcfg->lock);
+	fibril_condvar_broadcast(&dispcfg->cv);
+}
+
+/** @}
+ */
Index: uspace/lib/dispcfg/src/dispcfg_srv.c
===================================================================
--- uspace/lib/dispcfg/src/dispcfg_srv.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/dispcfg/src/dispcfg_srv.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,489 @@
+/*
+ * 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 libdispcfg
+ * @{
+ */
+/**
+ * @file
+ * @brief Display configuration protocol server stub
+ */
+
+#include <dispcfg.h>
+#include <dispcfg_srv.h>
+#include <errno.h>
+#include <ipc/dispcfg.h>
+#include <mem.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <str.h>
+#include "../private/dispcfg.h"
+
+static void dispcfg_callback_create_srv(dispcfg_srv_t *srv, ipc_call_t *call)
+{
+	async_sess_t *sess = async_callback_receive(EXCHANGE_SERIALIZE);
+	if (sess == NULL) {
+		async_answer_0(call, ENOMEM);
+		return;
+	}
+
+	srv->client_sess = sess;
+	async_answer_0(call, EOK);
+}
+
+static void dispcfg_get_seat_list_srv(dispcfg_srv_t *srv, ipc_call_t *icall)
+{
+	ipc_call_t call;
+	dispcfg_seat_list_t *list = NULL;
+	size_t size;
+	errno_t rc;
+
+	if (srv->ops->get_seat_list == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->get_seat_list(srv->arg, &list);
+	if (rc != EOK) {
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	/* Send list size */
+
+	if (!async_data_read_receive(&call, &size)) {
+		dispcfg_free_seat_list(list);
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != sizeof(list->nseats)) {
+		dispcfg_free_seat_list(list);
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	rc = async_data_read_finalize(&call, &list->nseats, size);
+	if (rc != EOK) {
+		dispcfg_free_seat_list(list);
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	/* Send seat list */
+
+	if (!async_data_read_receive(&call, &size)) {
+		dispcfg_free_seat_list(list);
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != list->nseats * sizeof(sysarg_t)) {
+		dispcfg_free_seat_list(list);
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	rc = async_data_read_finalize(&call, list->seats, size);
+	if (rc != EOK) {
+		dispcfg_free_seat_list(list);
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	async_answer_0(icall, EOK);
+	dispcfg_free_seat_list(list);
+}
+
+static void dispcfg_get_seat_info_srv(dispcfg_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t seat_id;
+	ipc_call_t call;
+	dispcfg_seat_info_t *info = NULL;
+	size_t namesize;
+	size_t size;
+	errno_t rc;
+
+	seat_id = ipc_get_arg1(icall);
+
+	if (srv->ops->get_seat_info == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->get_seat_info(srv->arg, seat_id, &info);
+	if (rc != EOK) {
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	/* Send name size */
+
+	if (!async_data_read_receive(&call, &size)) {
+		dispcfg_free_seat_info(info);
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != sizeof(size_t)) {
+		dispcfg_free_seat_info(info);
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	namesize = str_size(info->name);
+
+	rc = async_data_read_finalize(&call, &namesize, size);
+	if (rc != EOK) {
+		dispcfg_free_seat_info(info);
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	/* Send name */
+
+	if (!async_data_read_receive(&call, &size)) {
+		dispcfg_free_seat_info(info);
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != namesize) {
+		dispcfg_free_seat_info(info);
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	rc = async_data_read_finalize(&call, info->name, size);
+	if (rc != EOK) {
+		dispcfg_free_seat_info(info);
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	async_answer_0(icall, EOK);
+	dispcfg_free_seat_info(info);
+}
+
+static void dispcfg_seat_create_srv(dispcfg_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t seat_id;
+	ipc_call_t call;
+	char *name;
+	size_t size;
+	errno_t rc;
+
+	if (!async_data_write_receive(&call, &size)) {
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	name = calloc(size + 1, 1);
+	if (name == NULL) {
+		async_answer_0(&call, ENOMEM);
+		async_answer_0(icall, ENOMEM);
+		return;
+	}
+
+	rc = async_data_write_finalize(&call, name, size);
+	if (rc != EOK) {
+		free(name);
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	if (srv->ops->seat_create == NULL) {
+		free(name);
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->seat_create(srv->arg, name, &seat_id);
+	async_answer_1(icall, rc, seat_id);
+	free(name);
+}
+
+static void dispcfg_seat_delete_srv(dispcfg_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t seat_id;
+	errno_t rc;
+
+	seat_id = ipc_get_arg1(icall);
+
+	if (srv->ops->seat_delete == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->seat_delete(srv->arg, seat_id);
+	async_answer_0(icall, rc);
+}
+
+static void dispcfg_dev_assign_srv(dispcfg_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t svc_id;
+	sysarg_t seat_id;
+	errno_t rc;
+
+	svc_id = ipc_get_arg1(icall);
+	seat_id = ipc_get_arg2(icall);
+
+	if (srv->ops->dev_assign == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->dev_assign(srv->arg, svc_id, seat_id);
+	async_answer_0(icall, rc);
+}
+
+static void dispcfg_dev_unassign_srv(dispcfg_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t svc_id;
+	errno_t rc;
+
+	svc_id = ipc_get_arg1(icall);
+
+	if (srv->ops->dev_unassign == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->dev_unassign(srv->arg, svc_id);
+	async_answer_0(icall, rc);
+}
+
+static void dispcfg_get_asgn_dev_list_srv(dispcfg_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t seat_id;
+	ipc_call_t call;
+	dispcfg_dev_list_t *list = NULL;
+	size_t size;
+	errno_t rc;
+
+	seat_id = ipc_get_arg1(icall);
+
+	if (srv->ops->get_asgn_dev_list == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->get_asgn_dev_list(srv->arg, seat_id, &list);
+	if (rc != EOK) {
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	/* Send list size */
+
+	if (!async_data_read_receive(&call, &size)) {
+		dispcfg_free_dev_list(list);
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != sizeof(list->ndevs)) {
+		dispcfg_free_dev_list(list);
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	rc = async_data_read_finalize(&call, &list->ndevs, size);
+	if (rc != EOK) {
+		dispcfg_free_dev_list(list);
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	/* Send device list */
+
+	if (!async_data_read_receive(&call, &size)) {
+		dispcfg_free_dev_list(list);
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != list->ndevs * sizeof(sysarg_t)) {
+		dispcfg_free_dev_list(list);
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	rc = async_data_read_finalize(&call, list->devs, size);
+	if (rc != EOK) {
+		dispcfg_free_dev_list(list);
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	async_answer_0(icall, EOK);
+	dispcfg_free_dev_list(list);
+}
+
+static void dispcfg_get_event_srv(dispcfg_srv_t *srv, ipc_call_t *icall)
+{
+	dispcfg_ev_t event;
+	ipc_call_t call;
+	size_t size;
+	errno_t rc;
+
+	if (srv->ops->get_event == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->get_event(srv->arg, &event);
+	if (rc != EOK) {
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	/* Transfer event data */
+	if (!async_data_read_receive(&call, &size)) {
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != sizeof(event)) {
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	rc = async_data_read_finalize(&call, &event, sizeof(event));
+	if (rc != EOK) {
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	async_answer_0(icall, EOK);
+}
+
+void dispcfg_conn(ipc_call_t *icall, dispcfg_srv_t *srv)
+{
+	/* Accept the connection */
+	async_accept_0(icall);
+
+	while (true) {
+		ipc_call_t call;
+
+		async_get_call(&call);
+		sysarg_t method = ipc_get_imethod(&call);
+
+		if (!method) {
+			/* The other side has hung up */
+			async_answer_0(&call, EOK);
+			break;
+		}
+
+		switch (method) {
+		case DISPCFG_CALLBACK_CREATE:
+			dispcfg_callback_create_srv(srv, &call);
+			break;
+		case DISPCFG_GET_SEAT_LIST:
+			dispcfg_get_seat_list_srv(srv, &call);
+			break;
+		case DISPCFG_GET_SEAT_INFO:
+			dispcfg_get_seat_info_srv(srv, &call);
+			break;
+		case DISPCFG_SEAT_CREATE:
+			dispcfg_seat_create_srv(srv, &call);
+			break;
+		case DISPCFG_SEAT_DELETE:
+			dispcfg_seat_delete_srv(srv, &call);
+			break;
+		case DISPCFG_DEV_ASSIGN:
+			dispcfg_dev_assign_srv(srv, &call);
+			break;
+		case DISPCFG_DEV_UNASSIGN:
+			dispcfg_dev_unassign_srv(srv, &call);
+			break;
+		case DISPCFG_GET_ASGN_DEV_LIST:
+			dispcfg_get_asgn_dev_list_srv(srv, &call);
+			break;
+		case DISPCFG_GET_EVENT:
+			dispcfg_get_event_srv(srv, &call);
+			break;
+		default:
+			async_answer_0(&call, ENOTSUP);
+		}
+	}
+
+	/* Hang up callback session */
+	if (srv->client_sess != NULL) {
+		async_hangup(srv->client_sess);
+		srv->client_sess = NULL;
+	}
+}
+
+/** Initialize display configuration server structure
+ *
+ * @param srv Display configuration server structure to initialize
+ */
+void dispcfg_srv_initialize(dispcfg_srv_t *srv)
+{
+	memset(srv, 0, sizeof(*srv));
+}
+
+/** Send 'pending' event to client.
+ *
+ * @param srv Display configuration server structure
+ */
+void dispcfg_srv_ev_pending(dispcfg_srv_t *srv)
+{
+	async_exch_t *exch;
+
+	exch = async_exchange_begin(srv->client_sess);
+	async_msg_0(exch, DISPCFG_EV_PENDING);
+	async_exchange_end(exch);
+}
+
+/** @}
+ */
Index: uspace/lib/dispcfg/src/testdc.c
===================================================================
--- uspace/lib/dispcfg/src/testdc.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/dispcfg/src/testdc.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,212 @@
+/*
+ * 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 libdispcfg
+ * @{
+ */
+/**
+ * @file
+ * @brief Display configuration test service
+ */
+
+#include <errno.h>
+#include <dispcfg.h>
+#include <dispcfg_srv.h>
+#include <fibril_synch.h>
+#include <str.h>
+#include <testdc.h>
+
+static errno_t test_get_seat_list(void *, dispcfg_seat_list_t **);
+static errno_t test_get_seat_info(void *, sysarg_t, dispcfg_seat_info_t **);
+static errno_t test_seat_create(void *, const char *, sysarg_t *);
+static errno_t test_seat_delete(void *, sysarg_t);
+static errno_t test_dev_assign(void *, sysarg_t, sysarg_t);
+static errno_t test_dev_unassign(void *, sysarg_t);
+static errno_t test_get_asgn_dev_list(void *, sysarg_t, dispcfg_dev_list_t **);
+static errno_t test_get_event(void *, dispcfg_ev_t *);
+
+static void test_seat_added(void *, sysarg_t);
+static void test_seat_removed(void *, sysarg_t);
+
+static dispcfg_ops_t test_dispcfg_srv_ops = {
+	.get_seat_list = test_get_seat_list,
+	.get_seat_info = test_get_seat_info,
+	.seat_create = test_seat_create,
+	.seat_delete = test_seat_delete,
+	.dev_assign = test_dev_assign,
+	.dev_unassign = test_dev_unassign,
+	.get_asgn_dev_list = test_get_asgn_dev_list,
+	.get_event = test_get_event
+};
+
+dispcfg_cb_t test_dispcfg_cb = {
+	.seat_added = test_seat_added,
+	.seat_removed = test_seat_removed
+};
+
+/** Test seat management service connection. */
+void test_dispcfg_conn(ipc_call_t *icall, void *arg)
+{
+	test_response_t *resp = (test_response_t *) arg;
+	dispcfg_srv_t srv;
+
+	/* Set up protocol structure */
+	dispcfg_srv_initialize(&srv);
+	srv.ops = &test_dispcfg_srv_ops;
+	srv.arg = arg;
+	resp->srv = &srv;
+
+	/* Handle connection */
+	dispcfg_conn(icall, &srv);
+
+	resp->srv = NULL;
+}
+
+static void test_seat_added(void *arg, sysarg_t seat_id)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->revent.etype = dcev_seat_added;
+
+	fibril_mutex_lock(&resp->event_lock);
+	resp->seat_added_called = true;
+	resp->seat_added_seat_id = seat_id;
+	fibril_condvar_broadcast(&resp->event_cv);
+	fibril_mutex_unlock(&resp->event_lock);
+}
+
+static void test_seat_removed(void *arg, sysarg_t seat_id)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->revent.etype = dcev_seat_removed;
+
+	fibril_mutex_lock(&resp->event_lock);
+	resp->seat_removed_called = true;
+	resp->seat_removed_seat_id = seat_id;
+	fibril_condvar_broadcast(&resp->event_cv);
+	fibril_mutex_unlock(&resp->event_lock);
+}
+
+static errno_t test_get_seat_list(void *arg, dispcfg_seat_list_t **rlist)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->get_seat_list_called = true;
+
+	if (resp->rc != EOK)
+		return resp->rc;
+
+	*rlist = resp->get_seat_list_rlist;
+	return EOK;
+}
+
+static errno_t test_get_seat_info(void *arg, sysarg_t seat_id,
+    dispcfg_seat_info_t **rinfo)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->get_seat_info_called = true;
+	resp->get_seat_info_seat_id = seat_id;
+
+	if (resp->rc != EOK)
+		return resp->rc;
+
+	*rinfo = resp->get_seat_info_rinfo;
+	return EOK;
+}
+
+static errno_t test_seat_create(void *arg, const char *name, sysarg_t *rseat_id)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->seat_create_called = true;
+	resp->seat_create_name = str_dup(name);
+	*rseat_id = resp->seat_create_seat_id;
+	return resp->rc;
+}
+
+static errno_t test_seat_delete(void *arg, sysarg_t seat_id)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->seat_delete_called = true;
+	resp->seat_delete_seat_id = seat_id;
+	return resp->rc;
+}
+
+static errno_t test_dev_assign(void *arg, sysarg_t svc_id, sysarg_t seat_id)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->dev_assign_called = true;
+	resp->dev_assign_svc_id = svc_id;
+	resp->dev_assign_seat_id = seat_id;
+	return resp->rc;
+}
+
+static errno_t test_dev_unassign(void *arg, sysarg_t svc_id)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->dev_unassign_called = true;
+	resp->dev_unassign_svc_id = svc_id;
+	return resp->rc;
+}
+
+static errno_t test_get_asgn_dev_list(void *arg, sysarg_t seat_id,
+    dispcfg_dev_list_t **rlist)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->get_asgn_dev_list_called = true;
+	resp->get_asgn_dev_list_seat_id = seat_id;
+
+	if (resp->rc != EOK)
+		return resp->rc;
+
+	*rlist = resp->get_asgn_dev_list_rlist;
+	return EOK;
+}
+
+static errno_t test_get_event(void *arg, dispcfg_ev_t *event)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->get_event_called = true;
+	if (resp->event_cnt > 0) {
+		--resp->event_cnt;
+		*event = resp->event;
+		return EOK;
+	}
+
+	return ENOENT;
+}
+
+/** @}
+ */
Index: uspace/lib/dispcfg/test/dispcfg.c
===================================================================
--- uspace/lib/dispcfg/test/dispcfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/dispcfg/test/dispcfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,746 @@
+/*
+ * 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.
+ */
+
+#include <async.h>
+#include <errno.h>
+#include <dispcfg.h>
+#include <dispcfg_srv.h>
+#include <fibril_synch.h>
+#include <loc.h>
+#include <pcut/pcut.h>
+#include <str.h>
+#include <testdc.h>
+#include "../private/dispcfg.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(dispcfg);
+
+static const char *test_dispcfg_server = "test-dispcfg";
+static const char *test_dispcfg_svc = "test/dispcfg";
+
+/** dispcfg_open(), dispcfg_close() work for valid seat management service */
+PCUT_TEST(open_close)
+{
+	errno_t rc;
+	service_id_t sid;
+	dispcfg_t *dispcfg = NULL;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dispcfg_open(test_dispcfg_svc, NULL, NULL, &dispcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dispcfg);
+
+	dispcfg_close(dispcfg);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** dispcfg_get_seat_list() with server returning error response works */
+PCUT_TEST(get_seat_list_failure)
+{
+	errno_t rc;
+	service_id_t sid;
+	dispcfg_t *dispcfg = NULL;
+	dispcfg_seat_list_t *list;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dispcfg_open(test_dispcfg_svc, NULL, NULL, &dispcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dispcfg);
+
+	resp.rc = ENOMEM;
+	resp.get_seat_list_called = false;
+
+	rc = dispcfg_get_seat_list(dispcfg, &list);
+	PCUT_ASSERT_TRUE(resp.get_seat_list_called);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	dispcfg_close(dispcfg);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** dispcfg_get_seat_list() with server returning success response works */
+PCUT_TEST(get_seat_list_success)
+{
+	errno_t rc;
+	service_id_t sid;
+	dispcfg_t *dispcfg = NULL;
+	dispcfg_seat_list_t *list;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dispcfg_open(test_dispcfg_svc, NULL, NULL, &dispcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dispcfg);
+
+	resp.rc = EOK;
+	resp.get_seat_list_called = false;
+	resp.get_seat_list_rlist = calloc(1, sizeof(dispcfg_seat_list_t));
+	PCUT_ASSERT_NOT_NULL(resp.get_seat_list_rlist);
+	resp.get_seat_list_rlist->nseats = 2;
+	resp.get_seat_list_rlist->seats = calloc(2, sizeof(sysarg_t));
+	PCUT_ASSERT_NOT_NULL(resp.get_seat_list_rlist->seats);
+	resp.get_seat_list_rlist->seats[0] = 42;
+	resp.get_seat_list_rlist->seats[1] = 43;
+
+	rc = dispcfg_get_seat_list(dispcfg, &list);
+	PCUT_ASSERT_TRUE(resp.get_seat_list_called);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	PCUT_ASSERT_INT_EQUALS(2, list->nseats);
+	PCUT_ASSERT_INT_EQUALS(42, list->seats[0]);
+	PCUT_ASSERT_INT_EQUALS(43, list->seats[1]);
+
+	dispcfg_free_seat_list(list);
+	dispcfg_close(dispcfg);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** dispcfg_get_seat_infp() with server returning error response works */
+PCUT_TEST(get_seat_info_failure)
+{
+	errno_t rc;
+	service_id_t sid;
+	dispcfg_t *dispcfg = NULL;
+	sysarg_t seat_id;
+	dispcfg_seat_info_t *info;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dispcfg_open(test_dispcfg_svc, NULL, NULL, &dispcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dispcfg);
+
+	resp.rc = ENOMEM;
+	resp.get_seat_info_called = false;
+	seat_id = 1;
+
+	rc = dispcfg_get_seat_info(dispcfg, seat_id, &info);
+	PCUT_ASSERT_TRUE(resp.get_seat_info_called);
+	PCUT_ASSERT_INT_EQUALS(seat_id, resp.get_seat_info_seat_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	dispcfg_close(dispcfg);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** dispcfg_get_seat_info() with server returning success response works */
+PCUT_TEST(get_seat_info_success)
+{
+	errno_t rc;
+	service_id_t sid;
+	dispcfg_t *dispcfg = NULL;
+	sysarg_t seat_id;
+	dispcfg_seat_info_t *info;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dispcfg_open(test_dispcfg_svc, NULL, NULL, &dispcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dispcfg);
+
+	resp.rc = EOK;
+	resp.get_seat_info_called = false;
+	resp.get_seat_info_rinfo = calloc(1, sizeof(dispcfg_seat_info_t));
+	PCUT_ASSERT_NOT_NULL(resp.get_seat_info_rinfo);
+	resp.get_seat_info_rinfo->name = str_dup("Hello");
+	PCUT_ASSERT_NOT_NULL(resp.get_seat_info_rinfo->name);
+	seat_id = 1;
+
+	rc = dispcfg_get_seat_info(dispcfg, seat_id, &info);
+	PCUT_ASSERT_TRUE(resp.get_seat_info_called);
+	PCUT_ASSERT_INT_EQUALS(seat_id, resp.get_seat_info_seat_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	PCUT_ASSERT_STR_EQUALS("Hello", info->name);
+
+	dispcfg_free_seat_info(info);
+	dispcfg_close(dispcfg);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** dispcfg_seat_create() with server returning error response works */
+PCUT_TEST(seat_create_failure)
+{
+	errno_t rc;
+	service_id_t sid;
+	dispcfg_t *dispcfg = NULL;
+	sysarg_t seat_id;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dispcfg_open(test_dispcfg_svc, NULL, NULL, &dispcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dispcfg);
+
+	seat_id = 13;
+	seat_id = 42;
+	resp.rc = ENOMEM;
+	resp.seat_create_called = false;
+
+	rc = dispcfg_seat_create(dispcfg, "Alice", &seat_id);
+	PCUT_ASSERT_TRUE(resp.seat_create_called);
+	PCUT_ASSERT_STR_EQUALS("Alice", resp.seat_create_name);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+	free(resp.seat_create_name);
+
+	dispcfg_close(dispcfg);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** dispcfg_seat_create() with server returning success response works */
+PCUT_TEST(seat_create_success)
+{
+	errno_t rc;
+	service_id_t sid;
+	dispcfg_t *dispcfg = NULL;
+	sysarg_t seat_id;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dispcfg_open(test_dispcfg_svc, NULL, NULL, &dispcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dispcfg);
+
+	resp.rc = EOK;
+	resp.seat_create_called = false;
+	resp.seat_create_seat_id = 42;
+
+	rc = dispcfg_seat_create(dispcfg, "Alice", &seat_id);
+	PCUT_ASSERT_TRUE(resp.seat_create_called);
+	PCUT_ASSERT_STR_EQUALS("Alice", resp.seat_create_name);
+	PCUT_ASSERT_INT_EQUALS(seat_id, resp.seat_create_seat_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+	free(resp.seat_create_name);
+
+	dispcfg_close(dispcfg);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** dispcfg_seat_delete() with server returning error response works */
+PCUT_TEST(seat_delete_failure)
+{
+	errno_t rc;
+	service_id_t sid;
+	dispcfg_t *dispcfg = NULL;
+	sysarg_t seat_id;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dispcfg_open(test_dispcfg_svc, NULL, NULL, &dispcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dispcfg);
+
+	seat_id = 42;
+	resp.rc = ENOMEM;
+	resp.seat_delete_called = false;
+
+	rc = dispcfg_seat_delete(dispcfg, seat_id);
+	PCUT_ASSERT_TRUE(resp.seat_delete_called);
+	PCUT_ASSERT_INT_EQUALS(seat_id, resp.seat_delete_seat_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	dispcfg_close(dispcfg);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** dispcfg_seat_delete() with server returning success response works */
+PCUT_TEST(seat_delete_success)
+{
+	errno_t rc;
+	service_id_t sid;
+	dispcfg_t *dispcfg = NULL;
+	sysarg_t seat_id;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dispcfg_open(test_dispcfg_svc, NULL, NULL, &dispcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dispcfg);
+
+	seat_id = 42;
+	resp.rc = EOK;
+	resp.seat_delete_called = false;
+
+	rc = dispcfg_seat_delete(dispcfg, seat_id);
+	PCUT_ASSERT_TRUE(resp.seat_delete_called);
+	PCUT_ASSERT_INT_EQUALS(seat_id, resp.seat_delete_seat_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	dispcfg_close(dispcfg);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** dispcfg_dev_assign() with server returning error response works */
+PCUT_TEST(dev_assign_failure)
+{
+	errno_t rc;
+	service_id_t sid;
+	dispcfg_t *dispcfg = NULL;
+	sysarg_t svc_id;
+	sysarg_t seat_id;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dispcfg_open(test_dispcfg_svc, NULL, NULL, &dispcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dispcfg);
+
+	svc_id = 12;
+	seat_id = 13;
+	resp.rc = ENOMEM;
+	resp.dev_assign_called = false;
+
+	rc = dispcfg_dev_assign(dispcfg, svc_id, seat_id);
+	PCUT_ASSERT_TRUE(resp.dev_assign_called);
+	PCUT_ASSERT_INT_EQUALS(svc_id, resp.dev_assign_svc_id);
+	PCUT_ASSERT_INT_EQUALS(seat_id, resp.dev_assign_seat_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	dispcfg_close(dispcfg);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** dispcfg_dev_assign() with server returning success response works */
+PCUT_TEST(dev_assign_success)
+{
+	errno_t rc;
+	service_id_t sid;
+	dispcfg_t *dispcfg = NULL;
+	sysarg_t svc_id;
+	sysarg_t seat_id;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dispcfg_open(test_dispcfg_svc, NULL, NULL, &dispcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dispcfg);
+
+	svc_id = 12;
+	seat_id = 13;
+	resp.rc = EOK;
+	resp.dev_assign_called = false;
+
+	rc = dispcfg_dev_assign(dispcfg, svc_id, seat_id);
+	PCUT_ASSERT_TRUE(resp.dev_assign_called);
+	PCUT_ASSERT_INT_EQUALS(svc_id, resp.dev_assign_svc_id);
+	PCUT_ASSERT_INT_EQUALS(seat_id, resp.dev_assign_seat_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	dispcfg_close(dispcfg);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** dispcfg_dev_unassign() with server returning error response works */
+PCUT_TEST(dev_unassign_failure)
+{
+	errno_t rc;
+	service_id_t sid;
+	dispcfg_t *dispcfg = NULL;
+	sysarg_t svc_id;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dispcfg_open(test_dispcfg_svc, NULL, NULL, &dispcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dispcfg);
+
+	svc_id = 12;
+	resp.rc = ENOMEM;
+	resp.dev_unassign_called = false;
+
+	rc = dispcfg_dev_unassign(dispcfg, svc_id);
+	PCUT_ASSERT_TRUE(resp.dev_unassign_called);
+	PCUT_ASSERT_INT_EQUALS(svc_id, resp.dev_unassign_svc_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	dispcfg_close(dispcfg);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** dispcfg_dev_unassign() with server returning success response works */
+PCUT_TEST(dev_unassign_success)
+{
+	errno_t rc;
+	service_id_t sid;
+	dispcfg_t *dispcfg = NULL;
+	sysarg_t svc_id;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dispcfg_open(test_dispcfg_svc, NULL, NULL, &dispcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dispcfg);
+
+	svc_id = 12;
+	resp.rc = EOK;
+	resp.dev_unassign_called = false;
+
+	rc = dispcfg_dev_unassign(dispcfg, svc_id);
+	PCUT_ASSERT_TRUE(resp.dev_unassign_called);
+	PCUT_ASSERT_INT_EQUALS(svc_id, resp.dev_unassign_svc_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	dispcfg_close(dispcfg);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** dispcfg_get_asgn_dev_list() with server returning error response works */
+PCUT_TEST(get_asgn_dev_list_failure)
+{
+	errno_t rc;
+	service_id_t sid;
+	dispcfg_t *dispcfg = NULL;
+	dispcfg_dev_list_t *list;
+	sysarg_t seat_id;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dispcfg_open(test_dispcfg_svc, NULL, NULL, &dispcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dispcfg);
+
+	resp.rc = ENOMEM;
+	resp.get_asgn_dev_list_called = false;
+	seat_id = 42;
+
+	rc = dispcfg_get_asgn_dev_list(dispcfg, seat_id, &list);
+	PCUT_ASSERT_TRUE(resp.get_asgn_dev_list_called);
+	PCUT_ASSERT_INT_EQUALS(seat_id, resp.get_asgn_dev_list_seat_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	dispcfg_close(dispcfg);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** dispcfg_get_asgn_dev_list() with server returning success response works */
+PCUT_TEST(get_asgn_dev_list_success)
+{
+	errno_t rc;
+	service_id_t sid;
+	dispcfg_t *dispcfg = NULL;
+	dispcfg_dev_list_t *list;
+	sysarg_t seat_id;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dispcfg_open(test_dispcfg_svc, NULL, NULL, &dispcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dispcfg);
+
+	resp.rc = EOK;
+	resp.get_asgn_dev_list_called = false;
+	resp.get_asgn_dev_list_rlist = calloc(1, sizeof(dispcfg_dev_list_t));
+	PCUT_ASSERT_NOT_NULL(resp.get_asgn_dev_list_rlist);
+	resp.get_asgn_dev_list_rlist->ndevs = 2;
+	resp.get_asgn_dev_list_rlist->devs = calloc(2, sizeof(sysarg_t));
+	PCUT_ASSERT_NOT_NULL(resp.get_asgn_dev_list_rlist->devs);
+	resp.get_asgn_dev_list_rlist->devs[0] = 11;
+	resp.get_asgn_dev_list_rlist->devs[1] = 12;
+	seat_id = 42;
+
+	rc = dispcfg_get_asgn_dev_list(dispcfg, seat_id, &list);
+	PCUT_ASSERT_TRUE(resp.get_asgn_dev_list_called);
+	PCUT_ASSERT_INT_EQUALS(seat_id, resp.get_asgn_dev_list_seat_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	PCUT_ASSERT_INT_EQUALS(2, list->ndevs);
+	PCUT_ASSERT_INT_EQUALS(11, list->devs[0]);
+	PCUT_ASSERT_INT_EQUALS(12, list->devs[1]);
+
+	dispcfg_free_dev_list(list);
+	dispcfg_close(dispcfg);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** Window added event can be delivered from server to client callback function */
+PCUT_TEST(seat_added_deliver)
+{
+	errno_t rc;
+	service_id_t sid;
+	dispcfg_t *dispcfg = NULL;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dispcfg_open(test_dispcfg_svc, &test_dispcfg_cb, &resp, &dispcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dispcfg);
+	PCUT_ASSERT_NOT_NULL(resp.srv);
+
+	resp.event_cnt = 1;
+	resp.event.etype = dcev_seat_added;
+	resp.event.seat_id = 42;
+	resp.seat_added_called = false;
+	fibril_mutex_initialize(&resp.event_lock);
+	fibril_condvar_initialize(&resp.event_cv);
+	dispcfg_srv_ev_pending(resp.srv);
+
+	/* Wait for the event handler to be called. */
+	fibril_mutex_lock(&resp.event_lock);
+	while (!resp.seat_added_called) {
+		fibril_condvar_wait(&resp.event_cv, &resp.event_lock);
+	}
+	fibril_mutex_unlock(&resp.event_lock);
+
+	/* Verify that the event was delivered correctly */
+	PCUT_ASSERT_INT_EQUALS(resp.event.etype,
+	    resp.revent.etype);
+
+	dispcfg_close(dispcfg);
+
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** Window removed event can be delivered from server to client callback function */
+PCUT_TEST(seat_removed_deliver)
+{
+	errno_t rc;
+	service_id_t sid;
+	dispcfg_t *dispcfg = NULL;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_dispcfg_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_dispcfg_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_dispcfg_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = dispcfg_open(test_dispcfg_svc, &test_dispcfg_cb, &resp, &dispcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dispcfg);
+	PCUT_ASSERT_NOT_NULL(resp.srv);
+
+	resp.event_cnt = 1;
+	resp.event.etype = dcev_seat_removed;
+	resp.event.seat_id = 42;
+	resp.seat_removed_called = false;
+	fibril_mutex_initialize(&resp.event_lock);
+	fibril_condvar_initialize(&resp.event_cv);
+	dispcfg_srv_ev_pending(resp.srv);
+
+	/* Wait for the event handler to be called. */
+	fibril_mutex_lock(&resp.event_lock);
+	while (!resp.seat_removed_called) {
+		fibril_condvar_wait(&resp.event_cv, &resp.event_lock);
+	}
+	fibril_mutex_unlock(&resp.event_lock);
+
+	/* Verify that the event was delivered correctly */
+	PCUT_ASSERT_INT_EQUALS(resp.event.etype,
+	    resp.revent.etype);
+
+	dispcfg_close(dispcfg);
+
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+PCUT_EXPORT(dispcfg);
Index: uspace/lib/dispcfg/test/main.c
===================================================================
--- uspace/lib/dispcfg/test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/dispcfg/test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+#include <pcut/pcut.h>
+
+PCUT_INIT;
+
+PCUT_IMPORT(dispcfg);
+
+PCUT_MAIN();
Index: uspace/lib/display/include/disp_srv.h
===================================================================
--- uspace/lib/display/include/disp_srv.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/display/include/disp_srv.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -57,11 +57,17 @@
 	errno_t (*window_create)(void *, display_wnd_params_t *, sysarg_t *);
 	errno_t (*window_destroy)(void *, sysarg_t);
-	errno_t (*window_move_req)(void *, sysarg_t, gfx_coord2_t *);
+	errno_t (*window_move_req)(void *, sysarg_t, gfx_coord2_t *,
+	    sysarg_t);
 	errno_t (*window_resize_req)(void *, sysarg_t, display_wnd_rsztype_t,
-	    gfx_coord2_t *);
+	    gfx_coord2_t *, sysarg_t);
 	errno_t (*window_move)(void *, sysarg_t, gfx_coord2_t *);
 	errno_t (*window_get_pos)(void *, sysarg_t, gfx_coord2_t *);
+	errno_t (*window_get_max_rect)(void *, sysarg_t, gfx_rect_t *);
 	errno_t (*window_resize)(void *, sysarg_t, gfx_coord2_t *, gfx_rect_t *);
+	errno_t (*window_minimize)(void *, sysarg_t);
+	errno_t (*window_maximize)(void *, sysarg_t);
+	errno_t (*window_unmaximize)(void *, sysarg_t);
 	errno_t (*window_set_cursor)(void *, sysarg_t, display_stock_cursor_t);
+	errno_t (*window_set_caption)(void *, sysarg_t, const char *);
 	errno_t (*get_event)(void *, sysarg_t *, display_wnd_ev_t *);
 	errno_t (*get_info)(void *, display_info_t *);
Index: uspace/lib/display/include/display.h
===================================================================
--- uspace/lib/display/include/display.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/display/include/display.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -54,13 +54,19 @@
 extern errno_t display_window_destroy(display_window_t *);
 extern errno_t display_window_get_gc(display_window_t *, gfx_context_t **);
-extern errno_t display_window_move_req(display_window_t *, gfx_coord2_t *);
+extern errno_t display_window_move_req(display_window_t *, gfx_coord2_t *,
+    sysarg_t);
 extern errno_t display_window_resize_req(display_window_t *,
-    display_wnd_rsztype_t, gfx_coord2_t *);
+    display_wnd_rsztype_t, gfx_coord2_t *, sysarg_t);
 extern errno_t display_window_move(display_window_t *, gfx_coord2_t *);
 extern errno_t display_window_get_pos(display_window_t *, gfx_coord2_t *);
+extern errno_t display_window_get_max_rect(display_window_t *, gfx_rect_t *);
 extern errno_t display_window_resize(display_window_t *,
     gfx_coord2_t *, gfx_rect_t *);
+extern errno_t display_window_minimize(display_window_t *);
+extern errno_t display_window_maximize(display_window_t *);
+extern errno_t display_window_unmaximize(display_window_t *);
 extern errno_t display_window_set_cursor(display_window_t *,
     display_stock_cursor_t);
+extern errno_t display_window_set_caption(display_window_t *, const char *);
 
 #endif
Index: uspace/lib/display/include/ipc/display.h
===================================================================
--- uspace/lib/display/include/ipc/display.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/display/include/ipc/display.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -42,10 +42,15 @@
 	DISPLAY_WINDOW_CREATE,
 	DISPLAY_WINDOW_DESTROY,
+	DISPLAY_WINDOW_MINIMIZE,
+	DISPLAY_WINDOW_MAXIMIZE,
 	DISPLAY_WINDOW_MOVE,
 	DISPLAY_WINDOW_MOVE_REQ,
 	DISPLAY_WINDOW_GET_POS,
+	DISPLAY_WINDOW_GET_MAX_RECT,
 	DISPLAY_WINDOW_RESIZE,
 	DISPLAY_WINDOW_RESIZE_REQ,
 	DISPLAY_WINDOW_SET_CURSOR,
+	DISPLAY_WINDOW_SET_CAPTION,
+	DISPLAY_WINDOW_UNMAXIMIZE,
 	DISPLAY_GET_EVENT,
 	DISPLAY_GET_INFO
Index: uspace/lib/display/include/types/display.h
===================================================================
--- uspace/lib/display/include/types/display.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/display/include/types/display.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -60,5 +60,5 @@
 	void (*close_event)(void *);
 	/** Focus event */
-	void (*focus_event)(void *);
+	void (*focus_event)(void *, unsigned);
 	/** Keyboard event */
 	void (*kbd_event)(void *, kbd_event_t *);
@@ -68,5 +68,5 @@
 	void (*resize_event)(void *, gfx_rect_t *);
 	/** Unfocus event */
-	void (*unfocus_event)(void *);
+	void (*unfocus_event)(void *, unsigned);
 } display_wnd_cb_t;
 
Index: uspace/lib/display/include/types/display/event.h
===================================================================
--- uspace/lib/display/include/types/display/event.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/display/include/types/display/event.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -56,8 +56,20 @@
 } display_wnd_ev_type_t;
 
+/** Display window focus event */
+typedef struct {
+	/** New number of foci */
+	unsigned nfocus;
+} display_wnd_focus_ev_t;
+
 /** Display window resize event */
 typedef struct {
 	gfx_rect_t rect;
 } display_wnd_resize_ev_t;
+
+/** Display window unfocus event */
+typedef struct {
+	/** Number of remaining foci */
+	unsigned nfocus;
+} display_wnd_unfocus_ev_t;
 
 /** Display window event */
@@ -66,4 +78,6 @@
 	display_wnd_ev_type_t etype;
 	union {
+		/** Focus event data */
+		display_wnd_focus_ev_t focus;
 		/** Keyboard event data */
 		kbd_event_t kbd;
@@ -72,4 +86,6 @@
 		/** Resize event data */
 		display_wnd_resize_ev_t resize;
+		/** Unfocus event data */
+		display_wnd_unfocus_ev_t unfocus;
 	} ev;
 } display_wnd_ev_t;
Index: uspace/lib/display/include/types/display/wndparams.h
===================================================================
--- uspace/lib/display/include/types/display/wndparams.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/display/include/types/display/wndparams.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -42,6 +42,16 @@
 	/** Popup window (capture events, no focus) */
 	wndf_popup = 0x1,
+	/** Topmost window */
+	wndf_topmost = 0x2,
 	/** Set specific initial window position */
-	wndf_setpos = 0x2
+	wndf_setpos = 0x4,
+	/** Window is minimized */
+	wndf_minimized = 0x8,
+	/** Window is maximized */
+	wndf_maximized = 0x10,
+	/** Special system window */
+	wndf_system = 0x20,
+	/** Maximized windows should avoid this window */
+	wndf_avoid = 0x40
 } display_wnd_flags_t;
 
@@ -56,4 +66,6 @@
 	/** Bounding rectangle */
 	gfx_rect_t rect;
+	/** Window caption */
+	const char *caption;
 	/** Minimum size (when being resized) */
 	gfx_coord2_t min_size;
@@ -62,4 +74,6 @@
 	/** Flags */
 	display_wnd_flags_t flags;
+	/** Input device ID associated with window's seat (or zero) */
+	sysarg_t idev_id;
 } display_wnd_params_t;
 
Index: uspace/lib/display/meson.build
===================================================================
--- uspace/lib/display/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/display/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -27,5 +27,5 @@
 #
 
-deps = [ 'gfx', 'ipcgfx' ]
+deps = [ 'gfx', 'ipcgfx', 'input' ]
 src = files(
 	'src/display.c',
Index: uspace/lib/display/private/params.h
===================================================================
--- uspace/lib/display/private/params.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/display/private/params.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -37,4 +37,5 @@
 
 #include <gfx/coord.h>
+#include <types/common.h>
 
 /** Window resize arguments. */
@@ -46,4 +47,23 @@
 } display_wnd_resize_t;
 
+/** Display window parameters encoded for transport
+ *
+ * This omits variable-length fields (window caption).
+ */
+typedef struct {
+	/** Bounding rectangle */
+	gfx_rect_t rect;
+	/** Window caption size in bytes */
+	size_t caption_size;
+	/** Minimum size (when being resized) */
+	gfx_coord2_t min_size;
+	/** Initial position (if flag wndf_setpos is set) */
+	gfx_coord2_t pos;
+	/** Flags */
+	display_wnd_flags_t flags;
+	/** Input device ID associated with window's seat (or zero) */
+	sysarg_t idev_id;
+} display_wnd_params_enc_t;
+
 #endif
 
Index: uspace/lib/display/src/disp_srv.c
===================================================================
--- uspace/lib/display/src/disp_srv.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/display/src/disp_srv.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -63,5 +63,7 @@
 	sysarg_t wnd_id;
 	ipc_call_t call;
+	display_wnd_params_enc_t eparams;
 	display_wnd_params_t params;
+	char *caption;
 	size_t size;
 	errno_t rc;
@@ -73,12 +75,40 @@
 	}
 
-	if (size != sizeof(display_wnd_params_t)) {
-		async_answer_0(&call, EINVAL);
-		async_answer_0(icall, EINVAL);
-		return;
-	}
-
-	rc = async_data_write_finalize(&call, &params, size);
-	if (rc != EOK) {
+	if (size != sizeof(display_wnd_params_enc_t)) {
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	rc = async_data_write_finalize(&call, &eparams, size);
+	if (rc != EOK) {
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	caption = calloc(eparams.caption_size + 1, 1);
+	if (caption == NULL) {
+		async_answer_0(icall, ENOMEM);
+		return;
+	}
+
+	if (!async_data_write_receive(&call, &size)) {
+		free(caption);
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != eparams.caption_size) {
+		free(caption);
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	rc = async_data_write_finalize(&call, caption, eparams.caption_size);
+	if (rc != EOK) {
+		free(caption);
 		async_answer_0(&call, rc);
 		async_answer_0(icall, rc);
@@ -87,7 +117,16 @@
 
 	if (srv->ops->window_create == NULL) {
-		async_answer_0(icall, ENOTSUP);
-		return;
-	}
+		free(caption);
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	/* Decode the parameters from transport */
+	params.rect = eparams.rect;
+	params.caption = caption;
+	params.min_size = eparams.min_size;
+	params.pos = eparams.pos;
+	params.flags = eparams.flags;
+	params.idev_id = eparams.idev_id;
 
 	rc = srv->ops->window_create(srv->arg, &params, &wnd_id);
@@ -114,4 +153,5 @@
 {
 	sysarg_t wnd_id;
+	sysarg_t pos_id;
 	ipc_call_t call;
 	gfx_coord2_t pos;
@@ -120,4 +160,5 @@
 
 	wnd_id = ipc_get_arg1(icall);
+	pos_id = ipc_get_arg2(icall);
 
 	if (!async_data_write_receive(&call, &size)) {
@@ -145,5 +186,5 @@
 	}
 
-	rc = srv->ops->window_move_req(srv->arg, wnd_id, &pos);
+	rc = srv->ops->window_move_req(srv->arg, wnd_id, &pos, pos_id);
 	async_answer_0(icall, rc);
 }
@@ -203,4 +244,5 @@
 
 	if (!async_data_read_receive(&call, &size)) {
+		async_answer_0(&call, EREFUSED);
 		async_answer_0(icall, EREFUSED);
 		return;
@@ -221,4 +263,49 @@
 
 	rc = async_data_read_finalize(&call, &dpos, size);
+	if (rc != EOK) {
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	async_answer_0(icall, EOK);
+}
+
+static void display_window_get_max_rect_srv(display_srv_t *srv,
+    ipc_call_t *icall)
+{
+	sysarg_t wnd_id;
+	ipc_call_t call;
+	gfx_rect_t rect;
+	size_t size;
+	errno_t rc;
+
+	wnd_id = ipc_get_arg1(icall);
+
+	if (srv->ops->window_get_max_rect == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	if (!async_data_read_receive(&call, &size)) {
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	rc = srv->ops->window_get_max_rect(srv->arg, wnd_id, &rect);
+	if (rc != EOK) {
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	if (size != sizeof(gfx_rect_t)) {
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	rc = async_data_read_finalize(&call, &rect, size);
 	if (rc != EOK) {
 		async_answer_0(&call, rc);
@@ -236,4 +323,5 @@
 	display_wnd_rsztype_t rsztype;
 	gfx_coord2_t pos;
+	sysarg_t pos_id;
 	size_t size;
 	errno_t rc;
@@ -241,4 +329,5 @@
 	wnd_id = ipc_get_arg1(icall);
 	rsztype = (display_wnd_rsztype_t) ipc_get_arg2(icall);
+	pos_id = ipc_get_arg3(icall);
 
 	if (!async_data_write_receive(&call, &size)) {
@@ -266,5 +355,6 @@
 	}
 
-	rc = srv->ops->window_resize_req(srv->arg, wnd_id, rsztype, &pos);
+	rc = srv->ops->window_resize_req(srv->arg, wnd_id, rsztype, &pos,
+	    pos_id);
 	async_answer_0(icall, rc);
 }
@@ -309,4 +399,52 @@
 }
 
+static void display_window_minimize_srv(display_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t wnd_id;
+	errno_t rc;
+
+	wnd_id = ipc_get_arg1(icall);
+
+	if (srv->ops->window_minimize == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->window_minimize(srv->arg, wnd_id);
+	async_answer_0(icall, rc);
+}
+
+static void display_window_maximize_srv(display_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t wnd_id;
+	errno_t rc;
+
+	wnd_id = ipc_get_arg1(icall);
+
+	if (srv->ops->window_maximize == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->window_maximize(srv->arg, wnd_id);
+	async_answer_0(icall, rc);
+}
+
+static void display_window_unmaximize_srv(display_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t wnd_id;
+	errno_t rc;
+
+	wnd_id = ipc_get_arg1(icall);
+
+	if (srv->ops->window_unmaximize == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->window_unmaximize(srv->arg, wnd_id);
+	async_answer_0(icall, rc);
+}
+
 static void display_window_set_cursor_srv(display_srv_t *srv, ipc_call_t *icall)
 {
@@ -325,4 +463,47 @@
 	rc = srv->ops->window_set_cursor(srv->arg, wnd_id, cursor);
 	async_answer_0(icall, rc);
+}
+
+static void display_window_set_caption_srv(display_srv_t *srv,
+    ipc_call_t *icall)
+{
+	sysarg_t wnd_id;
+	ipc_call_t call;
+	char *caption;
+	size_t size;
+	errno_t rc;
+
+	wnd_id = ipc_get_arg1(icall);
+
+	if (!async_data_write_receive(&call, &size)) {
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	caption = calloc(size + 1, 1);
+	if (caption == NULL) {
+		async_answer_0(&call, ENOMEM);
+		async_answer_0(icall, ENOMEM);
+		return;
+	}
+
+	rc = async_data_write_finalize(&call, caption, size);
+	if (rc != EOK) {
+		free(caption);
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	if (srv->ops->window_set_caption == NULL) {
+		free(caption);
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->window_set_caption(srv->arg, wnd_id, caption);
+	async_answer_0(icall, rc);
+	free(caption);
 }
 
@@ -348,4 +529,5 @@
 	/* Transfer event data */
 	if (!async_data_read_receive(&call, &size)) {
+		async_answer_0(&call, EREFUSED);
 		async_answer_0(icall, EREFUSED);
 		return;
@@ -382,4 +564,5 @@
 	/* Transfer information */
 	if (!async_data_read_receive(&call, &size)) {
+		async_answer_0(&call, EREFUSED);
 		async_answer_0(icall, EREFUSED);
 		return;
@@ -445,4 +628,7 @@
 			display_window_get_pos_srv(srv, &call);
 			break;
+		case DISPLAY_WINDOW_GET_MAX_RECT:
+			display_window_get_max_rect_srv(srv, &call);
+			break;
 		case DISPLAY_WINDOW_RESIZE_REQ:
 			display_window_resize_req_srv(srv, &call);
@@ -451,6 +637,18 @@
 			display_window_resize_srv(srv, &call);
 			break;
+		case DISPLAY_WINDOW_MINIMIZE:
+			display_window_minimize_srv(srv, &call);
+			break;
+		case DISPLAY_WINDOW_MAXIMIZE:
+			display_window_maximize_srv(srv, &call);
+			break;
+		case DISPLAY_WINDOW_UNMAXIMIZE:
+			display_window_unmaximize_srv(srv, &call);
+			break;
 		case DISPLAY_WINDOW_SET_CURSOR:
 			display_window_set_cursor_srv(srv, &call);
+			break;
+		case DISPLAY_WINDOW_SET_CAPTION:
+			display_window_set_caption_srv(srv, &call);
 			break;
 		case DISPLAY_GET_EVENT:
Index: uspace/lib/display/src/display.c
===================================================================
--- uspace/lib/display/src/display.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/display/src/display.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -38,4 +38,5 @@
 #include <mem.h>
 #include <stdlib.h>
+#include <str.h>
 #include "../private/display.h"
 #include "../private/params.h"
@@ -68,5 +69,5 @@
 		dsname = SERVICE_NAME_DISPLAY;
 
-	rc = loc_service_get_id(dsname, &display_svc, IPC_FLAG_BLOCKING);
+	rc = loc_service_get_id(dsname, &display_svc, 0);
 	if (rc != EOK) {
 		free(display);
@@ -75,5 +76,5 @@
 
 	display->sess = loc_service_connect(display_svc, INTERFACE_DISPLAY,
-	    IPC_FLAG_BLOCKING);
+	    0);
 	if (display->sess == NULL) {
 		free(display);
@@ -147,4 +148,5 @@
 {
 	memset(params, 0, sizeof(*params));
+	params->caption = "";
 }
 
@@ -162,8 +164,17 @@
 {
 	display_window_t *window;
-	async_exch_t *exch;
-	aid_t req;
-	ipc_call_t answer;
-	errno_t rc;
+	display_wnd_params_enc_t eparams;
+	async_exch_t *exch;
+	aid_t req;
+	ipc_call_t answer;
+	errno_t rc;
+
+	/* Encode the parameters for transport */
+	eparams.rect = params->rect;
+	eparams.caption_size = str_size(params->caption);
+	eparams.min_size = params->min_size;
+	eparams.pos = params->pos;
+	eparams.flags = params->flags;
+	eparams.idev_id = params->idev_id;
 
 	window = calloc(1, sizeof(display_window_t));
@@ -173,5 +184,18 @@
 	exch = async_exchange_begin(display->sess);
 	req = async_send_0(exch, DISPLAY_WINDOW_CREATE, &answer);
-	rc = async_data_write_start(exch, params, sizeof (display_wnd_params_t));
+
+	/* Write fixed fields */
+	rc = async_data_write_start(exch, &eparams,
+	    sizeof (display_wnd_params_enc_t));
+	if (rc != EOK) {
+		async_exchange_end(exch);
+		async_forget(req);
+		free(window);
+		return rc;
+	}
+
+	/* Write caption */
+	rc = async_data_write_start(exch, params->caption,
+	    eparams.caption_size);
 	async_exchange_end(exch);
 	if (rc != EOK) {
@@ -261,15 +285,18 @@
  * @param window Window
  * @param pos Position in the window where the button was pressed
- * @return EOK on success or an error code
- */
-errno_t display_window_move_req(display_window_t *window, gfx_coord2_t *pos)
-{
-	async_exch_t *exch;
-	aid_t req;
-	ipc_call_t answer;
-	errno_t rc;
-
-	exch = async_exchange_begin(window->display->sess);
-	req = async_send_1(exch, DISPLAY_WINDOW_MOVE_REQ, window->id, &answer);
+ * @param pos_id Positioning device ID
+ * @return EOK on success or an error code
+ */
+errno_t display_window_move_req(display_window_t *window, gfx_coord2_t *pos,
+    sysarg_t pos_id)
+{
+	async_exch_t *exch;
+	aid_t req;
+	ipc_call_t answer;
+	errno_t rc;
+
+	exch = async_exchange_begin(window->display->sess);
+	req = async_send_2(exch, DISPLAY_WINDOW_MOVE_REQ, window->id,
+	    pos_id, &answer);
 	rc = async_data_write_start(exch, (void *)pos, sizeof (gfx_coord2_t));
 	async_exchange_end(exch);
@@ -350,4 +377,36 @@
 }
 
+/** Get display window maximized rectangle.
+ *
+ * Get the rectangle to which a window would be maximized.
+ *
+ * @param window Window
+ * @param rect Place to store maximized rectangle
+ * @return EOK on success or an error code
+ */
+errno_t display_window_get_max_rect(display_window_t *window, gfx_rect_t *rect)
+{
+	async_exch_t *exch;
+	aid_t req;
+	ipc_call_t answer;
+	errno_t rc;
+
+	exch = async_exchange_begin(window->display->sess);
+	req = async_send_1(exch, DISPLAY_WINDOW_GET_MAX_RECT, window->id,
+	    &answer);
+	rc = async_data_read_start(exch, rect, sizeof (gfx_rect_t));
+	async_exchange_end(exch);
+	if (rc != EOK) {
+		async_forget(req);
+		return rc;
+	}
+
+	async_wait_for(req, &rc);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
 /** Request a window resize.
  *
@@ -359,17 +418,18 @@
  * @param rsztype Resize type (which part of window frame is being dragged)
  * @param pos Position in the window where the button was pressed
+ * @param pos_id Positioning device ID
  * @return EOK on success or an error code
  */
 errno_t display_window_resize_req(display_window_t *window,
-    display_wnd_rsztype_t rsztype, gfx_coord2_t *pos)
-{
-	async_exch_t *exch;
-	aid_t req;
-	ipc_call_t answer;
-	errno_t rc;
-
-	exch = async_exchange_begin(window->display->sess);
-	req = async_send_2(exch, DISPLAY_WINDOW_RESIZE_REQ, window->id,
-	    (sysarg_t) rsztype, &answer);
+    display_wnd_rsztype_t rsztype, gfx_coord2_t *pos, sysarg_t pos_id)
+{
+	async_exch_t *exch;
+	aid_t req;
+	ipc_call_t answer;
+	errno_t rc;
+
+	exch = async_exchange_begin(window->display->sess);
+	req = async_send_3(exch, DISPLAY_WINDOW_RESIZE_REQ, window->id,
+	    (sysarg_t) rsztype, pos_id, &answer);
 	rc = async_data_write_start(exch, (void *)pos, sizeof (gfx_coord2_t));
 	async_exchange_end(exch);
@@ -455,4 +515,55 @@
 }
 
+/** Minimize window.
+ *
+ * @param window Window
+ * @return EOK on success or an error code
+ */
+errno_t display_window_minimize(display_window_t *window)
+{
+	async_exch_t *exch;
+	errno_t rc;
+
+	exch = async_exchange_begin(window->display->sess);
+	rc = async_req_1_0(exch, DISPLAY_WINDOW_MINIMIZE, window->id);
+	async_exchange_end(exch);
+
+	return rc;
+}
+
+/** Maximize window.
+ *
+ * @param window Window
+ * @return EOK on success or an error code
+ */
+errno_t display_window_maximize(display_window_t *window)
+{
+	async_exch_t *exch;
+	errno_t rc;
+
+	exch = async_exchange_begin(window->display->sess);
+	rc = async_req_1_0(exch, DISPLAY_WINDOW_MAXIMIZE, window->id);
+	async_exchange_end(exch);
+
+	return rc;
+}
+
+/** Unmaximize window.
+ *
+ * @param window Window
+ * @return EOK on success or an error code
+ */
+errno_t display_window_unmaximize(display_window_t *window)
+{
+	async_exch_t *exch;
+	errno_t rc;
+
+	exch = async_exchange_begin(window->display->sess);
+	rc = async_req_1_0(exch, DISPLAY_WINDOW_UNMAXIMIZE, window->id);
+	async_exchange_end(exch);
+
+	return rc;
+}
+
 /** Set window cursor.
  *
@@ -474,4 +585,37 @@
 	    cursor);
 	async_exchange_end(exch);
+	return rc;
+}
+
+/** Set display window caption.
+ *
+ * @param window Window
+ * @param caption New caption
+ * @return EOK on success or an error code
+ */
+errno_t display_window_set_caption(display_window_t *window,
+    const char *caption)
+{
+	async_exch_t *exch;
+	aid_t req;
+	ipc_call_t answer;
+	size_t cap_size;
+	errno_t rc;
+
+	cap_size = str_size(caption);
+
+	exch = async_exchange_begin(window->display->sess);
+	req = async_send_1(exch, DISPLAY_WINDOW_SET_CAPTION, window->id,
+	    &answer);
+
+	/* Write caption */
+	rc = async_data_write_start(exch, caption, cap_size);
+	async_exchange_end(exch);
+	if (rc != EOK) {
+		async_forget(req);
+		return rc;
+	}
+
+	async_wait_for(req, &rc);
 	return rc;
 }
@@ -577,5 +721,6 @@
 		case wev_focus:
 			if (window->cb != NULL && window->cb->focus_event != NULL) {
-				window->cb->focus_event(window->cb_arg);
+				window->cb->focus_event(window->cb_arg,
+				    event.ev.focus.nfocus);
 			}
 			break;
@@ -600,5 +745,6 @@
 		case wev_unfocus:
 			if (window->cb != NULL && window->cb->unfocus_event != NULL) {
-				window->cb->unfocus_event(window->cb_arg);
+				window->cb->unfocus_event(window->cb_arg,
+				    event.ev.unfocus.nfocus);
 			}
 			break;
Index: uspace/lib/display/test/display.c
===================================================================
--- uspace/lib/display/test/display.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/display/test/display.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -38,4 +38,5 @@
 #include <loc.h>
 #include <pcut/pcut.h>
+#include <str.h>
 #include "../private/display.h"
 
@@ -50,19 +51,24 @@
 
 static void test_close_event(void *);
-static void test_focus_event(void *);
+static void test_focus_event(void *, unsigned);
 static void test_kbd_event(void *, kbd_event_t *);
 static void test_pos_event(void *, pos_event_t *);
-static void test_unfocus_event(void *);
+static void test_unfocus_event(void *, unsigned);
 
 static errno_t test_window_create(void *, display_wnd_params_t *, sysarg_t *);
 static errno_t test_window_destroy(void *, sysarg_t);
-static errno_t test_window_move_req(void *, sysarg_t, gfx_coord2_t *);
+static errno_t test_window_move_req(void *, sysarg_t, gfx_coord2_t *, sysarg_t);
 static errno_t test_window_move(void *, sysarg_t, gfx_coord2_t *);
 static errno_t test_window_get_pos(void *, sysarg_t, gfx_coord2_t *);
+static errno_t test_window_get_max_rect(void *, sysarg_t, gfx_rect_t *);
 static errno_t test_window_resize_req(void *, sysarg_t, display_wnd_rsztype_t,
-    gfx_coord2_t *);
+    gfx_coord2_t *, sysarg_t);
 static errno_t test_window_resize(void *, sysarg_t, gfx_coord2_t *,
     gfx_rect_t *);
+static errno_t test_window_minimize(void *, sysarg_t);
+static errno_t test_window_maximize(void *, sysarg_t);
+static errno_t test_window_unmaximize(void *, sysarg_t);
 static errno_t test_window_set_cursor(void *, sysarg_t, display_stock_cursor_t);
+static errno_t test_window_set_caption(void *, sysarg_t, const char *);
 static errno_t test_get_event(void *, sysarg_t *, display_wnd_ev_t *);
 static errno_t test_get_info(void *, display_info_t *);
@@ -76,7 +82,12 @@
 	.window_move = test_window_move,
 	.window_get_pos = test_window_get_pos,
+	.window_get_max_rect = test_window_get_max_rect,
 	.window_resize_req = test_window_resize_req,
 	.window_resize = test_window_resize,
+	.window_minimize = test_window_minimize,
+	.window_maximize = test_window_maximize,
+	.window_unmaximize = test_window_unmaximize,
 	.window_set_cursor = test_window_set_cursor,
+	.window_set_caption = test_window_set_caption,
 	.get_event = test_get_event,
 	.get_info = test_get_info
@@ -107,4 +118,5 @@
 	gfx_rect_t create_rect;
 	gfx_coord2_t create_min_size;
+	sysarg_t create_idev_id;
 	bool window_destroy_called;
 	sysarg_t destroy_wnd_id;
@@ -113,4 +125,5 @@
 	sysarg_t move_req_wnd_id;
 	gfx_coord2_t move_req_pos;
+	sysarg_t move_req_pos_id;
 
 	bool window_move_called;
@@ -121,4 +134,8 @@
 	sysarg_t get_pos_wnd_id;
 	gfx_coord2_t get_pos_rpos;
+
+	bool window_get_max_rect_called;
+	sysarg_t get_max_rect_wnd_id;
+	gfx_rect_t get_max_rect_rrect;
 
 	bool window_resize_req_called;
@@ -126,4 +143,5 @@
 	display_wnd_rsztype_t resize_req_rsztype;
 	gfx_coord2_t resize_req_pos;
+	sysarg_t resize_req_pos_id;
 
 	bool window_resize_called;
@@ -132,8 +150,16 @@
 	sysarg_t resize_wnd_id;
 
+	bool window_minimize_called;
+	bool window_maximize_called;
+	bool window_unmaximize_called;
+
 	bool window_set_cursor_called;
 	sysarg_t set_cursor_wnd_id;
 	display_stock_cursor_t set_cursor_cursor;
 
+	bool window_set_caption_called;
+	sysarg_t set_caption_wnd_id;
+	char *set_caption_caption;
+
 	bool get_event_called;
 
@@ -143,4 +169,5 @@
 	bool set_color_called;
 	bool close_event_called;
+
 	bool focus_event_called;
 	bool kbd_event_called;
@@ -159,21 +186,23 @@
 	display_t *disp = NULL;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = display_open(test_display_svc, &disp);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(disp);
-
-	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = display_open(test_display_svc, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(disp);
+
+	display_close(disp);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -187,12 +216,13 @@
 	display_window_t *wnd;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -211,4 +241,5 @@
 	params.min_size.x = 11;
 	params.min_size.y = 12;
+	params.idev_id = 42;
 
 	rc = display_window_create(disp, &params, &test_display_wnd_cb,
@@ -221,10 +252,12 @@
 	PCUT_ASSERT_EQUALS(params.min_size.x, resp.create_min_size.x);
 	PCUT_ASSERT_EQUALS(params.min_size.y, resp.create_min_size.y);
+	PCUT_ASSERT_EQUALS(params.idev_id, resp.create_idev_id);
 	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
 	PCUT_ASSERT_NULL(wnd);
 
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -241,12 +274,13 @@
 	display_window_t *wnd;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -263,4 +297,5 @@
 	params.rect.p0.x = 100;
 	params.rect.p0.y = 100;
+	params.idev_id = 42;
 
 	rc = display_window_create(disp, &params, &test_display_wnd_cb,
@@ -271,4 +306,5 @@
 	PCUT_ASSERT_EQUALS(params.rect.p1.x, resp.create_rect.p1.x);
 	PCUT_ASSERT_EQUALS(params.rect.p1.y, resp.create_rect.p1.y);
+	PCUT_ASSERT_EQUALS(params.idev_id, resp.create_idev_id);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(wnd);
@@ -281,6 +317,7 @@
 
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -294,12 +331,13 @@
 	display_window_t *wnd;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -334,6 +372,7 @@
 
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -354,12 +393,14 @@
 	test_response_t resp;
 	gfx_coord2_t pos;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	sysarg_t pos_id;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -385,6 +426,7 @@
 	pos.x = 42;
 	pos.y = 43;
-
-	rc = display_window_move_req(wnd, &pos);
+	pos_id = 44;
+
+	rc = display_window_move_req(wnd, &pos, pos_id);
 	PCUT_ASSERT_TRUE(resp.window_move_req_called);
 	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
@@ -392,9 +434,11 @@
 	PCUT_ASSERT_INT_EQUALS(pos.x, resp.move_req_pos.x);
 	PCUT_ASSERT_INT_EQUALS(pos.y, resp.move_req_pos.y);
+	PCUT_ASSERT_INT_EQUALS(pos_id, resp.move_req_pos_id);
 
 	display_window_destroy(wnd);
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -409,12 +453,14 @@
 	test_response_t resp;
 	gfx_coord2_t pos;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	sysarg_t pos_id;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -440,6 +486,7 @@
 	pos.x = 42;
 	pos.y = 43;
-
-	rc = display_window_move_req(wnd, &pos);
+	pos_id = 44;
+
+	rc = display_window_move_req(wnd, &pos, pos_id);
 	PCUT_ASSERT_TRUE(resp.window_move_req_called);
 	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
@@ -447,9 +494,11 @@
 	PCUT_ASSERT_INT_EQUALS(pos.x, resp.move_req_pos.x);
 	PCUT_ASSERT_INT_EQUALS(pos.y, resp.move_req_pos.y);
+	PCUT_ASSERT_INT_EQUALS(pos_id, resp.move_req_pos_id);
 
 	display_window_destroy(wnd);
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -464,12 +513,13 @@
 	gfx_coord2_t dpos;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -504,6 +554,7 @@
 	display_window_destroy(wnd);
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -518,12 +569,13 @@
 	gfx_coord2_t dpos;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -558,6 +610,7 @@
 	display_window_destroy(wnd);
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -572,12 +625,13 @@
 	gfx_coord2_t dpos;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -613,6 +667,7 @@
 	display_window_destroy(wnd);
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -627,12 +682,13 @@
 	gfx_coord2_t dpos;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -670,6 +726,133 @@
 	display_window_destroy(wnd);
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** display_window_get_max_rect() with server returning error response works. */
+PCUT_TEST(window_get_max_rect_failure)
+{
+	errno_t rc;
+	service_id_t sid;
+	display_t *disp = NULL;
+	display_wnd_params_t params;
+	display_window_t *wnd;
+	gfx_rect_t rect;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = display_open(test_display_svc, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(disp);
+
+	resp.rc = EOK;
+	display_wnd_params_init(&params);
+	params.rect.p0.x = 0;
+	params.rect.p0.y = 0;
+	params.rect.p0.x = 100;
+	params.rect.p0.y = 100;
+
+	rc = display_window_create(disp, &params, &test_display_wnd_cb,
+	    (void *) &resp, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wnd);
+
+	resp.rc = EIO;
+	resp.window_get_max_rect_called = false;
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 0;
+	rect.p1.y = 0;
+
+	rc = display_window_get_max_rect(wnd, &rect);
+	PCUT_ASSERT_TRUE(resp.window_get_max_rect_called);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+	PCUT_ASSERT_INT_EQUALS(wnd->id, resp.get_max_rect_wnd_id);
+	PCUT_ASSERT_INT_EQUALS(0, rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(0, rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(0, rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(0, rect.p1.y);
+
+	display_window_destroy(wnd);
+	display_close(disp);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** display_window_get_max_rect() with server returning success response works. */
+PCUT_TEST(window_get_max_rect_success)
+{
+	errno_t rc;
+	service_id_t sid;
+	display_t *disp = NULL;
+	display_wnd_params_t params;
+	display_window_t *wnd;
+	gfx_rect_t rect;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = display_open(test_display_svc, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(disp);
+
+	resp.rc = EOK;
+	display_wnd_params_init(&params);
+	params.rect.p0.x = 0;
+	params.rect.p0.y = 0;
+	params.rect.p0.x = 100;
+	params.rect.p0.y = 100;
+
+	rc = display_window_create(disp, &params, &test_display_wnd_cb,
+	    (void *) &resp, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wnd);
+
+	resp.rc = EOK;
+	resp.window_get_max_rect_called = false;
+	resp.get_max_rect_rrect.p0.x = 11;
+	resp.get_max_rect_rrect.p0.y = 12;
+	resp.get_max_rect_rrect.p1.x = 13;
+	resp.get_max_rect_rrect.p1.y = 14;
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 0;
+	rect.p1.y = 0;
+
+	rc = display_window_get_max_rect(wnd, &rect);
+	PCUT_ASSERT_TRUE(resp.window_get_max_rect_called);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+	PCUT_ASSERT_INT_EQUALS(wnd->id, resp.get_max_rect_wnd_id);
+	PCUT_ASSERT_INT_EQUALS(resp.get_max_rect_rrect.p0.x, rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(resp.get_max_rect_rrect.p0.y, rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(resp.get_max_rect_rrect.p1.x, rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(resp.get_max_rect_rrect.p1.y, rect.p1.y);
+
+	display_window_destroy(wnd);
+	display_close(disp);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -685,12 +868,14 @@
 	display_wnd_rsztype_t rsztype;
 	gfx_coord2_t pos;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	sysarg_t pos_id;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -717,6 +902,7 @@
 	pos.x = 42;
 	pos.y = 43;
-
-	rc = display_window_resize_req(wnd, rsztype, &pos);
+	pos_id = 44;
+
+	rc = display_window_resize_req(wnd, rsztype, &pos, pos_id);
 	PCUT_ASSERT_TRUE(resp.window_resize_req_called);
 	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
@@ -725,9 +911,11 @@
 	PCUT_ASSERT_INT_EQUALS(pos.x, resp.resize_req_pos.x);
 	PCUT_ASSERT_INT_EQUALS(pos.y, resp.resize_req_pos.y);
+	PCUT_ASSERT_INT_EQUALS(pos_id, resp.resize_req_pos_id);
 
 	display_window_destroy(wnd);
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -743,12 +931,14 @@
 	display_wnd_rsztype_t rsztype;
 	gfx_coord2_t pos;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	sysarg_t pos_id;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -775,6 +965,7 @@
 	pos.x = 42;
 	pos.y = 43;
-
-	rc = display_window_resize_req(wnd, rsztype, &pos);
+	pos_id = 44;
+
+	rc = display_window_resize_req(wnd, rsztype, &pos, pos_id);
 	PCUT_ASSERT_TRUE(resp.window_resize_req_called);
 	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
@@ -783,9 +974,11 @@
 	PCUT_ASSERT_INT_EQUALS(pos.x, resp.resize_req_pos.x);
 	PCUT_ASSERT_INT_EQUALS(pos.y, resp.resize_req_pos.y);
+	PCUT_ASSERT_INT_EQUALS(pos_id, resp.resize_req_pos_id);
 
 	display_window_destroy(wnd);
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -801,12 +994,13 @@
 	gfx_rect_t nrect;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -849,6 +1043,7 @@
 	display_window_destroy(wnd);
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -864,12 +1059,13 @@
 	gfx_rect_t nrect;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -911,6 +1107,207 @@
 	display_window_destroy(wnd);
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** display_window_minimize() with server returning error response works. */
+PCUT_TEST(window_minimize_failure)
+{
+	errno_t rc;
+	service_id_t sid;
+	display_t *disp = NULL;
+	display_wnd_params_t params;
+	display_window_t *wnd;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = display_open(test_display_svc, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(disp);
+
+	resp.rc = EOK;
+	display_wnd_params_init(&params);
+	params.rect.p0.x = 0;
+	params.rect.p0.y = 0;
+	params.rect.p0.x = 100;
+	params.rect.p0.y = 100;
+
+	rc = display_window_create(disp, &params, &test_display_wnd_cb,
+	    (void *) &resp, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wnd);
+
+	resp.rc = EIO;
+	resp.window_minimize_called = false;
+
+	rc = display_window_minimize(wnd);
+	PCUT_ASSERT_TRUE(resp.window_minimize_called);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	display_window_destroy(wnd);
+	display_close(disp);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** display_window_minimize() with server returning success response works. */
+PCUT_TEST(window_minimize_success)
+{
+	errno_t rc;
+	service_id_t sid;
+	display_t *disp = NULL;
+	display_wnd_params_t params;
+	display_window_t *wnd;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = display_open(test_display_svc, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(disp);
+
+	resp.rc = EOK;
+	display_wnd_params_init(&params);
+	params.rect.p0.x = 0;
+	params.rect.p0.y = 0;
+	params.rect.p0.x = 100;
+	params.rect.p0.y = 100;
+
+	rc = display_window_create(disp, &params, &test_display_wnd_cb,
+	    (void *) &resp, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wnd);
+
+	resp.rc = EOK;
+	resp.window_minimize_called = false;
+
+	rc = display_window_minimize(wnd);
+	PCUT_ASSERT_TRUE(resp.window_minimize_called);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	display_window_destroy(wnd);
+	display_close(disp);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** display_window_maximize() with server returning error response works. */
+PCUT_TEST(window_maximize_failure)
+{
+	errno_t rc;
+	service_id_t sid;
+	display_t *disp = NULL;
+	display_wnd_params_t params;
+	display_window_t *wnd;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = display_open(test_display_svc, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(disp);
+
+	resp.rc = EOK;
+	display_wnd_params_init(&params);
+	params.rect.p0.x = 0;
+	params.rect.p0.y = 0;
+	params.rect.p0.x = 100;
+	params.rect.p0.y = 100;
+
+	rc = display_window_create(disp, &params, &test_display_wnd_cb,
+	    (void *) &resp, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wnd);
+
+	resp.rc = EIO;
+	resp.window_maximize_called = false;
+
+	rc = display_window_maximize(wnd);
+	PCUT_ASSERT_TRUE(resp.window_maximize_called);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	display_window_destroy(wnd);
+	display_close(disp);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** display_window_maximize() with server returning success response works. */
+PCUT_TEST(window_maximize_success)
+{
+	errno_t rc;
+	service_id_t sid;
+	display_t *disp = NULL;
+	display_wnd_params_t params;
+	display_window_t *wnd;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = display_open(test_display_svc, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(disp);
+
+	resp.rc = EOK;
+	display_wnd_params_init(&params);
+	params.rect.p0.x = 0;
+	params.rect.p0.y = 0;
+	params.rect.p0.x = 100;
+	params.rect.p0.y = 100;
+
+	rc = display_window_create(disp, &params, &test_display_wnd_cb,
+	    (void *) &resp, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wnd);
+
+	resp.rc = EOK;
+	resp.window_maximize_called = false;
+
+	rc = display_window_maximize(wnd);
+	PCUT_ASSERT_TRUE(resp.window_maximize_called);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	display_window_destroy(wnd);
+	display_close(disp);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -924,12 +1321,13 @@
 	display_window_t *wnd;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -961,6 +1359,7 @@
 	display_window_destroy(wnd);
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -974,12 +1373,13 @@
 	display_window_t *wnd;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -1011,6 +1411,119 @@
 	display_window_destroy(wnd);
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** display_window_set_caption() with server returning error response works. */
+PCUT_TEST(window_set_caption_failure)
+{
+	errno_t rc;
+	service_id_t sid;
+	display_t *disp = NULL;
+	display_wnd_params_t params;
+	display_window_t *wnd;
+	const char *caption;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = display_open(test_display_svc, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(disp);
+
+	resp.rc = EOK;
+	display_wnd_params_init(&params);
+	params.rect.p0.x = 0;
+	params.rect.p0.y = 0;
+	params.rect.p0.x = 100;
+	params.rect.p0.y = 100;
+
+	rc = display_window_create(disp, &params, &test_display_wnd_cb,
+	    (void *) &resp, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wnd);
+
+	caption = "Hello";
+
+	resp.rc = EIO;
+	resp.window_set_caption_called = false;
+
+	rc = display_window_set_caption(wnd, caption);
+	PCUT_ASSERT_INT_EQUALS(wnd->id, resp.set_caption_wnd_id);
+	PCUT_ASSERT_TRUE(resp.window_set_caption_called);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+	PCUT_ASSERT_INT_EQUALS(0, str_cmp(caption, resp.set_caption_caption));
+
+	//free(resp.set_caption_caption);
+	display_window_destroy(wnd);
+	display_close(disp);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** display_window_set_caption() with server returning success response works. */
+PCUT_TEST(window_set_caption_success)
+{
+	errno_t rc;
+	service_id_t sid;
+	display_t *disp = NULL;
+	display_wnd_params_t params;
+	display_window_t *wnd;
+	const char *caption;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = display_open(test_display_svc, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(disp);
+
+	resp.rc = EOK;
+	display_wnd_params_init(&params);
+	params.rect.p0.x = 0;
+	params.rect.p0.y = 0;
+	params.rect.p0.x = 100;
+	params.rect.p0.y = 100;
+
+	rc = display_window_create(disp, &params, &test_display_wnd_cb,
+	    (void *) &resp, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wnd);
+
+	caption = "Hello";
+
+	resp.rc = EOK;
+	resp.window_set_caption_called = false;
+
+	rc = display_window_set_caption(wnd, caption);
+	PCUT_ASSERT_INT_EQUALS(wnd->id, resp.set_caption_wnd_id);
+	PCUT_ASSERT_TRUE(resp.window_set_caption_called);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+	PCUT_ASSERT_INT_EQUALS(0, str_cmp(caption, resp.set_caption_caption));
+
+	//free(resp.set_caption_caption);
+	display_window_destroy(wnd);
+	display_close(disp);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -1025,12 +1538,13 @@
 	test_response_t resp;
 	gfx_context_t *gc;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -1063,6 +1577,7 @@
 
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -1078,12 +1593,13 @@
 	gfx_context_t *gc;
 	gfx_color_t *color;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -1124,6 +1640,7 @@
 
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -1137,12 +1654,13 @@
 	display_window_t *wnd;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -1189,6 +1707,7 @@
 	display_close(disp);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -1202,12 +1721,13 @@
 	display_window_t *wnd;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -1232,4 +1752,5 @@
 	resp.event_cnt = 1;
 	resp.event.etype = wev_focus;
+	resp.event.ev.focus.nfocus = 42;
 	resp.wnd_id = wnd->id;
 	resp.focus_event_called = false;
@@ -1248,4 +1769,6 @@
 	PCUT_ASSERT_INT_EQUALS(resp.event.etype,
 	    resp.revent.etype);
+	PCUT_ASSERT_INT_EQUALS(resp.event.ev.focus.nfocus,
+	    resp.revent.ev.focus.nfocus);
 
 	rc = display_window_destroy(wnd);
@@ -1254,6 +1777,7 @@
 	display_close(disp);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -1267,12 +1791,13 @@
 	display_window_t *wnd;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -1331,6 +1856,7 @@
 	display_close(disp);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -1344,12 +1870,13 @@
 	display_window_t *wnd;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -1408,6 +1935,7 @@
 	display_close(disp);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -1421,12 +1949,13 @@
 	display_window_t *wnd;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -1451,4 +1980,5 @@
 	resp.event_cnt = 1;
 	resp.event.etype = wev_unfocus;
+	resp.event.ev.unfocus.nfocus = 42;
 	resp.wnd_id = wnd->id;
 	resp.unfocus_event_called = false;
@@ -1467,4 +1997,6 @@
 	PCUT_ASSERT_INT_EQUALS(resp.event.etype,
 	    resp.revent.etype);
+	PCUT_ASSERT_INT_EQUALS(resp.event.ev.focus.nfocus,
+	    resp.revent.ev.focus.nfocus);
 
 	rc = display_window_destroy(wnd);
@@ -1473,6 +2005,7 @@
 	display_close(disp);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -1485,12 +2018,13 @@
 	display_info_t info;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -1507,6 +2041,7 @@
 
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -1519,12 +2054,13 @@
 	display_info_t info;
 	test_response_t resp;
-
-	async_set_fallback_port_handler(test_display_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_display_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_display_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_display_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -1549,6 +2085,7 @@
 
 	display_close(disp);
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -1612,9 +2149,10 @@
 }
 
-static void test_focus_event(void *arg)
+static void test_focus_event(void *arg, unsigned nfocus)
 {
 	test_response_t *resp = (test_response_t *) arg;
 
 	resp->revent.etype = wev_focus;
+	resp->revent.ev.focus.nfocus = nfocus;
 
 	fibril_mutex_lock(&resp->event_lock);
@@ -1650,9 +2188,10 @@
 }
 
-static void test_unfocus_event(void *arg)
+static void test_unfocus_event(void *arg, unsigned nfocus)
 {
 	test_response_t *resp = (test_response_t *) arg;
 
 	resp->revent.etype = wev_unfocus;
+	resp->revent.ev.unfocus.nfocus = nfocus;
 
 	fibril_mutex_lock(&resp->event_lock);
@@ -1670,4 +2209,5 @@
 	resp->create_rect = params->rect;
 	resp->create_min_size = params->min_size;
+	resp->create_idev_id = params->idev_id;
 	if (resp->rc == EOK)
 		*rwnd_id = resp->wnd_id;
@@ -1686,5 +2226,5 @@
 
 static errno_t test_window_move_req(void *arg, sysarg_t wnd_id,
-    gfx_coord2_t *pos)
+    gfx_coord2_t *pos, sysarg_t pos_id)
 {
 	test_response_t *resp = (test_response_t *) arg;
@@ -1693,4 +2233,5 @@
 	resp->move_req_wnd_id = wnd_id;
 	resp->move_req_pos = *pos;
+	resp->move_req_pos_id = pos_id;
 	return resp->rc;
 }
@@ -1719,6 +2260,20 @@
 }
 
+static errno_t test_window_get_max_rect(void *arg, sysarg_t wnd_id,
+    gfx_rect_t *rect)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->window_get_max_rect_called = true;
+	resp->get_max_rect_wnd_id = wnd_id;
+
+	if (resp->rc == EOK)
+		*rect = resp->get_max_rect_rrect;
+
+	return resp->rc;
+}
+
 static errno_t test_window_resize_req(void *arg, sysarg_t wnd_id,
-    display_wnd_rsztype_t rsztype, gfx_coord2_t *pos)
+    display_wnd_rsztype_t rsztype, gfx_coord2_t *pos, sysarg_t pos_id)
 {
 	test_response_t *resp = (test_response_t *) arg;
@@ -1728,4 +2283,5 @@
 	resp->resize_req_wnd_id = wnd_id;
 	resp->resize_req_pos = *pos;
+	resp->resize_req_pos_id = pos_id;
 	return resp->rc;
 }
@@ -1743,4 +2299,31 @@
 }
 
+static errno_t test_window_minimize(void *arg, sysarg_t wnd_id)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->window_minimize_called = true;
+	resp->resize_wnd_id = wnd_id;
+	return resp->rc;
+}
+
+static errno_t test_window_maximize(void *arg, sysarg_t wnd_id)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->window_maximize_called = true;
+	resp->resize_wnd_id = wnd_id;
+	return resp->rc;
+}
+
+static errno_t test_window_unmaximize(void *arg, sysarg_t wnd_id)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->window_unmaximize_called = true;
+	resp->resize_wnd_id = wnd_id;
+	return resp->rc;
+}
+
 static errno_t test_window_set_cursor(void *arg, sysarg_t wnd_id,
     display_stock_cursor_t cursor)
@@ -1751,4 +2334,16 @@
 	resp->set_cursor_wnd_id = wnd_id;
 	resp->set_cursor_cursor = cursor;
+
+	return resp->rc;
+}
+
+static errno_t test_window_set_caption(void *arg, sysarg_t wnd_id,
+    const char *caption)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->window_set_caption_called = true;
+	resp->set_caption_wnd_id = wnd_id;
+	resp->set_caption_caption = str_dup(caption);
 
 	return resp->rc;
Index: uspace/lib/dltest/dltest.c
===================================================================
--- uspace/lib/dltest/dltest.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/dltest/dltest.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2016 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -43,4 +43,6 @@
 /** Public initialized variable */
 int dl_public_var = dl_public_var_val;
+/** Public variable initialized with the address of a symbol */
+int *dl_public_ptr_var = &dl_public_var;
 /** Public uninitialized variable */
 int dl_public_uvar;
Index: uspace/lib/dltest/libdltest.h
===================================================================
--- uspace/lib/dltest/libdltest.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/dltest/libdltest.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2016 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -67,4 +67,5 @@
 extern int dl_public_var;
 extern int dl_public_uvar;
+extern int *dl_public_ptr_var;
 extern fibril_local int dl_public_fib_var;
 extern fibril_local int dl_public_fib_uvar;
Index: uspace/lib/drv/generic/interrupt.c
===================================================================
--- uspace/lib/drv/generic/interrupt.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/drv/generic/interrupt.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2010 Lenka Trochtova
  * All rights reserved.
@@ -43,9 +44,10 @@
 
 errno_t register_interrupt_handler(ddf_dev_t *dev, int irq,
-    interrupt_handler_t *handler, const irq_code_t *irq_code,
+    interrupt_handler_t *handler, void *arg, const irq_code_t *irq_code,
     cap_irq_handle_t *handle)
 {
+	(void)dev;
 	return async_irq_subscribe(irq, (async_notification_handler_t) handler,
-	    dev, irq_code, handle);
+	    arg, irq_code, handle);
 }
 
Index: uspace/lib/drv/generic/remote_hw_res.c
===================================================================
--- uspace/lib/drv/generic/remote_hw_res.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/drv/generic/remote_hw_res.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2010 Lenka Trochtova
  * Copyright (c) 2011 Jan Vesely
@@ -47,4 +48,5 @@
 static void remote_hw_res_dma_channel_setup(ddf_fun_t *, void *, ipc_call_t *);
 static void remote_hw_res_dma_channel_remain(ddf_fun_t *, void *, ipc_call_t *);
+static void remote_hw_res_get_flags(ddf_fun_t *, void *, ipc_call_t *);
 
 static const remote_iface_func_ptr_t remote_hw_res_iface_ops [] = {
@@ -55,4 +57,5 @@
 	[HW_RES_DMA_CHANNEL_SETUP] = &remote_hw_res_dma_channel_setup,
 	[HW_RES_DMA_CHANNEL_REMAIN] = &remote_hw_res_dma_channel_remain,
+	[HW_RES_GET_FLAGS] = &remote_hw_res_get_flags
 };
 
@@ -103,5 +106,5 @@
 
 	const int irq = DEV_IPC_GET_ARG1(*call);
-	const errno_t ret = hw_res_ops->enable_interrupt(fun, irq);
+	const errno_t ret = hw_res_ops->clear_interrupt(fun, irq);
 	async_answer_0(call, ret);
 }
@@ -171,4 +174,19 @@
 }
 
+static void remote_hw_res_get_flags(ddf_fun_t *fun, void *ops,
+    ipc_call_t *call)
+{
+	hw_res_ops_t *hw_res_ops = ops;
+
+	if (hw_res_ops->get_flags == NULL) {
+		async_answer_0(call, ENOTSUP);
+		return;
+	}
+
+	hw_res_flags_t flags = 0;
+	const errno_t ret = hw_res_ops->get_flags(fun, &flags);
+	async_answer_1(call, ret, flags);
+}
+
 /**
  * @}
Index: uspace/lib/drv/include/ddf/interrupt.h
===================================================================
--- uspace/lib/drv/include/ddf/interrupt.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/drv/include/ddf/interrupt.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2010 Lenka Trochtova
  * All rights reserved.
@@ -48,8 +49,8 @@
  */
 
-typedef void interrupt_handler_t(ipc_call_t *, ddf_dev_t *);
+typedef void interrupt_handler_t(ipc_call_t *, void *);
 
-extern errno_t register_interrupt_handler(ddf_dev_t *, int, interrupt_handler_t *,
-    const irq_code_t *, cap_irq_handle_t *);
+extern errno_t register_interrupt_handler(ddf_dev_t *, int,
+    interrupt_handler_t *, void *, const irq_code_t *, cap_irq_handle_t *);
 extern errno_t unregister_interrupt_handler(ddf_dev_t *, cap_irq_handle_t);
 
Index: uspace/lib/drv/include/ops/hw_res.h
===================================================================
--- uspace/lib/drv/include/ops/hw_res.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/drv/include/ops/hw_res.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2010 Lenka Trochtova
  * Copyright (c) 2011 Jan Vesely
@@ -49,4 +50,5 @@
 	errno_t (*dma_channel_setup)(ddf_fun_t *, unsigned, uint32_t, uint32_t, uint8_t);
 	errno_t (*dma_channel_remain)(ddf_fun_t *, unsigned, size_t *);
+	errno_t (*get_flags)(ddf_fun_t *, hw_res_flags_t *);
 } hw_res_ops_t;
 
Index: uspace/lib/ext4/include/ext4/types.h
===================================================================
--- uspace/lib/ext4/include/ext4/types.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ext4/include/ext4/types.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -141,5 +141,5 @@
 	uint32_t encrypt_algos;             /* Encrypt algorithm in use */
 	uint32_t padding[105];              /* Padding to the end of the block */
-} __attribute__((packed)) ext4_superblock_t;
+} ext4_superblock_t;
 
 #define EXT4_GOOD_OLD_REV 0
@@ -335,5 +335,5 @@
 			uint32_t author;
 		} hurd2;
-	} __attribute__((packed)) osd2;
+	} osd2;
 
 	uint16_t extra_isize;
@@ -345,5 +345,5 @@
 	uint32_t crtime_extra;  /* Extra file creation time (nsec << 2 | epoch) */
 	uint32_t version_hi;    /* High 32 bits for 64-bit version */
-} __attribute__((packed)) ext4_inode_t;
+} ext4_inode_t;
 
 #define EXT4_INODE_MODE_FIFO       0x1000
@@ -418,8 +418,8 @@
 		uint8_t name_length_high;  /* Higher 8 bits of name length */
 		uint8_t inode_type;        /* Type of referenced inode (in rev >= 0.5) */
-	} __attribute__((packed));
+	};
 
 	uint8_t name[EXT4_DIRECTORY_FILENAME_LEN];  /* Entry name */
-} __attribute__((packed)) ext4_directory_entry_ll_t;
+} ext4_directory_entry_ll_t;
 
 typedef struct ext4_directory_iterator {
Index: uspace/lib/ext4/src/extent.c
===================================================================
--- uspace/lib/ext4/src/extent.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ext4/src/extent.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -643,5 +643,5 @@
 
 	uint16_t delete_count = block_count -
-	    (ext4_extent_get_start(path_ptr->extent) - first_fblock);
+	    (first_fblock - ext4_extent_get_start(path_ptr->extent));
 
 	/* Release all blocks */
Index: uspace/lib/ext4/src/filesystem.c
===================================================================
--- uspace/lib/ext4/src/filesystem.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ext4/src/filesystem.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2018 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2011 Martin Sucha
  * Copyright (c) 2012 Frantisek Princ
@@ -83,5 +83,5 @@
 
 	/* Initialize block library (4096 is size of communication channel) */
-	rc = block_init(fs->device, 4096);
+	rc = block_init(fs->device);
 	if (rc != EOK)
 		goto err;
@@ -265,5 +265,5 @@
 
 	/* Initialize block library (4096 is size of communication channel) */
-	rc = block_init(service_id, 4096);
+	rc = block_init(service_id);
 	if (rc != EOK)
 		goto err;
Index: uspace/lib/ext4/src/ops.c
===================================================================
--- uspace/lib/ext4/src/ops.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ext4/src/ops.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -122,5 +122,5 @@
 }
 
-static hash_table_ops_t open_nodes_ops = {
+static const hash_table_ops_t open_nodes_ops = {
 	.hash = open_nodes_hash,
 	.key_hash = open_nodes_key_hash,
@@ -810,15 +810,14 @@
 }
 
-/** Extract device identifier from node.
+/** Get service ID for a service-special file.
  *
  * @param node Node to extract id from
  *
- * @return id of device, where is the filesystem
+ * @return Service ID for a service-special file, zero for a regular file.
  *
  */
 service_id_t ext4_service_get(fs_node_t *fn)
 {
-	ext4_node_t *enode = EXT4_NODE(fn);
-	return enode->instance->service_id;
+	return 0;
 }
 
Index: uspace/lib/fdisk/src/fdisk.c
===================================================================
--- uspace/lib/fdisk/src/fdisk.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/fdisk/src/fdisk.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2015 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -810,7 +810,16 @@
 }
 
+/** Destroy partition.
+ *
+ * @param part Partition
+ * @return EOK on success or error code
+ */
 errno_t fdisk_part_destroy(fdisk_part_t *part)
 {
 	errno_t rc;
+
+	rc = vol_part_eject(part->dev->fdisk->vol, part->svc_id);
+	if (rc != EOK)
+		return EIO;
 
 	rc = vbd_part_delete(part->dev->fdisk->vbd, part->part_id);
Index: uspace/lib/fmtutil/meson.build
===================================================================
--- uspace/lib/fmtutil/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/fmtutil/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -27,3 +27,4 @@
 #
 
+deps = [ 'console' ]
 src = files('fmtutil.c')
Index: uspace/lib/gfx/src/color.c
===================================================================
--- uspace/lib/gfx/src/color.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/gfx/src/color.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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/gfxfont/include/gfx/text.h
===================================================================
--- uspace/lib/gfxfont/include/gfx/text.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/gfxfont/include/gfx/text.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -38,4 +38,5 @@
 
 #include <errno.h>
+#include <stddef.h>
 #include <types/gfx/coord.h>
 #include <types/gfx/font.h>
@@ -44,14 +45,13 @@
 extern void gfx_text_fmt_init(gfx_text_fmt_t *);
 extern gfx_coord_t gfx_text_width(gfx_font_t *, const char *);
-extern errno_t gfx_puttext(gfx_font_t *, gfx_coord2_t *, gfx_text_fmt_t *,
-    const char *);
-extern void gfx_text_start_pos(gfx_font_t *, gfx_coord2_t *, gfx_text_fmt_t *,
-    const char *, gfx_coord2_t *);
-extern size_t gfx_text_find_pos(gfx_font_t *, gfx_coord2_t *, gfx_text_fmt_t *,
-    const char *, gfx_coord2_t *);
-extern void gfx_text_cont(gfx_font_t *, gfx_coord2_t *, gfx_text_fmt_t *,
-    const char *, gfx_coord2_t *, gfx_text_fmt_t *);
-extern void gfx_text_rect(gfx_font_t *, gfx_coord2_t *, gfx_text_fmt_t *,
-    const char *, gfx_rect_t *);
+extern errno_t gfx_puttext(gfx_coord2_t *, gfx_text_fmt_t *, const char *);
+extern void gfx_text_start_pos(gfx_coord2_t *, gfx_text_fmt_t *, const char *,
+    gfx_coord2_t *);
+extern size_t gfx_text_find_pos(gfx_coord2_t *, gfx_text_fmt_t *, const char *,
+    gfx_coord2_t *);
+extern void gfx_text_cont(gfx_coord2_t *, gfx_text_fmt_t *, const char *,
+    gfx_coord2_t *, gfx_text_fmt_t *);
+extern void gfx_text_rect(gfx_coord2_t *, gfx_text_fmt_t *, const char *,
+    gfx_rect_t *);
 
 #endif
Index: uspace/lib/gfxfont/include/types/gfx/text.h
===================================================================
--- uspace/lib/gfxfont/include/types/gfx/text.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/gfxfont/include/types/gfx/text.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -37,4 +37,5 @@
 #define _TYPES_GFX_TEXT_H
 
+#include <stdbool.h>
 #include <types/gfx/coord.h>
 #include <types/gfx/color.h>
@@ -66,12 +67,18 @@
 /** Text formatting */
 typedef struct {
+	/** Text font */
+	struct gfx_font *font;
 	/** Text color */
 	gfx_color_t *color;
 	/** Horizontal alignment */
 	gfx_halign_t halign;
-	/** Justification width (for gfx_halign_justify) */
-	gfx_coord_t justify_width;
+	/** Width available for the text */
+	gfx_coord_t width;
 	/** Vertical alignment */
 	gfx_valign_t valign;
+	/** Abbreviate the text with ellipsis if it does not fit @c width */
+	bool abbreviate;
+	/** Underline */
+	bool underline;
 } gfx_text_fmt_t;
 
Index: uspace/lib/gfxfont/include/types/gfx/typeface.h
===================================================================
--- uspace/lib/gfxfont/include/types/gfx/typeface.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/gfxfont/include/types/gfx/typeface.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -50,4 +50,8 @@
 	/** Leading */
 	gfx_coord_t leading;
+	/** Underline start Y coordinate (inclusive) */
+	gfx_coord_t underline_y0;
+	/** Underline end Y coordinate (exclusive) */
+	gfx_coord_t underline_y1;
 } gfx_font_metrics_t;
 
Index: uspace/lib/gfxfont/private/tpf_file.h
===================================================================
--- uspace/lib/gfxfont/private/tpf_file.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/gfxfont/private/tpf_file.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -76,4 +76,6 @@
 	uint16_t descent;
 	uint16_t leading;
+	int16_t underline_y0;
+	int16_t underline_y1;
 } tpf_font_metrics_t;
 
Index: uspace/lib/gfxfont/src/font.c
===================================================================
--- uspace/lib/gfxfont/src/font.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/gfxfont/src/font.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -546,4 +546,6 @@
 	metrics->descent = uint16_t_le2host(tmetrics.descent);
 	metrics->leading = uint16_t_le2host(tmetrics.leading);
+	metrics->underline_y0 = uint16_t_le2host(tmetrics.underline_y0);
+	metrics->underline_y1 = uint16_t_le2host(tmetrics.underline_y1);
 	return EOK;
 }
@@ -565,4 +567,6 @@
 	tmetrics.descent = host2uint16_t_le(metrics->descent);
 	tmetrics.leading = host2uint16_t_le(metrics->leading);
+	tmetrics.underline_y0 = host2uint16_t_le(metrics->underline_y0);
+	tmetrics.underline_y1 = host2uint16_t_le(metrics->underline_y1);
 
 	rc = riff_wchunk_start(riffw, CKID_fmtr, &mtrck);
Index: uspace/lib/gfxfont/src/text.c
===================================================================
--- uspace/lib/gfxfont/src/text.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/gfxfont/src/text.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -97,24 +97,39 @@
 /** Print string using text characters in text mode.
  *
- * @param font Font
  * @param pos Position of top-left corner of text
- * @param color Text color
+ * @param fmt Formatting
  * @param str String
  * @return EOK on success or an error code
  */
-static errno_t gfx_puttext_textmode(gfx_font_t *font, gfx_coord2_t *pos,
-    gfx_color_t *color, const char *str)
-{
-	gfx_context_t *gc = font->typeface->gc;
+static errno_t gfx_puttext_textmode(gfx_coord2_t *pos, gfx_text_fmt_t *fmt,
+    const char *str)
+{
+	gfx_context_t *gc = fmt->font->typeface->gc;
 	gfx_bitmap_params_t params;
 	gfx_bitmap_t *bitmap;
 	gfx_bitmap_alloc_t alloc;
+	gfx_coord_t width;
 	uint8_t attr;
 	pixelmap_t pmap;
 	gfx_coord_t x;
+	gfx_coord_t rmargin;
 	pixel_t pixel;
 	char32_t c;
 	size_t off;
+	bool ellipsis;
 	errno_t rc;
+
+	width = str_width(str);
+	if (fmt->abbreviate && width > fmt->width) {
+		ellipsis = true;
+		width = fmt->width;
+		if (width > 3)
+			rmargin = width - 3;
+		else
+			rmargin = width;
+	} else {
+		ellipsis = false;
+		rmargin = width;
+	}
 
 	/*
@@ -123,10 +138,10 @@
 	 */
 
-	gfx_color_get_ega(color, &attr);
+	gfx_color_get_ega(fmt->color, &attr);
 
 	gfx_bitmap_params_init(&params);
 	params.rect.p0.x = 0;
 	params.rect.p0.y = 0;
-	params.rect.p1.x = str_width(str);
+	params.rect.p1.x = width;
 	params.rect.p1.y = 1;
 
@@ -151,5 +166,5 @@
 
 	off = 0;
-	for (x = 0; x < params.rect.p1.x; x++) {
+	for (x = 0; x < rmargin; x++) {
 		c = str_decode(str, &off, STR_NO_LIMIT);
 		pixel = PIXEL(attr,
@@ -160,4 +175,15 @@
 	}
 
+	if (ellipsis) {
+		for (x = rmargin; x < params.rect.p1.x; x++) {
+			c = '.';
+			pixel = PIXEL(attr,
+			    (c >> 16) & 0xff,
+			    (c >> 8) & 0xff,
+			    c & 0xff);
+			pixelmap_put_pixel(&pmap, x, 0, pixel);
+		}
+	}
+
 	rc = gfx_bitmap_render(bitmap, NULL, pos);
 
@@ -168,5 +194,4 @@
 /** Get text starting position.
  *
- * @param font Font
  * @param pos Anchor position
  * @param fmt Text formatting
@@ -174,6 +199,6 @@
  * @param spos Place to store starting position
  */
-void gfx_text_start_pos(gfx_font_t *font, gfx_coord2_t *pos,
-    gfx_text_fmt_t *fmt, const char *str, gfx_coord2_t *spos)
+void gfx_text_start_pos(gfx_coord2_t *pos, gfx_text_fmt_t *fmt,
+    const char *str, gfx_coord2_t *spos)
 {
 	gfx_font_metrics_t fmetrics;
@@ -184,5 +209,9 @@
 	/* Adjust position for horizontal alignment */
 	if (fmt->halign != gfx_halign_left) {
-		width = gfx_text_width(font, str);
+		/* Compute text width */
+		width = gfx_text_width(fmt->font, str);
+		if (fmt->abbreviate && width > fmt->width)
+			width = fmt->width;
+
 		switch (fmt->halign) {
 		case gfx_halign_center:
@@ -198,5 +227,5 @@
 
 	/* Adjust position for vertical alignment */
-	gfx_font_get_metrics(font, &fmetrics);
+	gfx_font_get_metrics(fmt->font, &fmetrics);
 
 	if (fmt->valign != gfx_valign_baseline) {
@@ -219,5 +248,4 @@
 /** Render text.
  *
- * @param font Font
  * @param pos Anchor position
  * @param fmt Text formatting
@@ -225,27 +253,44 @@
  * @return EOK on success or an error code
  */
-errno_t gfx_puttext(gfx_font_t *font, gfx_coord2_t *pos,
-    gfx_text_fmt_t *fmt, const char *str)
+errno_t gfx_puttext(gfx_coord2_t *pos, gfx_text_fmt_t *fmt, const char *str)
 {
 	gfx_glyph_metrics_t gmetrics;
+	gfx_font_metrics_t fmetrics;
 	size_t stradv;
 	const char *cp;
 	gfx_glyph_t *glyph;
 	gfx_coord2_t cpos;
+	gfx_coord2_t spos;
+	gfx_rect_t rect;
+	gfx_coord_t width;
+	gfx_coord_t rmargin;
+	bool ellipsis;
 	errno_t rc;
 
-	gfx_text_start_pos(font, pos, fmt, str, &cpos);
+	gfx_text_start_pos(pos, fmt, str, &spos);
 
 	/* Text mode */
-	if ((font->finfo->props.flags & gff_text_mode) != 0)
-		return gfx_puttext_textmode(font, &cpos, fmt->color, str);
-
-	rc = gfx_set_color(font->typeface->gc, fmt->color);
+	if ((fmt->font->finfo->props.flags & gff_text_mode) != 0)
+		return gfx_puttext_textmode(&spos, fmt, str);
+
+	rc = gfx_set_color(fmt->font->typeface->gc, fmt->color);
 	if (rc != EOK)
 		return rc;
 
+	width = gfx_text_width(fmt->font, str);
+
+	if (fmt->abbreviate && width > fmt->width) {
+		/* Need to append ellipsis */
+		ellipsis = true;
+		rmargin = spos.x + fmt->width - gfx_text_width(fmt->font, "...");
+	} else {
+		ellipsis = false;
+		rmargin = spos.x + width;
+	}
+
+	cpos = spos;
 	cp = str;
 	while (*cp != '\0') {
-		rc = gfx_font_search_glyph(font, cp, &glyph, &stradv);
+		rc = gfx_font_search_glyph(fmt->font, cp, &glyph, &stradv);
 		if (rc != EOK) {
 			++cp;
@@ -255,4 +300,8 @@
 		gfx_glyph_get_metrics(glyph, &gmetrics);
 
+		/* Stop if we would run over the right margin */
+		if (fmt->abbreviate && cpos.x + gmetrics.advance > rmargin)
+			break;
+
 		rc = gfx_glyph_render(glyph, &cpos);
 		if (rc != EOK)
@@ -263,4 +312,43 @@
 	}
 
+	/* Text underlining */
+	if (fmt->underline) {
+		gfx_font_get_metrics(fmt->font, &fmetrics);
+
+		rect.p0.x = spos.x;
+		rect.p0.y = spos.y + fmetrics.underline_y0;
+		rect.p1.x = cpos.x;
+		rect.p1.y = spos.y + fmetrics.underline_y1;
+
+		rc = gfx_fill_rect(fmt->font->typeface->gc, &rect);
+		if (rc != EOK)
+			return rc;
+	}
+
+	/* Render ellipsis, if required */
+	if (ellipsis) {
+		rc = gfx_font_search_glyph(fmt->font, ".", &glyph, &stradv);
+		if (rc != EOK)
+			return EOK;
+
+		gfx_glyph_get_metrics(glyph, &gmetrics);
+
+		rc = gfx_glyph_render(glyph, &cpos);
+		if (rc != EOK)
+			return rc;
+
+		cpos.x += gmetrics.advance;
+
+		rc = gfx_glyph_render(glyph, &cpos);
+		if (rc != EOK)
+			return rc;
+
+		cpos.x += gmetrics.advance;
+
+		rc = gfx_glyph_render(glyph, &cpos);
+		if (rc != EOK)
+			return rc;
+	}
+
 	return EOK;
 }
@@ -268,5 +356,4 @@
 /** Find character position in string by X coordinate.
  *
- * @param font Font
  * @param pos Anchor position
  * @param fmt Text formatting
@@ -280,6 +367,6 @@
  *         offset of the following character.
  */
-size_t gfx_text_find_pos(gfx_font_t *font, gfx_coord2_t *pos,
-    gfx_text_fmt_t *fmt, const char *str, gfx_coord2_t *fpos)
+size_t gfx_text_find_pos(gfx_coord2_t *pos, gfx_text_fmt_t *fmt,
+    const char *str, gfx_coord2_t *fpos)
 {
 	gfx_glyph_metrics_t gmetrics;
@@ -292,8 +379,8 @@
 	errno_t rc;
 
-	gfx_text_start_pos(font, pos, fmt, str, &cpos);
+	gfx_text_start_pos(pos, fmt, str, &cpos);
 
 	/* Text mode */
-	if ((font->finfo->props.flags & gff_text_mode) != 0) {
+	if ((fmt->font->finfo->props.flags & gff_text_mode) != 0) {
 		off = 0;
 		strsize = str_size(str);
@@ -311,5 +398,5 @@
 	off = 0;
 	while (*cp != '\0') {
-		rc = gfx_font_search_glyph(font, cp, &glyph, &stradv);
+		rc = gfx_font_search_glyph(fmt->font, cp, &glyph, &stradv);
 		if (rc != EOK) {
 			++cp;
@@ -337,5 +424,4 @@
  * to the same objects, respectively.
  *
- * @param font Font
  * @param pos Anchor position
  * @param fmt Text formatting
@@ -344,7 +430,6 @@
  * @param cfmt Place to store format for continuation
  */
-void gfx_text_cont(gfx_font_t *font, gfx_coord2_t *pos,
-    gfx_text_fmt_t *fmt, const char *str, gfx_coord2_t *cpos,
-    gfx_text_fmt_t *cfmt)
+void gfx_text_cont(gfx_coord2_t *pos, gfx_text_fmt_t *fmt, const char *str,
+    gfx_coord2_t *cpos, gfx_text_fmt_t *cfmt)
 {
 	gfx_coord2_t spos;
@@ -352,6 +437,6 @@
 
 	/* Continuation should start where the current string ends */
-	gfx_text_start_pos(font, pos, fmt, str, &spos);
-	cpos->x = spos.x + gfx_text_width(font, str);
+	gfx_text_start_pos(pos, fmt, str, &spos);
+	cpos->x = spos.x + gfx_text_width(fmt->font, str);
 	cpos->y = spos.y;
 
@@ -364,4 +449,7 @@
 	tfmt.valign = gfx_valign_baseline;
 
+	/* Remaining available width */
+	tfmt.width = fmt->width - (cpos->x - spos.x);
+
 	*cfmt = tfmt;
 }
@@ -369,5 +457,4 @@
 /** Get text bounding rectangle.
  *
- * @param font Font
  * @param pos Anchor position
  * @param fmt Text formatting
@@ -375,15 +462,19 @@
  * @param rect Place to store bounding rectangle
  */
-void gfx_text_rect(gfx_font_t *font, gfx_coord2_t *pos,
-    gfx_text_fmt_t *fmt, const char *str, gfx_rect_t *rect)
+void gfx_text_rect(gfx_coord2_t *pos, gfx_text_fmt_t *fmt, const char *str,
+    gfx_rect_t *rect)
 {
 	gfx_coord2_t spos;
-
-	gfx_text_start_pos(font, pos, fmt, str, &spos);
+	gfx_coord_t width;
+
+	gfx_text_start_pos(pos, fmt, str, &spos);
+	width = gfx_text_width(fmt->font, str);
+	if (fmt->abbreviate && width > fmt->width)
+		width = fmt->width;
 
 	rect->p0.x = spos.x;
-	rect->p0.y = spos.y - font->metrics.ascent;
-	rect->p1.x = spos.x + gfx_text_width(font, str);
-	rect->p1.y = spos.y + font->metrics.descent + 1;
+	rect->p0.y = spos.y - fmt->font->metrics.ascent;
+	rect->p1.x = spos.x + width;
+	rect->p1.y = spos.y +  fmt->font->metrics.descent + 1;
 }
 
Index: uspace/lib/gfxfont/test/text.c
===================================================================
--- uspace/lib/gfxfont/test/text.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/gfxfont/test/text.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -135,9 +135,10 @@
 
 	gfx_text_fmt_init(&fmt);
+	fmt.font = font;
 	fmt.color = color;
 	pos.x = 0;
 	pos.y = 0;
 
-	rc = gfx_puttext(font, &pos, &fmt, "Hello world!");
+	rc = gfx_puttext(&pos, &fmt, "Hello world!");
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -181,9 +182,10 @@
 
 	gfx_text_fmt_init(&fmt);
+	fmt.font = font;
 	fmt.color = color;
 	pos.x = 0;
 	pos.y = 0;
 
-	rc = gfx_puttext(font, &pos, &fmt, "Hello world!");
+	rc = gfx_puttext(&pos, &fmt, "Hello world!");
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -245,4 +247,5 @@
 
 	gfx_text_fmt_init(&fmt);
+	fmt.font = font;
 	anchor.x = 10;
 	anchor.y = 0;
@@ -250,35 +253,35 @@
 	fpos.x = 9;
 	fpos.y = 0;
-	off = gfx_text_find_pos(font, &anchor, &fmt, "Aii", &fpos);
+	off = gfx_text_find_pos(&anchor, &fmt, "Aii", &fpos);
 	PCUT_ASSERT_INT_EQUALS(0, off);
 
 	fpos.x = 10;
 	fpos.y = 0;
-	off = gfx_text_find_pos(font, &anchor, &fmt, "Aii", &fpos);
+	off = gfx_text_find_pos(&anchor, &fmt, "Aii", &fpos);
 	PCUT_ASSERT_INT_EQUALS(0, off);
 
 	fpos.x = 11;
 	fpos.y = 0;
-	off = gfx_text_find_pos(font, &anchor, &fmt, "Aii", &fpos);
+	off = gfx_text_find_pos(&anchor, &fmt, "Aii", &fpos);
 	PCUT_ASSERT_INT_EQUALS(0, off);
 
 	fpos.x = 19;
 	fpos.y = 0;
-	off = gfx_text_find_pos(font, &anchor, &fmt, "Aii", &fpos);
+	off = gfx_text_find_pos(&anchor, &fmt, "Aii", &fpos);
 	PCUT_ASSERT_INT_EQUALS(1, off);
 
 	fpos.x = 20;
 	fpos.y = 0;
-	off = gfx_text_find_pos(font, &anchor, &fmt, "Aii", &fpos);
+	off = gfx_text_find_pos(&anchor, &fmt, "Aii", &fpos);
 	PCUT_ASSERT_INT_EQUALS(2, off);
 
 	fpos.x = 21;
 	fpos.y = 0;
-	off = gfx_text_find_pos(font, &anchor, &fmt, "Aii", &fpos);
+	off = gfx_text_find_pos(&anchor, &fmt, "Aii", &fpos);
 	PCUT_ASSERT_INT_EQUALS(3, off);
 
 	fpos.x = 22;
 	fpos.y = 0;
-	off = gfx_text_find_pos(font, &anchor, &fmt, "Aii", &fpos);
+	off = gfx_text_find_pos(&anchor, &fmt, "Aii", &fpos);
 	PCUT_ASSERT_INT_EQUALS(3, off);
 
@@ -318,33 +321,34 @@
 	anchor.y = 0;
 	gfx_text_fmt_init(&fmt);
+	fmt.font = font;
 
 	fpos.x = 9;
 	fpos.y = 0;
-	off = gfx_text_find_pos(font, &anchor, &fmt, "Abc", &fpos);
+	off = gfx_text_find_pos(&anchor, &fmt, "Abc", &fpos);
 	PCUT_ASSERT_INT_EQUALS(0, off);
 
 	fpos.x = 10;
 	fpos.y = 0;
-	off = gfx_text_find_pos(font, &anchor, &fmt, "Abc", &fpos);
+	off = gfx_text_find_pos(&anchor, &fmt, "Abc", &fpos);
 	PCUT_ASSERT_INT_EQUALS(0, off);
 
 	fpos.x = 11;
 	fpos.y = 0;
-	off = gfx_text_find_pos(font, &anchor, &fmt, "Abc", &fpos);
+	off = gfx_text_find_pos(&anchor, &fmt, "Abc", &fpos);
 	PCUT_ASSERT_INT_EQUALS(1, off);
 
 	fpos.x = 12;
 	fpos.y = 0;
-	off = gfx_text_find_pos(font, &anchor, &fmt, "Abc", &fpos);
+	off = gfx_text_find_pos(&anchor, &fmt, "Abc", &fpos);
 	PCUT_ASSERT_INT_EQUALS(2, off);
 
 	fpos.x = 13;
 	fpos.y = 0;
-	off = gfx_text_find_pos(font, &anchor, &fmt, "Abc", &fpos);
+	off = gfx_text_find_pos(&anchor, &fmt, "Abc", &fpos);
 	PCUT_ASSERT_INT_EQUALS(3, off);
 
 	fpos.x = 14;
 	fpos.y = 0;
-	off = gfx_text_find_pos(font, &anchor, &fmt, "Abc", &fpos);
+	off = gfx_text_find_pos(&anchor, &fmt, "Abc", &fpos);
 	PCUT_ASSERT_INT_EQUALS(3, off);
 
@@ -385,7 +389,8 @@
 	anchor.y = 20;
 	gfx_text_fmt_init(&fmt);
+	fmt.font = font;
 	fmt.color = color;
 
-	gfx_text_cont(font, &anchor, &fmt, "Abc", &cpos, &cfmt);
+	gfx_text_cont(&anchor, &fmt, "Abc", &cpos, &cfmt);
 
 	PCUT_ASSERT_INT_EQUALS(13, cpos.x);
@@ -431,7 +436,8 @@
 	anchor.y = 20;
 	gfx_text_fmt_init(&fmt);
+	fmt.font = font;
 	fmt.color = color;
 
-	gfx_text_rect(font, &anchor, &fmt, "Abc", &rect);
+	gfx_text_rect(&anchor, &fmt, "Abc", &rect);
 
 	PCUT_ASSERT_INT_EQUALS(10, rect.p0.x);
Index: uspace/lib/gfxfont/test/tpf.c
===================================================================
--- uspace/lib/gfxfont/test/tpf.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/gfxfont/test/tpf.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -73,7 +73,8 @@
 } testgc_bitmap_t;
 
+static const gfx_font_flags_t test_font_flags = gff_bold_italic;
+
 enum {
 	test_font_size = 9,
-	test_font_flags = gff_bold_italic,
 	test_font_ascent = 4,
 	test_font_descent = 3,
Index: uspace/lib/gfximage/include/gfximage/tga.h
===================================================================
--- uspace/lib/gfximage/include/gfximage/tga.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/gfximage/include/gfximage/tga.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -29,5 +29,5 @@
  */
 
-/** @addtogroup draw
+/** @addtogroup libgfximage
  * @{
  */
Index: uspace/lib/gfximage/include/gfximage/tga_gz.h
===================================================================
--- uspace/lib/gfximage/include/gfximage/tga_gz.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/gfximage/include/gfximage/tga_gz.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -29,5 +29,5 @@
  */
 
-/** @addtogroup draw
+/** @addtogroup libgfximage
  * @{
  */
Index: uspace/lib/gfximage/src/tga.c
===================================================================
--- uspace/lib/gfximage/src/tga.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/gfximage/src/tga.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -28,5 +28,5 @@
  */
 
-/** @addtogroup draw
+/** @addtogroup libgfximage
  * @{
  */
Index: uspace/lib/gfximage/src/tga_gz.c
===================================================================
--- uspace/lib/gfximage/src/tga_gz.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/gfximage/src/tga_gz.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -27,5 +27,5 @@
  */
 
-/** @addtogroup draw
+/** @addtogroup libgfximage
  * @{
  */
Index: uspace/lib/hound/src/protocol.c
===================================================================
--- uspace/lib/hound/src/protocol.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/hound/src/protocol.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2012 Jan Vesely
  * All rights reserved.
@@ -84,4 +85,7 @@
 const char *HOUND_SERVICE = "audio/hound";
 
+/** Server object */
+static loc_srv_t *hound_srv;
+
 /**
  * Start a new audio session.
@@ -730,12 +734,23 @@
 errno_t hound_server_register(const char *name, service_id_t *id)
 {
+	errno_t rc;
+
 	if (!name || !id)
 		return EINVAL;
 
-	errno_t ret = loc_server_register(name);
-	if (ret != EOK)
-		return ret;
-
-	return loc_service_register(HOUND_SERVICE, id);
+	if (hound_srv != NULL)
+		return EBUSY;
+
+	rc = loc_server_register(name, &hound_srv);
+	if (rc != EOK)
+		return rc;
+
+	rc = loc_service_register(hound_srv, HOUND_SERVICE, id);
+	if (rc != EOK) {
+		loc_server_unregister(hound_srv);
+		return rc;
+	}
+
+	return EOK;
 }
 
@@ -746,5 +761,6 @@
 void hound_server_unregister(service_id_t id)
 {
-	loc_service_unregister(id);
+	loc_service_unregister(hound_srv, id);
+	loc_server_unregister(hound_srv);
 }
 
Index: uspace/lib/inet/meson.build
===================================================================
--- uspace/lib/inet/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/inet/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 #
-# Copyright (c) 2021 Jiri Svoboda
+# Copyright (c) 2024 Jiri Svoboda
 # All rights reserved.
 #
@@ -46,4 +46,5 @@
 
 test_src = files(
+	'test/addr.c',
 	'test/eth_addr.c',
 	'test/main.c',
Index: uspace/lib/inet/src/addr.c
===================================================================
--- uspace/lib/inet/src/addr.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/inet/src/addr.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2013 Martin Decky
  * All rights reserved.
@@ -379,6 +379,9 @@
 				wildcard_size = 16 - i;
 				cur = gend + 2;
+				continue;
 			}
 		}
+
+		cur = gend + 1;
 	}
 
Index: uspace/lib/inet/src/iplink_srv.c
===================================================================
--- uspace/lib/inet/src/iplink_srv.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/inet/src/iplink_srv.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -74,6 +74,9 @@
 
 	rc = async_data_read_finalize(&call, &mac, size);
-	if (rc != EOK)
-		async_answer_0(&call, rc);
+	if (rc != EOK) {
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
 
 	async_answer_0(icall, rc);
@@ -94,4 +97,5 @@
 	rc = srv->ops->set_mac48(srv, &mac);
 	if (rc != EOK) {
+		async_answer_0(&call, rc);
 		async_answer_0(icall, rc);
 		return;
@@ -99,6 +103,9 @@
 
 	rc = async_data_read_finalize(&call, &mac, sizeof(eth_addr_t));
-	if (rc != EOK)
-		async_answer_0(&call, rc);
+	if (rc != EOK) {
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
 
 	async_answer_0(icall, rc);
@@ -109,4 +116,5 @@
 	ipc_call_t call;
 	size_t size;
+
 	if (!async_data_write_receive(&call, &size)) {
 		async_answer_0(&call, EREFUSED);
@@ -126,4 +134,5 @@
 		async_answer_0(&call, rc);
 		async_answer_0(icall, rc);
+		return;
 	}
 
@@ -136,4 +145,5 @@
 	ipc_call_t call;
 	size_t size;
+
 	if (!async_data_write_receive(&call, &size)) {
 		async_answer_0(&call, EREFUSED);
@@ -153,4 +163,5 @@
 		async_answer_0(&call, rc);
 		async_answer_0(icall, rc);
+		return;
 	}
 
@@ -181,7 +192,7 @@
 {
 	iplink_sdu6_t sdu;
-
-	ipc_call_t call;
-	size_t size;
+	ipc_call_t call;
+	size_t size;
+
 	if (!async_data_write_receive(&call, &size)) {
 		async_answer_0(&call, EREFUSED);
Index: uspace/lib/inet/src/udp.c
===================================================================
--- uspace/lib/inet/src/udp.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/inet/src/udp.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -256,7 +256,14 @@
 {
 	async_exch_t *exch;
+	inet_ep_t ddest;
 
 	exch = async_exchange_begin(assoc->udp->sess);
 	aid_t req = async_send_1(exch, UDP_ASSOC_SEND_MSG, assoc->id, NULL);
+
+	/* If dest is null, use default destination */
+	if (dest == NULL) {
+		inet_ep_init(&ddest);
+		dest = &ddest;
+	}
 
 	errno_t rc = async_data_write_start(exch, (void *)dest,
Index: uspace/lib/inet/test/addr.c
===================================================================
--- uspace/lib/inet/test/addr.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/inet/test/addr.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,199 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <inet/addr.h>
+#include <pcut/pcut.h>
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(addr);
+
+/** Test inet_addr_parse() with unabbreviated address */
+PCUT_TEST(inet_addr_parse_full)
+{
+	errno_t rc;
+	inet_addr_t addr;
+	char *endptr;
+
+	rc = inet_addr_parse("1122:3344:5566:7788:99aa:bbcc:ddee:ff00/", &addr,
+	    &endptr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS('/', *endptr);
+
+	PCUT_ASSERT_INT_EQUALS(ip_v6, addr.version);
+
+	PCUT_ASSERT_INT_EQUALS(0x11, addr.addr6[0]);
+	PCUT_ASSERT_INT_EQUALS(0x22, addr.addr6[1]);
+	PCUT_ASSERT_INT_EQUALS(0x33, addr.addr6[2]);
+	PCUT_ASSERT_INT_EQUALS(0x44, addr.addr6[3]);
+	PCUT_ASSERT_INT_EQUALS(0x55, addr.addr6[4]);
+	PCUT_ASSERT_INT_EQUALS(0x66, addr.addr6[5]);
+	PCUT_ASSERT_INT_EQUALS(0x77, addr.addr6[6]);
+	PCUT_ASSERT_INT_EQUALS(0x88, addr.addr6[7]);
+
+	PCUT_ASSERT_INT_EQUALS(0x99, addr.addr6[8]);
+	PCUT_ASSERT_INT_EQUALS(0xaa, addr.addr6[9]);
+	PCUT_ASSERT_INT_EQUALS(0xbb, addr.addr6[10]);
+	PCUT_ASSERT_INT_EQUALS(0xcc, addr.addr6[11]);
+	PCUT_ASSERT_INT_EQUALS(0xdd, addr.addr6[12]);
+	PCUT_ASSERT_INT_EQUALS(0xee, addr.addr6[13]);
+	PCUT_ASSERT_INT_EQUALS(0xff, addr.addr6[14]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[15]);
+}
+
+/** Test inet_addr_parse() with short groups (< 0x1000) */
+PCUT_TEST(inet_addr_parse_shortgr)
+{
+	errno_t rc;
+	inet_addr_t addr;
+	char *endptr;
+
+	rc = inet_addr_parse("1:22:333:4444:5:66:777:8888/", &addr,
+	    &endptr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS('/', *endptr);
+
+	PCUT_ASSERT_INT_EQUALS(ip_v6, addr.version);
+
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[0]);
+	PCUT_ASSERT_INT_EQUALS(0x01, addr.addr6[1]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[2]);
+	PCUT_ASSERT_INT_EQUALS(0x22, addr.addr6[3]);
+	PCUT_ASSERT_INT_EQUALS(0x03, addr.addr6[4]);
+	PCUT_ASSERT_INT_EQUALS(0x33, addr.addr6[5]);
+	PCUT_ASSERT_INT_EQUALS(0x44, addr.addr6[6]);
+	PCUT_ASSERT_INT_EQUALS(0x44, addr.addr6[7]);
+
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[8]);
+	PCUT_ASSERT_INT_EQUALS(0x05, addr.addr6[9]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[10]);
+	PCUT_ASSERT_INT_EQUALS(0x66, addr.addr6[11]);
+	PCUT_ASSERT_INT_EQUALS(0x07, addr.addr6[12]);
+	PCUT_ASSERT_INT_EQUALS(0x77, addr.addr6[13]);
+	PCUT_ASSERT_INT_EQUALS(0x88, addr.addr6[14]);
+	PCUT_ASSERT_INT_EQUALS(0x88, addr.addr6[15]);
+}
+
+/** Test inet_addr_parse() with wildcard at the beginning */
+PCUT_TEST(inet_addr_parse_wcbegin)
+{
+	errno_t rc;
+	inet_addr_t addr;
+	char *endptr;
+
+	rc = inet_addr_parse("::1234/", &addr, &endptr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS('/', *endptr);
+
+	PCUT_ASSERT_INT_EQUALS(ip_v6, addr.version);
+
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[0]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[1]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[2]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[3]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[4]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[5]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[6]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[7]);
+
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[8]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[9]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[10]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[11]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[12]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[13]);
+	PCUT_ASSERT_INT_EQUALS(0x12, addr.addr6[14]);
+	PCUT_ASSERT_INT_EQUALS(0x34, addr.addr6[15]);
+}
+
+/** Test inet_addr_parse() with wildcard in the middle */
+PCUT_TEST(inet_addr_parse_wcmiddle)
+{
+	errno_t rc;
+	inet_addr_t addr;
+	char *endptr;
+
+	rc = inet_addr_parse("1122:3344::5566/", &addr, &endptr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS('/', *endptr);
+
+	PCUT_ASSERT_INT_EQUALS(ip_v6, addr.version);
+
+	PCUT_ASSERT_INT_EQUALS(0x11, addr.addr6[0]);
+	PCUT_ASSERT_INT_EQUALS(0x22, addr.addr6[1]);
+	PCUT_ASSERT_INT_EQUALS(0x33, addr.addr6[2]);
+	PCUT_ASSERT_INT_EQUALS(0x44, addr.addr6[3]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[4]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[5]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[6]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[7]);
+
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[8]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[9]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[10]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[11]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[12]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[13]);
+	PCUT_ASSERT_INT_EQUALS(0x55, addr.addr6[14]);
+	PCUT_ASSERT_INT_EQUALS(0x66, addr.addr6[15]);
+}
+
+/** Test inet_addr_parse() with wildcard at the end */
+PCUT_TEST(inet_addr_parse_wcend)
+{
+	errno_t rc;
+	inet_addr_t addr;
+	char *endptr;
+
+	rc = inet_addr_parse("1234:5678::/", &addr, &endptr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS('/', *endptr);
+
+	PCUT_ASSERT_INT_EQUALS(ip_v6, addr.version);
+
+	PCUT_ASSERT_INT_EQUALS(0x12, addr.addr6[0]);
+	PCUT_ASSERT_INT_EQUALS(0x34, addr.addr6[1]);
+	PCUT_ASSERT_INT_EQUALS(0x56, addr.addr6[2]);
+	PCUT_ASSERT_INT_EQUALS(0x78, addr.addr6[3]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[4]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[5]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[6]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[7]);
+
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[8]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[9]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[10]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[11]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[12]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[13]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[14]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[15]);
+}
+
+PCUT_EXPORT(addr);
Index: uspace/lib/inet/test/main.c
===================================================================
--- uspace/lib/inet/test/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/inet/test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -31,4 +31,5 @@
 PCUT_INIT;
 
+PCUT_IMPORT(addr);
 PCUT_IMPORT(eth_addr);
 
Index: uspace/lib/input/doc/doxygroups.h
===================================================================
--- uspace/lib/input/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/input/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,3 @@
+/** @addtogroup libinput libinput
+ * @ingroup libs
+ */
Index: uspace/lib/input/include/io/input.h
===================================================================
--- uspace/lib/input/include/io/input.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/input/include/io/input.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,68 @@
+/*
+ * 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 libinput
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBINPUT_INPUT_H_
+#define _LIBINPUT_INPUT_H_
+
+#include <async.h>
+#include <io/kbd_event.h>
+
+struct input_ev_ops;
+
+typedef struct {
+	async_sess_t *sess;
+	struct input_ev_ops *ev_ops;
+	void *user;
+} input_t;
+
+typedef struct input_ev_ops {
+	errno_t (*active)(input_t *);
+	errno_t (*deactive)(input_t *);
+	errno_t (*key)(input_t *, unsigned, kbd_event_type_t, keycode_t,
+	    keymod_t, char32_t);
+	errno_t (*move)(input_t *, unsigned, int, int);
+	errno_t (*abs_move)(input_t *, unsigned, unsigned, unsigned, unsigned,
+	    unsigned);
+	errno_t (*button)(input_t *, unsigned, int, int);
+	errno_t (*dclick)(input_t *, unsigned, int);
+} input_ev_ops_t;
+
+extern errno_t input_open(async_sess_t *, input_ev_ops_t *, void *, input_t **);
+extern void input_close(input_t *);
+extern errno_t input_activate(input_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/input/include/io/kbd_event.h
===================================================================
--- uspace/lib/input/include/io/kbd_event.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/input/include/io/kbd_event.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libinput
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBINPUT_IO_KBD_EVENT_H_
+#define _LIBINPUT_IO_KBD_EVENT_H_
+
+#include <adt/list.h>
+#include <inttypes.h>
+#include <io/keycode.h>
+#include <types/common.h>
+
+typedef enum {
+	KEY_PRESS,
+	KEY_RELEASE
+} kbd_event_type_t;
+
+/** Console event structure. */
+typedef struct {
+	/** List handle */
+	link_t link;
+
+	/** Keyboard device ID */
+	sysarg_t kbd_id;
+
+	/** Press or release event. */
+	kbd_event_type_t type;
+
+	/** Keycode of the key that was pressed or released. */
+	keycode_t key;
+
+	/** Bitmask of modifiers held. */
+	keymod_t mods;
+
+	/** The character that was generated or '\0' for none. */
+	char32_t c;
+} kbd_event_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/input/include/io/keycode.h
===================================================================
--- uspace/lib/input/include/io/keycode.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/input/include/io/keycode.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2009 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 libinput
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBINPUT_IO_KEYCODE_H_
+#define _LIBINPUT_IO_KEYCODE_H_
+
+/** Keycode definitions.
+ *
+ * A keycode identifies a key by its position on the keyboard, rather
+ * than by its label. For human readability, key positions are noted
+ * with the key label on a keyboard with US layout. This label has
+ * nothing to do with the character, that the key produces
+ * -- this is determined by the keymap.
+ *
+ * The keyboard model reflects a standard PC keyboard layout.
+ * Non-standard keyboards need to be mapped to this model in some
+ * logical way. Scancodes are mapped to keycodes with a scanmap.
+ *
+ * For easier mapping to the model and to emphasize the nature of keycodes,
+ * they really are organized here by position, rather than by label.
+ */
+typedef enum {
+
+	/* Main block row 1 */
+
+	KC_BACKTICK = 1,
+
+	KC_1,
+	KC_2,
+	KC_3,
+	KC_4,
+	KC_5,
+	KC_6,
+	KC_7,
+	KC_8,
+	KC_9,
+	KC_0,
+
+	KC_MINUS,
+	KC_EQUALS,
+	KC_BACKSPACE,
+
+	/* Main block row 2 */
+
+	KC_TAB,
+
+	KC_Q,
+	KC_W,
+	KC_E,
+	KC_R,
+	KC_T,
+	KC_Y,
+	KC_U,
+	KC_I,
+	KC_O,
+	KC_P,
+
+	KC_LBRACKET,
+	KC_RBRACKET,
+
+	/* Main block row 3 */
+
+	KC_CAPS_LOCK,
+
+	KC_A,
+	KC_S,
+	KC_D,
+	KC_F,
+	KC_G,
+	KC_H,
+	KC_J,
+	KC_K,
+	KC_L,
+
+	KC_SEMICOLON,
+	KC_QUOTE,
+	KC_BACKSLASH,
+	KC_HASH,
+
+	KC_ENTER,
+
+	/* Main block row 4 */
+
+	KC_LSHIFT,
+
+	KC_Z,
+	KC_X,
+	KC_C,
+	KC_V,
+	KC_B,
+	KC_N,
+	KC_M,
+
+	KC_COMMA,
+	KC_PERIOD,
+	KC_SLASH,
+
+	KC_RSHIFT,
+
+	/* Main block row 5 */
+
+	KC_LCTRL,
+	KC_LALT,
+	KC_SPACE,
+	KC_RALT,
+	KC_RCTRL,
+
+	/* Function keys block */
+
+	KC_ESCAPE,
+
+	KC_F1,
+	KC_F2,
+	KC_F3,
+	KC_F4,
+	KC_F5,
+	KC_F6,
+	KC_F7,
+	KC_F8,
+	KC_F9,
+	KC_F10,
+	KC_F11,
+	KC_F12,
+
+	KC_PRTSCR,
+	KC_SYSREQ,
+	KC_SCROLL_LOCK,
+	KC_PAUSE,
+	KC_BREAK,
+
+	/* Cursor keys block */
+
+	KC_INSERT,
+	KC_HOME,
+	KC_PAGE_UP,
+
+	KC_DELETE,
+	KC_END,
+	KC_PAGE_DOWN,
+
+	KC_UP,
+	KC_LEFT,
+	KC_DOWN,
+	KC_RIGHT,
+
+	/* Numeric block */
+
+	KC_NUM_LOCK,
+	KC_NSLASH,
+	KC_NTIMES,
+	KC_NMINUS,
+
+	KC_NPLUS,
+	KC_NENTER,
+
+	KC_N7,
+	KC_N8,
+	KC_N9,
+
+	KC_N4,
+	KC_N5,
+	KC_N6,
+
+	KC_N1,
+	KC_N2,
+	KC_N3,
+
+	KC_N0,
+	KC_NPERIOD
+
+} keycode_t;
+
+typedef enum {
+	KM_LSHIFT      = 0x001,
+	KM_RSHIFT      = 0x002,
+	KM_LCTRL       = 0x004,
+	KM_RCTRL       = 0x008,
+	KM_LALT        = 0x010,
+	KM_RALT        = 0x020,
+	KM_CAPS_LOCK   = 0x040,
+	KM_NUM_LOCK    = 0x080,
+	KM_SCROLL_LOCK = 0x100,
+
+	KM_SHIFT       = KM_LSHIFT | KM_RSHIFT,
+	KM_CTRL        = KM_LCTRL | KM_RCTRL,
+	KM_ALT         = KM_LALT | KM_RALT
+} keymod_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/input/include/io/pos_event.h
===================================================================
--- uspace/lib/input/include/io/pos_event.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/input/include/io/pos_event.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2021 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 libinput
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBINPUT_IO_POS_EVENT_H_
+#define _LIBINPUT_IO_POS_EVENT_H_
+
+#include <types/common.h>
+
+typedef enum {
+	/** Position update */
+	POS_UPDATE,
+	/** Button press */
+	POS_PRESS,
+	/** Button release */
+	POS_RELEASE,
+	/** Double click */
+	POS_DCLICK
+} pos_event_type_t;
+
+/** Positioning device event */
+typedef struct {
+	sysarg_t pos_id;
+	pos_event_type_t type;
+	sysarg_t btn_num;
+	sysarg_t hpos;
+	sysarg_t vpos;
+} pos_event_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/input/include/ipc/input.h
===================================================================
--- uspace/lib/input/include/ipc/input.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/input/include/ipc/input.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,58 @@
+/*
+ * 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 libinput
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBINPUT_IPC_INPUT_H_
+#define _LIBINPUT_IPC_INPUT_H_
+
+#include <ipc/common.h>
+
+typedef enum {
+	INPUT_ACTIVATE = IPC_FIRST_USER_METHOD
+} input_request_t;
+
+typedef enum {
+	INPUT_EVENT_ACTIVE = IPC_FIRST_USER_METHOD,
+	INPUT_EVENT_DEACTIVE,
+	INPUT_EVENT_KEY,
+	INPUT_EVENT_MOVE,
+	INPUT_EVENT_ABS_MOVE,
+	INPUT_EVENT_BUTTON,
+	INPUT_EVENT_DCLICK
+} input_notif_t;
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/lib/input/include/ipc/kbdev.h
===================================================================
--- uspace/lib/input/include/ipc/kbdev.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/input/include/ipc/kbdev.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,55 @@
+/*
+ * 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 libinput
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBINPUT_IPC_KBDEV_H_
+#define _LIBINPUT_IPC_KBDEV_H_
+
+#include <ipc/common.h>
+#include <ipc/dev_iface.h>
+
+typedef enum {
+	KBDEV_YIELD = DEV_FIRST_CUSTOM_METHOD,
+	KBDEV_RECLAIM,
+	KBDEV_SET_IND
+} kbdev_request_t;
+
+typedef enum {
+	KBDEV_EVENT = IPC_FIRST_USER_METHOD
+} kbdev_notif_t;
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/lib/input/include/ipc/mouseev.h
===================================================================
--- uspace/lib/input/include/ipc/mouseev.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/input/include/ipc/mouseev.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,57 @@
+/*
+ * 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 libinput
+ * @brief
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBINPUT_IPC_MOUSEEV_H_
+#define _LIBINPUT_IPC_MOUSEEV_H_
+
+#include <ipc/common.h>
+#include <ipc/dev_iface.h>
+
+typedef enum {
+	MOUSEEV_YIELD = DEV_FIRST_CUSTOM_METHOD,
+	MOUSEEV_RECLAIM
+} mouseev_request_t;
+
+typedef enum {
+	MOUSEEV_MOVE_EVENT = IPC_FIRST_USER_METHOD,
+	MOUSEEV_ABS_MOVE_EVENT,
+	MOUSEEV_BUTTON_EVENT
+} mouseev_notif_t;
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/lib/input/meson.build
===================================================================
--- uspace/lib/input/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/input/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,31 @@
+#
+# 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.
+#
+
+src = files(
+	'src/input.c',
+)
Index: uspace/lib/input/src/input.c
===================================================================
--- uspace/lib/input/src/input.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/input/src/input.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,229 @@
+/*
+ * 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 libinput
+ * @{
+ */
+/**
+ * @file
+ * @brief Input protocol client stub
+ */
+
+#include <async.h>
+#include <assert.h>
+#include <errno.h>
+#include <io/kbd_event.h>
+#include <io/input.h>
+#include <ipc/input.h>
+#include <stdlib.h>
+
+static void input_cb_conn(ipc_call_t *icall, void *arg);
+
+errno_t input_open(async_sess_t *sess, input_ev_ops_t *ev_ops,
+    void *arg, input_t **rinput)
+{
+	input_t *input = calloc(1, sizeof(input_t));
+	if (input == NULL)
+		return ENOMEM;
+
+	input->sess = sess;
+	input->ev_ops = ev_ops;
+	input->user = arg;
+
+	async_exch_t *exch = async_exchange_begin(sess);
+
+	port_id_t port;
+	errno_t rc = async_create_callback_port(exch, INTERFACE_INPUT_CB, 0, 0,
+	    input_cb_conn, input, &port);
+
+	async_exchange_end(exch);
+
+	if (rc != EOK)
+		goto error;
+
+	*rinput = input;
+	return EOK;
+
+error:
+	if (input != NULL)
+		free(input);
+
+	return rc;
+}
+
+void input_close(input_t *input)
+{
+	/* XXX Synchronize with input_cb_conn */
+	free(input);
+}
+
+errno_t input_activate(input_t *input)
+{
+	async_exch_t *exch = async_exchange_begin(input->sess);
+	errno_t rc = async_req_0_0(exch, INPUT_ACTIVATE);
+	async_exchange_end(exch);
+
+	return rc;
+}
+
+static void input_ev_active(input_t *input, ipc_call_t *call)
+{
+	errno_t rc = input->ev_ops->active(input);
+	async_answer_0(call, rc);
+}
+
+static void input_ev_deactive(input_t *input, ipc_call_t *call)
+{
+	errno_t rc = input->ev_ops->deactive(input);
+	async_answer_0(call, rc);
+}
+
+static void input_ev_key(input_t *input, ipc_call_t *call)
+{
+	unsigned kbd_id;
+	kbd_event_type_t type;
+	keycode_t key;
+	keymod_t mods;
+	char32_t c;
+	errno_t rc;
+
+	kbd_id = ipc_get_arg1(call);
+	type = ipc_get_arg2(call);
+	key = ipc_get_arg3(call);
+	mods = ipc_get_arg4(call);
+	c = ipc_get_arg5(call);
+
+	rc = input->ev_ops->key(input, kbd_id, type, key, mods, c);
+	async_answer_0(call, rc);
+}
+
+static void input_ev_move(input_t *input, ipc_call_t *call)
+{
+	unsigned pos_id;
+	int dx;
+	int dy;
+	errno_t rc;
+
+	pos_id = ipc_get_arg1(call);
+	dx = ipc_get_arg2(call);
+	dy = ipc_get_arg3(call);
+
+	rc = input->ev_ops->move(input, pos_id, dx, dy);
+	async_answer_0(call, rc);
+}
+
+static void input_ev_abs_move(input_t *input, ipc_call_t *call)
+{
+	unsigned pos_id;
+	unsigned x;
+	unsigned y;
+	unsigned max_x;
+	unsigned max_y;
+	errno_t rc;
+
+	pos_id = ipc_get_arg1(call);
+	x = ipc_get_arg2(call);
+	y = ipc_get_arg3(call);
+	max_x = ipc_get_arg4(call);
+	max_y = ipc_get_arg5(call);
+
+	rc = input->ev_ops->abs_move(input, pos_id, x, y, max_x, max_y);
+	async_answer_0(call, rc);
+}
+
+static void input_ev_button(input_t *input, ipc_call_t *call)
+{
+	unsigned pos_id;
+	int bnum;
+	int press;
+	errno_t rc;
+
+	pos_id = ipc_get_arg1(call);
+	bnum = ipc_get_arg2(call);
+	press = ipc_get_arg3(call);
+
+	rc = input->ev_ops->button(input, pos_id, bnum, press);
+	async_answer_0(call, rc);
+}
+
+static void input_ev_dclick(input_t *input, ipc_call_t *call)
+{
+	unsigned pos_id;
+	int bnum;
+	errno_t rc;
+
+	pos_id = ipc_get_arg1(call);
+	bnum = ipc_get_arg2(call);
+
+	rc = input->ev_ops->dclick(input, pos_id, bnum);
+	async_answer_0(call, rc);
+}
+
+static void input_cb_conn(ipc_call_t *icall, void *arg)
+{
+	input_t *input = (input_t *) arg;
+
+	while (true) {
+		ipc_call_t call;
+		async_get_call(&call);
+
+		if (!ipc_get_imethod(&call)) {
+			async_answer_0(&call, EOK);
+			return;
+		}
+
+		switch (ipc_get_imethod(&call)) {
+		case INPUT_EVENT_ACTIVE:
+			input_ev_active(input, &call);
+			break;
+		case INPUT_EVENT_DEACTIVE:
+			input_ev_deactive(input, &call);
+			break;
+		case INPUT_EVENT_KEY:
+			input_ev_key(input, &call);
+			break;
+		case INPUT_EVENT_MOVE:
+			input_ev_move(input, &call);
+			break;
+		case INPUT_EVENT_ABS_MOVE:
+			input_ev_abs_move(input, &call);
+			break;
+		case INPUT_EVENT_BUTTON:
+			input_ev_button(input, &call);
+			break;
+		case INPUT_EVENT_DCLICK:
+			input_ev_dclick(input, &call);
+			break;
+		default:
+			async_answer_0(&call, ENOTSUP);
+		}
+	}
+}
+
+/** @}
+ */
Index: uspace/lib/ipcgfx/src/server.c
===================================================================
--- uspace/lib/ipcgfx/src/server.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ipcgfx/src/server.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -151,4 +151,5 @@
 
 	if (!async_share_out_receive(&call, &size, &flags)) {
+		async_answer_0(&call, EINVAL);
 		async_answer_0(icall, EINVAL);
 		return;
@@ -157,4 +158,5 @@
 	/* Check size */
 	if (size != PAGES2SIZE(SIZE2PAGES(dim.x * dim.y * sizeof(uint32_t)))) {
+		async_answer_0(&call, EINVAL);
 		async_answer_0(icall, EINVAL);
 		return;
@@ -163,4 +165,5 @@
 	rc = async_share_out_finalize(&call, &pixels);
 	if (rc != EOK || pixels == AS_MAP_FAILED) {
+		async_answer_0(&call, ENOMEM);
 		async_answer_0(icall, ENOMEM);
 		return;
Index: uspace/lib/ipcgfx/test/ipcgfx.c
===================================================================
--- uspace/lib/ipcgfx/test/ipcgfx.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ipcgfx/test/ipcgfx.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -120,12 +120,13 @@
 	async_sess_t *sess;
 	ipc_gc_t *ipcgc;
-
-	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ipcgfx_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ipcgfx_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ipcgfx_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ipcgfx_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -156,6 +157,7 @@
 	async_hangup(sess);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -170,12 +172,13 @@
 	async_sess_t *sess;
 	ipc_gc_t *ipcgc;
-
-	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ipcgfx_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ipcgfx_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ipcgfx_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ipcgfx_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -207,6 +210,7 @@
 	async_hangup(sess);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -220,12 +224,13 @@
 	async_sess_t *sess;
 	ipc_gc_t *ipcgc;
-
-	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ipcgfx_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ipcgfx_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ipcgfx_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ipcgfx_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -250,6 +255,7 @@
 	async_hangup(sess);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -264,12 +270,13 @@
 	async_sess_t *sess;
 	ipc_gc_t *ipcgc;
-
-	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ipcgfx_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ipcgfx_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ipcgfx_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ipcgfx_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -300,6 +307,7 @@
 	async_hangup(sess);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -314,12 +322,13 @@
 	async_sess_t *sess;
 	ipc_gc_t *ipcgc;
-
-	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ipcgfx_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ipcgfx_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ipcgfx_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ipcgfx_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -350,6 +359,7 @@
 	async_hangup(sess);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -364,12 +374,13 @@
 	async_sess_t *sess;
 	ipc_gc_t *ipcgc;
-
-	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ipcgfx_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ipcgfx_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ipcgfx_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ipcgfx_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -400,6 +411,7 @@
 	async_hangup(sess);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -414,12 +426,13 @@
 	async_sess_t *sess;
 	ipc_gc_t *ipcgc;
-
-	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ipcgfx_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ipcgfx_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ipcgfx_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ipcgfx_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -450,6 +463,7 @@
 	async_hangup(sess);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -463,12 +477,13 @@
 	async_sess_t *sess;
 	ipc_gc_t *ipcgc;
-
-	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ipcgfx_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ipcgfx_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ipcgfx_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ipcgfx_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -491,6 +506,7 @@
 	async_hangup(sess);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -504,12 +520,13 @@
 	async_sess_t *sess;
 	ipc_gc_t *ipcgc;
-
-	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ipcgfx_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ipcgfx_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ipcgfx_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ipcgfx_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -532,6 +549,7 @@
 	async_hangup(sess);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -547,12 +565,13 @@
 	async_sess_t *sess;
 	ipc_gc_t *ipcgc;
-
-	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ipcgfx_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ipcgfx_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ipcgfx_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ipcgfx_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -591,6 +610,7 @@
 	async_hangup(sess);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -606,12 +626,13 @@
 	async_sess_t *sess;
 	ipc_gc_t *ipcgc;
-
-	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ipcgfx_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ipcgfx_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ipcgfx_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ipcgfx_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -655,6 +676,7 @@
 	async_hangup(sess);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -670,12 +692,13 @@
 	async_sess_t *sess;
 	ipc_gc_t *ipcgc;
-
-	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ipcgfx_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ipcgfx_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ipcgfx_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ipcgfx_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -708,6 +731,7 @@
 	async_hangup(sess);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -723,12 +747,13 @@
 	async_sess_t *sess;
 	ipc_gc_t *ipcgc;
-
-	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ipcgfx_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ipcgfx_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ipcgfx_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ipcgfx_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -768,6 +793,7 @@
 	async_hangup(sess);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -783,12 +809,13 @@
 	async_sess_t *sess;
 	ipc_gc_t *ipcgc;
-
-	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ipcgfx_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ipcgfx_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ipcgfx_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ipcgfx_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -833,6 +860,7 @@
 	async_hangup(sess);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -850,12 +878,13 @@
 	async_sess_t *sess;
 	ipc_gc_t *ipcgc;
-
-	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ipcgfx_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ipcgfx_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ipcgfx_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ipcgfx_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -901,6 +930,7 @@
 	async_hangup(sess);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -918,12 +948,13 @@
 	async_sess_t *sess;
 	ipc_gc_t *ipcgc;
-
-	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ipcgfx_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ipcgfx_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ipcgfx_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ipcgfx_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -969,6 +1000,7 @@
 	async_hangup(sess);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
@@ -985,12 +1017,13 @@
 	async_sess_t *sess;
 	ipc_gc_t *ipcgc;
-
-	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
-
-	// FIXME This causes this test to be non-reentrant!
-	rc = loc_server_register(test_ipcgfx_server);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = loc_service_register(test_ipcgfx_svc, &sid);
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_ipcgc_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_ipcgfx_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_ipcgfx_svc, &sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -1029,6 +1062,7 @@
 	async_hangup(sess);
 
-	rc = loc_service_unregister(sid);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
 }
 
Index: uspace/lib/ipctest/doc/doxygroups.h
===================================================================
--- uspace/lib/ipctest/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ipctest/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,3 @@
+/** @addtogroup libipctest libipctest
+ * @ingroup libs
+ */
Index: uspace/lib/ipctest/include/ipc/ipc_test.h
===================================================================
--- uspace/lib/ipctest/include/ipc/ipc_test.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ipctest/include/ipc/ipc_test.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,54 @@
+/*
+ * 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 libipctest
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBIPCTEST_IPC_IPC_TEST_H_
+#define _LIBIPCTEST_IPC_IPC_TEST_H_
+
+#include <ipc/common.h>
+
+typedef enum {
+	IPC_TEST_PING = IPC_FIRST_USER_METHOD,
+	IPC_TEST_GET_RO_AREA_SIZE,
+	IPC_TEST_GET_RW_AREA_SIZE,
+	IPC_TEST_SHARE_IN_RO,
+	IPC_TEST_SHARE_IN_RW,
+	IPC_TEST_SET_RW_BUF_SIZE,
+	IPC_TEST_READ,
+	IPC_TEST_WRITE
+} ipc_test_request_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ipctest/include/ipc_test.h
===================================================================
--- uspace/lib/ipctest/include/ipc_test.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ipctest/include/ipc_test.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,59 @@
+/*
+ * 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 libipctest
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBIPCTEST_H_
+#define _LIBIPCTEST_H_
+
+#include <async.h>
+#include <errno.h>
+
+typedef struct {
+	async_sess_t *sess;
+} ipc_test_t;
+
+extern errno_t ipc_test_create(ipc_test_t **);
+extern void ipc_test_destroy(ipc_test_t *);
+extern errno_t ipc_test_ping(ipc_test_t *);
+extern errno_t ipc_test_get_ro_area_size(ipc_test_t *, size_t *);
+extern errno_t ipc_test_get_rw_area_size(ipc_test_t *, size_t *);
+extern errno_t ipc_test_share_in_ro(ipc_test_t *, size_t, const void **);
+extern errno_t ipc_test_share_in_rw(ipc_test_t *, size_t, void **);
+extern errno_t ipc_test_set_rw_buf_size(ipc_test_t *, size_t);
+extern errno_t ipc_test_read(ipc_test_t *, void *, size_t);
+extern errno_t ipc_test_write(ipc_test_t *, const void *, size_t);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ipctest/meson.build
===================================================================
--- uspace/lib/ipctest/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ipctest/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,29 @@
+#
+# 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.
+#
+
+src = files('src/ipc_test.c')
Index: uspace/lib/ipctest/src/ipc_test.c
===================================================================
--- uspace/lib/ipctest/src/ipc_test.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ipctest/src/ipc_test.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,303 @@
+/*
+ * 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 libc
+ * @{
+ */
+/** @file IPC test service API
+ */
+
+#include <abi/ipc/interfaces.h>
+#include <as.h>
+#include <errno.h>
+#include <ipc/services.h>
+#include <ipc/ipc_test.h>
+#include <loc.h>
+#include <stdlib.h>
+#include <ipc_test.h>
+
+/** Create IPC test service session.
+ *
+ * @param rvol Place to store pointer to volume service session.
+ * @return     EOK on success, ENOMEM if out of memory, EIO if service
+ *             cannot be contacted.
+ */
+errno_t ipc_test_create(ipc_test_t **rtest)
+{
+	ipc_test_t *test;
+	service_id_t test_svcid;
+	errno_t rc;
+
+	test = calloc(1, sizeof(ipc_test_t));
+	if (test == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	rc = loc_service_get_id(SERVICE_NAME_IPC_TEST, &test_svcid, 0);
+	if (rc != EOK) {
+		rc = ENOENT;
+		goto error;
+	}
+
+	test->sess = loc_service_connect(test_svcid, INTERFACE_IPC_TEST, 0);
+	if (test->sess == NULL) {
+		rc = EIO;
+		goto error;
+	}
+
+	*rtest = test;
+	return EOK;
+error:
+	free(test);
+	return rc;
+}
+
+/** Destroy IPC test service session.
+ *
+ * @param test IPC test service session
+ */
+void ipc_test_destroy(ipc_test_t *test)
+{
+	if (test == NULL)
+		return;
+
+	async_hangup(test->sess);
+	free(test);
+}
+
+/** Simple ping.
+ *
+ * @param test IPC test service
+ * @return EOK on success or an error code
+ */
+errno_t ipc_test_ping(ipc_test_t *test)
+{
+	async_exch_t *exch;
+	errno_t retval;
+
+	exch = async_exchange_begin(test->sess);
+	retval = async_req_0_0(exch, IPC_TEST_PING);
+	async_exchange_end(exch);
+
+	if (retval != EOK)
+		return retval;
+
+	return EOK;
+}
+
+/** Get size of shared read-only memory area.
+ *
+ * @param test IPC test service
+ * @param rsize Place to store size of the shared area
+ * @return EOK on success or an error code
+ */
+errno_t ipc_test_get_ro_area_size(ipc_test_t *test, size_t *rsize)
+{
+	async_exch_t *exch;
+	errno_t retval;
+	sysarg_t size;
+
+	exch = async_exchange_begin(test->sess);
+	retval = async_req_0_1(exch, IPC_TEST_GET_RO_AREA_SIZE, &size);
+	async_exchange_end(exch);
+
+	if (retval != EOK)
+		return retval;
+
+	*rsize = size;
+	return EOK;
+}
+
+/** Get size of shared read-write memory area.
+ *
+ * @param test IPC test service
+ * @param rsize Place to store size of the shared area
+ * @return EOK on success or an error code
+ */
+errno_t ipc_test_get_rw_area_size(ipc_test_t *test, size_t *rsize)
+{
+	async_exch_t *exch;
+	errno_t retval;
+	sysarg_t size;
+
+	exch = async_exchange_begin(test->sess);
+	retval = async_req_0_1(exch, IPC_TEST_GET_RW_AREA_SIZE, &size);
+	async_exchange_end(exch);
+
+	if (retval != EOK)
+		return retval;
+
+	*rsize = size;
+	return EOK;
+}
+
+/** Test share-in of read-only area.
+ *
+ * @param test IPC test service
+ * @param size Size of the shared area
+ * @param rptr Place to store pointer to the shared-in area
+ * @return EOK on success or an error code
+ */
+errno_t ipc_test_share_in_ro(ipc_test_t *test, size_t size, const void **rptr)
+{
+	async_exch_t *exch;
+	ipc_call_t answer;
+	aid_t req;
+	void *dst;
+	errno_t rc;
+
+	exch = async_exchange_begin(test->sess);
+	req = async_send_0(exch, IPC_TEST_SHARE_IN_RO, &answer);
+
+	dst = NULL;
+	rc = async_share_in_start_0_0(exch, size, &dst);
+	if (rc != EOK || dst == AS_MAP_FAILED) {
+		async_exchange_end(exch);
+		async_forget(req);
+		return ENOMEM;
+	}
+
+	async_exchange_end(exch);
+	async_wait_for(req, NULL);
+	*rptr = dst;
+	return EOK;
+}
+
+/** Test share-in of read-write area.
+ *
+ * @param test IPC test service
+ * @param size Size of the shared area
+ * @param rptr Place to store pointer to the shared-in area
+ * @return EOK on success or an error code
+ */
+errno_t ipc_test_share_in_rw(ipc_test_t *test, size_t size, void **rptr)
+{
+	async_exch_t *exch;
+	ipc_call_t answer;
+	aid_t req;
+	void *dst;
+	errno_t rc;
+
+	exch = async_exchange_begin(test->sess);
+	req = async_send_0(exch, IPC_TEST_SHARE_IN_RW, &answer);
+
+	rc = async_share_in_start_0_0(exch, size, &dst);
+	if (rc != EOK || dst == AS_MAP_FAILED) {
+		async_exchange_end(exch);
+		async_forget(req);
+		return ENOMEM;
+	}
+
+	async_exchange_end(exch);
+	async_wait_for(req, NULL);
+	*rptr = dst;
+	return EOK;
+}
+
+/** Set server-side read/write buffer size.
+ *
+ * @param test IPC test service
+ * @param size Requested read/write buffer size
+ * @return EOK on success or an error code
+ */
+errno_t ipc_test_set_rw_buf_size(ipc_test_t *test, size_t size)
+{
+	async_exch_t *exch;
+	errno_t retval;
+
+	exch = async_exchange_begin(test->sess);
+	retval = async_req_1_0(exch, IPC_TEST_SET_RW_BUF_SIZE, size);
+	async_exchange_end(exch);
+
+	if (retval != EOK)
+		return retval;
+
+	return EOK;
+}
+
+/** Test IPC read.
+ *
+ * @param test IPC test service
+ * @param dest Destination buffer
+ * @param size Number of bytes to read / size of destination buffer
+ * @return EOK on success or an error code
+ */
+errno_t ipc_test_read(ipc_test_t *test, void *dest, size_t size)
+{
+	async_exch_t *exch;
+	ipc_call_t answer;
+	aid_t req;
+	errno_t rc;
+
+	exch = async_exchange_begin(test->sess);
+	req = async_send_0(exch, IPC_TEST_READ, &answer);
+
+	rc = async_data_read_start(exch, dest, size);
+	if (rc != EOK) {
+		async_exchange_end(exch);
+		async_forget(req);
+		return rc;
+	}
+
+	async_exchange_end(exch);
+	async_wait_for(req, NULL);
+	return EOK;
+}
+
+/** Test IPC write.
+ *
+ * @param test IPC test service
+ * @param data Source buffer
+ * @param size Number of bytes to write
+ * @return EOK on success or an error code
+ */
+errno_t ipc_test_write(ipc_test_t *test, const void *data, size_t size)
+{
+	async_exch_t *exch;
+	ipc_call_t answer;
+	aid_t req;
+	errno_t rc;
+
+	exch = async_exchange_begin(test->sess);
+	req = async_send_0(exch, IPC_TEST_WRITE, &answer);
+
+	rc = async_data_write_start(exch, data, size);
+	if (rc != EOK) {
+		async_exchange_end(exch);
+		async_forget(req);
+		return rc;
+	}
+
+	async_exchange_end(exch);
+	async_wait_for(req, NULL);
+	return EOK;
+}
+
+/** @}
+ */
Index: uspace/lib/label/src/gpt.c
===================================================================
--- uspace/lib/label/src/gpt.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/label/src/gpt.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -443,5 +443,5 @@
 		gpt_hdr->first_usable_lba = host2uint64_t_le(ba_min);
 		gpt_hdr->last_usable_lba = host2uint64_t_le(ba_max);
-		uuid_encode(&disk_uuid, gpt_hdr->disk_guid);
+		uuid_encode_le(&disk_uuid, gpt_hdr->disk_guid);
 		gpt_hdr->entry_lba = host2uint64_t_le(ptba[i]);
 		gpt_hdr->num_entries = host2uint32_t_le(num_entries);
@@ -812,6 +812,6 @@
 
 	memset(pte, 0, sizeof(gpt_entry_t));
-	uuid_encode(&part->ptype.t.uuid, pte->part_type);
-	uuid_encode(&part->part_uuid, pte->part_id);
+	uuid_encode_le(&part->ptype.t.uuid, pte->part_type);
+	uuid_encode_le(&part->part_uuid, pte->part_id);
 	pte->start_lba = host2uint64_t_le(part->block0);
 	pte->end_lba = host2uint64_t_le(eblock);
@@ -849,6 +849,6 @@
 	part->nblocks = b1 - b0 + 1;
 	part->ptype.fmt = lptf_uuid;
-	uuid_decode(pte->part_type, &part->ptype.t.uuid);
-	uuid_decode(pte->part_id, &part->part_uuid);
+	uuid_decode_le(pte->part_type, &part->ptype.t.uuid);
+	uuid_decode_le(pte->part_id, &part->part_uuid);
 
 	part->label = label;
Index: uspace/lib/meson.build
===================================================================
--- uspace/lib/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -33,5 +33,8 @@
 	'c',
 	'math',
+	'console',
 	'display',
+	'input',
+	'output',
 	'pixconv',
 	'posix',
@@ -53,10 +56,15 @@
 	'inet',
 
+	'input',
+	'output',
+	'console',
 	'device',
 
 	'block',
+	'clipboard',
 	'clui',
 	'codepage',
 	'compress',
+	'corecfg',
 	'cpp',
 	'crypto',
@@ -68,8 +76,10 @@
 	'gfx',
 	'http',
+	'ipctest',
 	'label',
 	'math',
 	'minix',
 	'nettl',
+	'ofw',
 	'pcm',
 	'pcut',
@@ -79,4 +89,5 @@
 	'scsi',
 	'sif',
+	'tbarcfg',
 	'trackmod',
 	'untar',
@@ -100,7 +111,10 @@
 	'virtio',
 
+	'ata',
 	'ieee80211',
 	'ddev',
+	'dispcfg',
 	'display',
+	'wndmgt',
 
 	'ui',
@@ -170,9 +184,11 @@
 		if run_command('[', '-d', incdir, ']').returncode() == 0
 			includes += include_directories(incdir)
+			_sdir = meson.current_source_dir() / l / 'include'
 
 			if installed_libs.contains(l)
-				_sdir = meson.current_source_dir() / l / 'include'
 				uspace_lib_devel_install_script_text += 'cp -R -L -T "@0@" "${DESTDIR}include/lib@1@"'.format(_sdir, l)
 			endif
+
+			exported_devel_files += ['include', _sdir, 'lib' + l]
 		else
 			includes += include_directories(l)
@@ -283,4 +299,6 @@
 			install_deps += [ _static_lib ]
 		endif
+
+		exported_devel_files += ['staticlib', _static_lib, 'lib' + l + '.a']
 
 		_static_dep = declare_dependency(
Index: uspace/lib/nic/src/nic_addr_db.c
===================================================================
--- uspace/lib/nic/src/nic_addr_db.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/nic/src/nic_addr_db.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -102,5 +102,5 @@
 }
 
-static hash_table_ops_t set_ops = {
+static const hash_table_ops_t set_ops = {
 	.hash = nic_addr_hash,
 	.key_hash = nic_addr_key_hash,
Index: uspace/lib/ofw/doc/doxygroups.h
===================================================================
--- uspace/lib/ofw/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ofw/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,3 @@
+/** @addtogroup libofw libofw
+ * @ingroup libs
+ */
Index: uspace/lib/ofw/include/ofw.h
===================================================================
--- uspace/lib/ofw/include/ofw.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ofw/include/ofw.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,61 @@
+/*
+ * 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 libofw
+ * @{
+ */
+/**
+ * @file OpenFirmware device tree access
+ */
+
+#ifndef _OFW_H
+#define _OFW_H
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <types/ofw.h>
+
+extern errno_t ofw_child_it_first(ofw_child_it_t *, const char *);
+extern void ofw_child_it_next(ofw_child_it_t *);
+extern bool ofw_child_it_end(ofw_child_it_t *);
+extern const char *ofw_child_it_get_name(ofw_child_it_t *);
+extern errno_t ofw_child_it_get_path(ofw_child_it_t *, char **);
+extern void ofw_child_it_fini(ofw_child_it_t *);
+
+extern errno_t ofw_prop_it_first(ofw_prop_it_t *, const char *);
+extern void ofw_prop_it_next(ofw_prop_it_t *);
+extern bool ofw_prop_it_end(ofw_prop_it_t *);
+extern const char *ofw_prop_it_get_name(ofw_prop_it_t *);
+extern const void *ofw_prop_it_get_data(ofw_prop_it_t *, size_t *);
+extern void ofw_prop_it_fini(ofw_prop_it_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ofw/include/types/ofw.h
===================================================================
--- uspace/lib/ofw/include/types/ofw.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ofw/include/types/ofw.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,66 @@
+/*
+ * 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 libofw
+ * @{
+ */
+/**
+ * @file OpenFirmware device tree access
+ */
+
+#ifndef _TYPES_OFW_H
+#define _TYPES_OFW_H
+
+#include <stddef.h>
+
+/** OpenFirmware child iterator */
+typedef struct {
+	/** OpenFirmware path of the parent */
+	char *ofwpath;
+	/** Key data from sysinfo */
+	char *keys;
+	/** Size of key data */
+	size_t keys_sz;
+	/** Byte offset of current child in @c keys */
+	size_t pos;
+} ofw_child_it_t;
+
+/** OpenFirmware property iterator */
+typedef struct {
+	/** Data from sysinfo */
+	void *data;
+	/** Size of data from sysinfo */
+	size_t data_sz;
+	/** Byte offset of current property in @c data */
+	size_t pos;
+} ofw_prop_it_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ofw/meson.build
===================================================================
--- uspace/lib/ofw/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ofw/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,31 @@
+#
+# 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.
+#
+
+src = files(
+	'src/ofw.c',
+)
Index: uspace/lib/ofw/src/ofw.c
===================================================================
--- uspace/lib/ofw/src/ofw.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ofw/src/ofw.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,320 @@
+/*
+ * 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 libofw
+ * @{
+ */
+/**
+ * @file OpenFirmware device tree access
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <ofw.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <str.h>
+#include <sysinfo.h>
+
+/** Convert OpenFirmware path to sysinfo path.
+ *
+ * @param ofwpath OpenFirmware path
+ * @param sipath place to store pointer to newly allocated sysinfo path
+ * @return EOK on success, ENOMEM if out of memory
+ */
+static errno_t ofw_path_to_sipath(const char *ofwpath, char **sipath)
+{
+	int rv;
+	size_t size;
+	size_t i;
+
+	if (str_cmp(ofwpath, "/") == 0)
+		ofwpath = "";
+
+	/* Append path to firmware.ofw */
+	rv = asprintf(sipath, "firmware.ofw%s", ofwpath);
+	if (rv < 0)
+		return ENOMEM;
+
+	/* Replace '/' with '.' */
+	size = str_size(*sipath);
+	for (i = 0; i < size; i++)
+		if ((*sipath)[i] == '/')
+			(*sipath)[i] = '.';
+
+	return EOK;
+}
+
+/** Initialize child iterator with first child entry.
+ *
+ * Initialize the child iterator and make it point to the first child
+ * of the specified OpenFirmware device node.
+ *
+ * @param it Child iterator to initialize
+ * @param ofwpath OpenFirmware path of the parent node
+ */
+errno_t ofw_child_it_first(ofw_child_it_t *it, const char *ofwpath)
+{
+	errno_t rc;
+	char *sipath = NULL;
+
+	it->ofwpath = NULL;
+	it->keys = NULL;
+	it->keys_sz = 0;
+	it->pos = 0;
+
+	rc = ofw_path_to_sipath(ofwpath, &sipath);
+	if (rc != EOK)
+		goto error;
+
+	if (str_cmp(ofwpath, "/") == 0) {
+		it->ofwpath = str_dup("");
+	} else {
+		it->ofwpath = str_dup(ofwpath);
+	}
+
+	if (it->ofwpath == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	it->keys = sysinfo_get_keys(sipath, &it->keys_sz);
+	if (it->keys == NULL) {
+		rc = ENOENT;
+		goto error;
+	}
+
+	return EOK;
+error:
+	if (sipath != NULL)
+		free(sipath);
+
+	if (it->ofwpath != NULL) {
+		free(it->ofwpath);
+		it->ofwpath = NULL;
+	}
+
+	return rc;
+}
+
+/** Move child iterator to the next child.
+ *
+ * @param it Child iterator
+ */
+void ofw_child_it_next(ofw_child_it_t *it)
+{
+	size_t adj;
+
+	assert(!ofw_child_it_end(it));
+
+	adj = str_nsize(it->keys + it->pos, it->keys_sz - it->pos) + 1;
+	assert(it->pos + adj <= it->keys_sz);
+	it->pos += adj;
+}
+
+/** Determine if there are no more child nodes.
+ *
+ * @param it Child iterator
+ * @return @c true iff iterator is beyond the last entry
+ */
+bool ofw_child_it_end(ofw_child_it_t *it)
+{
+	return it->pos >= it->keys_sz;
+}
+
+/** Get child name from iterator.
+ *
+ * @param it Child iterator
+ * @return Child name (valid until next operation on @a it
+ */
+const char *ofw_child_it_get_name(ofw_child_it_t *it)
+{
+	assert(!ofw_child_it_end(it));
+	return it->keys + it->pos;
+}
+
+/** Get child path from iterator.
+ *
+ * @param it Child iterator
+ * @param rpath Place to store pointer to allocated OpenFirmware path
+ * @return @c EOK on success or an error code
+ */
+errno_t ofw_child_it_get_path(ofw_child_it_t *it, char **rpath)
+{
+	const char *name;
+	int rv;
+
+	assert(!ofw_child_it_end(it));
+	name = ofw_child_it_get_name(it);
+
+	rv = asprintf(rpath, "%s/%s", it->ofwpath, name);
+	if (rv < 0)
+		return ENOMEM;
+
+	return EOK;
+}
+
+/** Finalize child iterator.
+ *
+ * This must be called after using a child iterator.
+ *
+ * @param it Child iterator
+ */
+void ofw_child_it_fini(ofw_child_it_t *it)
+{
+	free(it->ofwpath);
+	free(it->keys);
+	it->keys = NULL;
+	it->keys_sz = 0;
+}
+
+/** Initialize property iterator with property.
+ *
+ * Initialize the property iterator and make it point to the first property
+ * of the specified OpenFirmware device node.
+ *
+ * @param it Property iterator to initialize
+ * @param ofwpath OpenFirmware path of the node
+ */
+errno_t ofw_prop_it_first(ofw_prop_it_t *it, const char *ofwpath)
+{
+	errno_t rc;
+	char *sipath;
+
+	it->data = NULL;
+	it->data_sz = 0;
+	it->pos = 0;
+
+	rc = ofw_path_to_sipath(ofwpath, &sipath);
+	if (rc != EOK)
+		goto error;
+
+	it->data = sysinfo_get_data(sipath, &it->data_sz);
+	if (it->data == NULL) {
+		rc = ENOENT;
+		goto error;
+	}
+
+	return EOK;
+error:
+	if (sipath != NULL)
+		free(sipath);
+	return rc;
+}
+
+/** Move property iterator to the next property.
+ *
+ * @param it Child iterator
+ */
+void ofw_prop_it_next(ofw_prop_it_t *it)
+{
+	size_t cur_size;
+	size_t value_size;
+
+	cur_size = str_nsize(it->data + it->pos, it->data_sz - it->pos);
+	if (((char *) it->data)[it->pos + cur_size] != 0)
+		return;
+
+	it->pos += cur_size + 1;
+
+	/* Process value size */
+	memcpy(&value_size, it->data + it->pos, sizeof(value_size));
+
+	it->pos += sizeof(value_size);
+	if ((it->pos >= it->data_sz) || (it->pos + value_size > it->data_sz))
+		return;
+
+	it->pos += value_size;
+}
+
+/** Determine if there are no more properties.
+ *
+ * @param it Property iterator
+ * @return @c true iff iterator is beyond the last entry
+ */
+bool ofw_prop_it_end(ofw_prop_it_t *it)
+{
+	return it->pos >= it->data_sz;
+}
+
+/** Get property name from iterator.
+ *
+ * @param it Child iterator
+ * @return Property name (valid until next operation on @a it
+ */
+const char *ofw_prop_it_get_name(ofw_prop_it_t *it)
+{
+	assert(!ofw_prop_it_end(it));
+	return it->data + it->pos;
+}
+
+/** Get property data from iterator.
+ *
+ * @param it Property iterator
+ * @param rsize Place to store size of data
+ * @return Pointer to data
+ */
+const void *ofw_prop_it_get_data(ofw_prop_it_t *it, size_t *rsize)
+{
+	size_t cur_size;
+	size_t value_size;
+	size_t pos;
+
+	assert(!ofw_prop_it_end(it));
+	pos = it->pos;
+
+	cur_size = str_nsize(it->data + pos, it->data_sz - pos);
+	if (((char *) it->data)[pos + cur_size] != 0)
+		return NULL;
+
+	pos += cur_size + 1;
+
+	/* Process value size */
+	memcpy(&value_size, it->data + pos, sizeof(value_size));
+	pos += sizeof(value_size);
+
+	*rsize = value_size;
+	return it->data + pos;
+}
+
+/** Finalize property iterator.
+ *
+ * This must be called after using a child iterator.
+ *
+ * @param it Child iterator
+ */
+void ofw_prop_it_fini(ofw_prop_it_t *it)
+{
+	free(it->data);
+	it->data = NULL;
+	it->data_sz = 0;
+	it->pos = 0;
+}
+
+/** @}
+ */
Index: uspace/lib/output/doc/doxygroups.h
===================================================================
--- uspace/lib/output/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/output/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,3 @@
+/** @addtogroup liboutput liboutput
+ * @ingroup libs
+ */
Index: uspace/lib/output/include/io/charfield.h
===================================================================
--- uspace/lib/output/include/io/charfield.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/output/include/io/charfield.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2006 Josef Cejka
+ * Copyright (c) 2011 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 liboutput
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBOUTPUT_IO_CHARFIELD_H_
+#define _LIBOUTPUT_IO_CHARFIELD_H_
+
+#include <stdbool.h>
+#include <uchar.h>
+#include <io/color.h>
+#include <io/style.h>
+#include <io/pixel.h>
+
+typedef enum {
+	CHAR_FLAG_NONE = 0,
+	CHAR_FLAG_DIRTY = 1
+} char_flags_t;
+
+typedef enum {
+	CHAR_ATTR_STYLE,
+	CHAR_ATTR_INDEX,
+	CHAR_ATTR_RGB
+} char_attr_type_t;
+
+typedef struct {
+	console_color_t bgcolor;
+	console_color_t fgcolor;
+	console_color_attr_t attr;
+} char_attr_index_t;
+
+typedef struct {
+	pixel_t bgcolor;
+	pixel_t fgcolor;
+} char_attr_rgb_t;
+
+typedef union {
+	console_style_t style;
+	char_attr_index_t index;
+	char_attr_rgb_t rgb;
+} char_attr_val_t;
+
+typedef struct {
+	char_attr_type_t type;
+	char_attr_val_t val;
+} char_attrs_t;
+
+typedef struct {
+	char32_t ch;
+	char_attrs_t attrs;
+	char_flags_t flags;
+} charfield_t;
+
+static inline bool attrs_same(char_attrs_t a1, char_attrs_t a2)
+{
+	if (a1.type != a2.type)
+		return false;
+
+	switch (a1.type) {
+	case CHAR_ATTR_STYLE:
+		return (a1.val.style == a2.val.style);
+	case CHAR_ATTR_INDEX:
+		return (a1.val.index.bgcolor == a2.val.index.bgcolor) &&
+		    (a1.val.index.fgcolor == a2.val.index.fgcolor) &&
+		    (a1.val.index.attr == a2.val.index.attr);
+	case CHAR_ATTR_RGB:
+		return (a1.val.rgb.bgcolor == a2.val.rgb.bgcolor) &&
+		    (a1.val.rgb.fgcolor == a2.val.rgb.fgcolor);
+	}
+
+	return false;
+}
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/output/include/io/chargrid.h
===================================================================
--- uspace/lib/output/include/io/chargrid.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/output/include/io/chargrid.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2011 Martin Decky
+ * 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 liboutput
+ * @{
+ */
+/**
+ * @file
+ */
+
+#ifndef _LIBOUTPUT_IO_CHARGRID_H_
+#define _LIBOUTPUT_IO_CHARGRID_H_
+
+#include <io/charfield.h>
+#include <types/common.h>
+#include <stddef.h>
+
+typedef enum {
+	CHARGRID_FLAG_NONE = 0,
+	CHARGRID_FLAG_SHARED = 1
+} chargrid_flag_t;
+
+typedef struct {
+	size_t size;            /**< Structure size */
+	chargrid_flag_t flags;  /**< Screenbuffer flags */
+
+	sysarg_t cols;          /**< Number of columns */
+	sysarg_t rows;          /**< Number of rows */
+
+	sysarg_t col;           /**< Current column */
+	sysarg_t row;           /**< Current row */
+	bool cursor_visible;    /**< Cursor visibility */
+
+	char_attrs_t attrs;     /**< Current attributes */
+
+	sysarg_t top_row;       /**< The first row in the cyclic buffer */
+	charfield_t data[];     /**< Screen contents (cyclic buffer) */
+} chargrid_t;
+
+static inline charfield_t *chargrid_charfield_at(chargrid_t *chargrid,
+    sysarg_t col, sysarg_t row)
+{
+	return chargrid->data +
+	    ((row + chargrid->top_row) % chargrid->rows) * chargrid->cols +
+	    col;
+}
+
+extern chargrid_t *chargrid_create(sysarg_t, sysarg_t,
+    chargrid_flag_t);
+extern void chargrid_destroy(chargrid_t *);
+
+extern bool chargrid_cursor_at(chargrid_t *, sysarg_t, sysarg_t);
+
+extern sysarg_t chargrid_get_top_row(chargrid_t *);
+
+extern sysarg_t chargrid_putuchar(chargrid_t *, char32_t, bool);
+extern sysarg_t chargrid_newline(chargrid_t *);
+extern sysarg_t chargrid_tabstop(chargrid_t *, sysarg_t);
+extern sysarg_t chargrid_backspace(chargrid_t *);
+
+extern void chargrid_clear(chargrid_t *);
+extern void chargrid_clear_row(chargrid_t *, sysarg_t);
+
+extern void chargrid_set_cursor(chargrid_t *, sysarg_t, sysarg_t);
+extern void chargrid_set_cursor_visibility(chargrid_t *, bool);
+extern bool chargrid_get_cursor_visibility(chargrid_t *);
+
+extern void chargrid_get_cursor(chargrid_t *, sysarg_t *, sysarg_t *);
+
+extern void chargrid_set_style(chargrid_t *, console_style_t);
+extern void chargrid_set_color(chargrid_t *, console_color_t,
+    console_color_t, console_color_attr_t);
+extern void chargrid_set_rgb_color(chargrid_t *, pixel_t, pixel_t);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/output/include/io/color.h
===================================================================
--- uspace/lib/output/include/io/color.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/output/include/io/color.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,58 @@
+/*
+ * 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 liboutput
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBOUTPUT_IO_COLOR_H_
+#define _LIBOUTPUT_IO_COLOR_H_
+
+typedef enum {
+	COLOR_BLACK   = 0,
+	COLOR_BLUE    = 1,
+	COLOR_GREEN   = 2,
+	COLOR_CYAN    = 3,
+	COLOR_RED     = 4,
+	COLOR_MAGENTA = 5,
+	COLOR_YELLOW  = 6,
+	COLOR_WHITE   = 7
+} console_color_t;
+
+typedef enum {
+	CATTR_NORMAL = 0,
+	CATTR_BRIGHT = 8,
+	CATTR_BLINK  = 16
+} console_color_attr_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/output/include/io/concaps.h
===================================================================
--- uspace/lib/output/include/io/concaps.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/output/include/io/concaps.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,48 @@
+/*
+ * 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 liboutput
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBOUTPUT_IO_CONCAPS_H_
+#define _LIBOUTPUT_IO_CONCAPS_H_
+
+typedef enum {
+	CONSOLE_CAP_NONE = 0,
+	CONSOLE_CAP_STYLE = 1,
+	CONSOLE_CAP_INDEXED = 2,
+	CONSOLE_CAP_RGB = 4
+} console_caps_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/output/include/io/output.h
===================================================================
--- uspace/lib/output/include/io/output.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/output/include/io/output.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2011 Martin Decky
+ * 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 liboutput
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBOUTPUT_IO_OUTPUT_H_
+#define _LIBOUTPUT_IO_OUTPUT_H_
+
+#include <ipc/output.h>
+#include <io/chargrid.h>
+#include <io/concaps.h>
+#include <io/style.h>
+
+extern errno_t output_yield(async_sess_t *);
+extern errno_t output_claim(async_sess_t *);
+extern errno_t output_get_dimensions(async_sess_t *, sysarg_t *, sysarg_t *);
+extern errno_t output_get_caps(async_sess_t *, console_caps_t *);
+
+extern frontbuf_handle_t output_frontbuf_create(async_sess_t *, chargrid_t *);
+
+extern errno_t output_cursor_update(async_sess_t *, frontbuf_handle_t);
+extern errno_t output_set_style(async_sess_t *, console_style_t);
+
+extern errno_t output_update(async_sess_t *, frontbuf_handle_t);
+extern errno_t output_damage(async_sess_t *, frontbuf_handle_t,
+    sysarg_t, sysarg_t, sysarg_t, sysarg_t);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/output/include/ipc/output.h
===================================================================
--- uspace/lib/output/include/ipc/output.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/output/include/ipc/output.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2012 Martin Decky
+ * 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 liboutput
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBOUTPUT_IPC_OUTPUT_H_
+#define _LIBOUTPUT_IPC_OUTPUT_H_
+
+#include <ipc/common.h>
+
+typedef sysarg_t frontbuf_handle_t;
+
+typedef enum {
+	OUTPUT_YIELD = IPC_FIRST_USER_METHOD,
+	OUTPUT_CLAIM,
+	OUTPUT_GET_DIMENSIONS,
+	OUTPUT_GET_CAPS,
+
+	OUTPUT_FRONTBUF_CREATE,
+	OUTPUT_FRONTBUF_DESTROY,
+
+	OUTPUT_CURSOR_UPDATE,
+	OUTPUT_SET_STYLE,
+	OUTPUT_SET_COLOR,
+	OUTPUT_SET_RGB_COLOR,
+
+	OUTPUT_UPDATE,
+	OUTPUT_DAMAGE
+} output_request_t;
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/lib/output/meson.build
===================================================================
--- uspace/lib/output/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/output/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,32 @@
+#
+# 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.
+#
+
+src = files(
+	'src/chargrid.c',
+	'src/output.c',
+)
Index: uspace/lib/output/src/chargrid.c
===================================================================
--- uspace/lib/output/src/chargrid.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/output/src/chargrid.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,362 @@
+/*
+ * Copyright (c) 2006 Josef Cejka
+ * 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 liboutput
+ * @{
+ */
+/** @file
+ */
+
+#include <io/style.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <as.h>
+#include <io/chargrid.h>
+
+/** Create a chargrid.
+ *
+ * @param[in] cols  Number of columns.
+ * @param[in] rows  Number of rows.
+ * @param[in] flags Chargrid flags.
+ *
+ * @return New chargrid.
+ * @return NULL on failure.
+ *
+ */
+chargrid_t *chargrid_create(sysarg_t cols, sysarg_t rows,
+    chargrid_flag_t flags)
+{
+	size_t size =
+	    sizeof(chargrid_t) + cols * rows * sizeof(charfield_t);
+	chargrid_t *scrbuf;
+
+	if ((flags & CHARGRID_FLAG_SHARED) == CHARGRID_FLAG_SHARED) {
+		scrbuf = (chargrid_t *) as_area_create(AS_AREA_ANY, size,
+		    AS_AREA_READ | AS_AREA_WRITE | AS_AREA_CACHEABLE,
+		    AS_AREA_UNPAGED);
+		if (scrbuf == AS_MAP_FAILED)
+			return NULL;
+	} else {
+		scrbuf = (chargrid_t *) malloc(size);
+		if (scrbuf == NULL)
+			return NULL;
+	}
+
+	scrbuf->size = size;
+	scrbuf->flags = flags;
+	scrbuf->cols = cols;
+	scrbuf->rows = rows;
+	scrbuf->cursor_visible = false;
+
+	scrbuf->attrs.type = CHAR_ATTR_STYLE;
+	scrbuf->attrs.val.style = STYLE_NORMAL;
+
+	scrbuf->top_row = 0;
+	chargrid_clear(scrbuf);
+
+	return scrbuf;
+}
+
+void chargrid_destroy(chargrid_t *srcbuf)
+{
+	// TODO
+}
+
+bool chargrid_cursor_at(chargrid_t *scrbuf, sysarg_t col, sysarg_t row)
+{
+	return ((scrbuf->cursor_visible) && (scrbuf->col == col) &&
+	    (scrbuf->row == row));
+}
+
+sysarg_t chargrid_get_top_row(chargrid_t *scrbuf)
+{
+	return scrbuf->top_row;
+}
+
+static sysarg_t chargrid_update_rows(chargrid_t *scrbuf)
+{
+	if (scrbuf->row == scrbuf->rows) {
+		scrbuf->row = scrbuf->rows - 1;
+		scrbuf->top_row = (scrbuf->top_row + 1) % scrbuf->rows;
+		chargrid_clear_row(scrbuf, scrbuf->row);
+
+		return scrbuf->rows;
+	}
+
+	return 2;
+}
+
+static sysarg_t chargrid_update_cols(chargrid_t *scrbuf)
+{
+	/* Column overflow */
+	if (scrbuf->col == scrbuf->cols) {
+		scrbuf->col = 0;
+		scrbuf->row++;
+		return chargrid_update_rows(scrbuf);
+	}
+
+	return 1;
+}
+
+/** Store one character to chargrid.
+ *
+ * Its position is determined by scrbuf->col
+ * and scrbuf->row.
+ *
+ * @param scrbuf Chargrid.
+ * @param ch     Character to store.
+ * @param update Update coordinates.
+ *
+ * @return Number of rows which have been affected. In usual
+ *         situations this is 1. If the current position was
+ *         updated to a new row, this value is 2.
+ *
+ */
+sysarg_t chargrid_putuchar(chargrid_t *scrbuf, char32_t ch, bool update)
+{
+	assert(scrbuf->col < scrbuf->cols);
+	assert(scrbuf->row < scrbuf->rows);
+
+	charfield_t *field =
+	    chargrid_charfield_at(scrbuf, scrbuf->col, scrbuf->row);
+
+	field->ch = ch;
+	field->attrs = scrbuf->attrs;
+	field->flags |= CHAR_FLAG_DIRTY;
+
+	if (update) {
+		scrbuf->col++;
+		return chargrid_update_cols(scrbuf);
+	}
+
+	return 1;
+}
+
+/** Jump to a new row in chargrid.
+ *
+ * @param scrbuf Chargrid.
+ *
+ * @return Number of rows which have been affected. In usual
+ *         situations this is 2 (the original row and the new
+ *         row).
+ *
+ */
+sysarg_t chargrid_newline(chargrid_t *scrbuf)
+{
+	assert(scrbuf->col < scrbuf->cols);
+	assert(scrbuf->row < scrbuf->rows);
+
+	scrbuf->col = 0;
+	scrbuf->row++;
+
+	return chargrid_update_rows(scrbuf);
+}
+
+/** Jump to a new row in chargrid.
+ *
+ * @param scrbuf   Chargrid.
+ * @param tab_size Tab size.
+ *
+ * @return Number of rows which have been affected. In usual
+ *         situations this is 1. If the current position was
+ *         updated to a new row, this value is 2.
+ *
+ */
+sysarg_t chargrid_tabstop(chargrid_t *scrbuf, sysarg_t tab_size)
+{
+	assert(scrbuf->col < scrbuf->cols);
+	assert(scrbuf->row < scrbuf->rows);
+
+	sysarg_t spaces = tab_size - scrbuf->cols % tab_size;
+	sysarg_t flush = 1;
+
+	for (sysarg_t i = 0; i < spaces; i++)
+		flush += chargrid_putuchar(scrbuf, ' ', true) - 1;
+
+	return flush;
+}
+
+/** Jump to the previous character in chargrid.
+ *
+ * Currently no scrollback is supported.
+ *
+ * @param scrbuf Chargrid.
+ *
+ * @return Number of rows which have been affected. In usual
+ *         situations this is 1. If the current position was
+ *         updated to the previous row, this value is 2.
+ * @return 0 if no backspace is possible.
+ *
+ */
+sysarg_t chargrid_backspace(chargrid_t *scrbuf)
+{
+	assert(scrbuf->col < scrbuf->cols);
+	assert(scrbuf->row < scrbuf->rows);
+
+	if ((scrbuf->col == 0) && (scrbuf->row == 0))
+		return 0;
+
+	if (scrbuf->col == 0) {
+		scrbuf->col = scrbuf->cols - 1;
+		scrbuf->row--;
+
+		chargrid_putuchar(scrbuf, ' ', false);
+		return 2;
+	}
+
+	scrbuf->col--;
+	chargrid_putuchar(scrbuf, ' ', false);
+	return 1;
+}
+
+/** Clear the chargrid.
+ *
+ * @param scrbuf Chargrid.
+ *
+ */
+void chargrid_clear(chargrid_t *scrbuf)
+{
+	for (size_t pos = 0; pos < (scrbuf->cols * scrbuf->rows); pos++) {
+		scrbuf->data[pos].ch = 0;
+		scrbuf->data[pos].attrs = scrbuf->attrs;
+		scrbuf->data[pos].flags = CHAR_FLAG_DIRTY;
+	}
+
+	scrbuf->col = 0;
+	scrbuf->row = 0;
+}
+
+/** Update current chargrid coordinates
+ *
+ * @param scrbuf Chargrid.
+ * @param col    New column.
+ * @param row    New row.
+ *
+ */
+void chargrid_set_cursor(chargrid_t *scrbuf, sysarg_t col, sysarg_t row)
+{
+	if (col >= scrbuf->cols || row >= scrbuf->rows)
+		return;
+
+	scrbuf->col = col;
+	scrbuf->row = row;
+}
+
+void chargrid_set_cursor_visibility(chargrid_t *scrbuf, bool visible)
+{
+	scrbuf->cursor_visible = visible;
+}
+
+/** Get current chargrid coordinates
+ *
+ * @param scrbuf Chargrid.
+ * @param col    Column.
+ * @param row    Row.
+ *
+ */
+void chargrid_get_cursor(chargrid_t *scrbuf, sysarg_t *col,
+    sysarg_t *row)
+{
+	assert(col);
+	assert(row);
+
+	*col = scrbuf->col;
+	*row = scrbuf->row;
+}
+
+bool chargrid_get_cursor_visibility(chargrid_t *scrbuf)
+{
+	return scrbuf->cursor_visible;
+}
+
+/** Clear one buffer row.
+ *
+ * @param scrbuf Chargrid.
+ * @param row    Row to clear.
+ *
+ */
+void chargrid_clear_row(chargrid_t *scrbuf, sysarg_t row)
+{
+	for (sysarg_t col = 0; col < scrbuf->cols; col++) {
+		charfield_t *field =
+		    chargrid_charfield_at(scrbuf, col, row);
+
+		field->ch = 0;
+		field->attrs = scrbuf->attrs;
+		field->flags |= CHAR_FLAG_DIRTY;
+	}
+}
+
+/** Set chargrid style.
+ *
+ * @param scrbuf Chargrid.
+ * @param style  Style.
+ *
+ */
+void chargrid_set_style(chargrid_t *scrbuf, console_style_t style)
+{
+	scrbuf->attrs.type = CHAR_ATTR_STYLE;
+	scrbuf->attrs.val.style = style;
+}
+
+/** Set chargrid color.
+ *
+ * @param scrbuf  Chargrid.
+ * @param bgcolor Background color.
+ * @param fgcolor Foreground color.
+ * @param attr    Color attribute.
+ *
+ */
+void chargrid_set_color(chargrid_t *scrbuf, console_color_t bgcolor,
+    console_color_t fgcolor, console_color_attr_t attr)
+{
+	scrbuf->attrs.type = CHAR_ATTR_INDEX;
+	scrbuf->attrs.val.index.bgcolor = bgcolor;
+	scrbuf->attrs.val.index.fgcolor = fgcolor;
+	scrbuf->attrs.val.index.attr = attr;
+}
+
+/** Set chargrid RGB color.
+ *
+ * @param scrbuf  Chargrid.
+ * @param bgcolor Background color.
+ * @param fgcolor Foreground color.
+ *
+ */
+void chargrid_set_rgb_color(chargrid_t *scrbuf, pixel_t bgcolor,
+    pixel_t fgcolor)
+{
+	scrbuf->attrs.type = CHAR_ATTR_RGB;
+	scrbuf->attrs.val.rgb.bgcolor = bgcolor;
+	scrbuf->attrs.val.rgb.fgcolor = fgcolor;
+}
+
+/** @}
+ */
Index: uspace/lib/output/src/output.c
===================================================================
--- uspace/lib/output/src/output.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/output/src/output.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2011 Martin Decky
+ * 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 liboutput
+ * @{
+ */
+/** @file
+ */
+
+#include <async.h>
+#include <errno.h>
+#include <as.h>
+#include <ipc/output.h>
+#include <io/concaps.h>
+#include <io/output.h>
+
+errno_t output_yield(async_sess_t *sess)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	errno_t ret = async_req_0_0(exch, OUTPUT_YIELD);
+	async_exchange_end(exch);
+
+	return ret;
+}
+
+errno_t output_claim(async_sess_t *sess)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	errno_t ret = async_req_0_0(exch, OUTPUT_CLAIM);
+	async_exchange_end(exch);
+
+	return ret;
+}
+
+errno_t output_get_dimensions(async_sess_t *sess, sysarg_t *maxx, sysarg_t *maxy)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	errno_t ret = async_req_0_2(exch, OUTPUT_GET_DIMENSIONS, maxx, maxy);
+	async_exchange_end(exch);
+
+	return ret;
+}
+
+errno_t output_get_caps(async_sess_t *sess, console_caps_t *ccaps)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+
+	sysarg_t rv;
+	errno_t ret = async_req_0_1(exch, OUTPUT_GET_CAPS, &rv);
+
+	async_exchange_end(exch);
+
+	if (ret == EOK)
+		*ccaps = (console_caps_t) rv;
+
+	return ret;
+}
+
+frontbuf_handle_t output_frontbuf_create(async_sess_t *sess,
+    chargrid_t *frontbuf)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+
+	ipc_call_t answer;
+	aid_t req = async_send_0(exch, OUTPUT_FRONTBUF_CREATE, &answer);
+	errno_t rc = async_share_out_start(exch, frontbuf, AS_AREA_READ |
+	    AS_AREA_WRITE | AS_AREA_CACHEABLE);
+
+	async_exchange_end(exch);
+
+	errno_t ret;
+	async_wait_for(req, &ret);
+
+	if ((rc != EOK) || (ret != EOK))
+		return 0;
+
+	return (frontbuf_handle_t) ipc_get_arg1(&answer);
+}
+
+errno_t output_set_style(async_sess_t *sess, console_style_t style)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	errno_t ret = async_req_1_0(exch, OUTPUT_SET_STYLE, style);
+	async_exchange_end(exch);
+
+	return ret;
+}
+
+errno_t output_cursor_update(async_sess_t *sess, frontbuf_handle_t frontbuf)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	errno_t ret = async_req_1_0(exch, OUTPUT_CURSOR_UPDATE, frontbuf);
+	async_exchange_end(exch);
+
+	return ret;
+}
+
+errno_t output_update(async_sess_t *sess, frontbuf_handle_t frontbuf)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	errno_t ret = async_req_1_0(exch, OUTPUT_UPDATE, frontbuf);
+	async_exchange_end(exch);
+
+	return ret;
+}
+
+errno_t output_damage(async_sess_t *sess, frontbuf_handle_t frontbuf, sysarg_t col,
+    sysarg_t row, sysarg_t cols, sysarg_t rows)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	errno_t ret = async_req_5_0(exch, OUTPUT_DAMAGE, frontbuf, col, row,
+	    cols, rows);
+	async_exchange_end(exch);
+
+	return ret;
+}
+
+/** @}
+ */
Index: uspace/lib/posix/include/common
===================================================================
--- uspace/lib/posix/include/common	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/posix/include/common	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,1 @@
+../../../../common/include
Index: uspace/lib/posix/include/posix/ctype.h
===================================================================
--- uspace/lib/posix/include/posix/ctype.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/posix/include/posix/ctype.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -37,5 +37,5 @@
 #define POSIX_CTYPE_H_
 
-#include <libc/ctype.h>
+#include <common/ctype.h>
 
 __C_DECLS_BEGIN;
Index: uspace/lib/posix/include/posix/stdlib.h
===================================================================
--- uspace/lib/posix/include/posix/stdlib.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/posix/include/posix/stdlib.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -37,5 +37,5 @@
 #define POSIX_STDLIB_H_
 
-#include <libc/stdlib.h>
+#include <common/stdlib.h>
 #include <sys/types.h>
 
Index: uspace/lib/posix/include/posix/string.h
===================================================================
--- uspace/lib/posix/include/posix/string.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/posix/include/posix/string.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -46,5 +46,5 @@
 #include <stddef.h>
 
-#include <libc/mem.h>
+#include <common/mem.h>
 
 #define _REALLY_WANT_STRING_H
Index: uspace/lib/posix/meson.build
===================================================================
--- uspace/lib/posix/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/posix/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -66,4 +66,6 @@
 uspace_lib_devel_install_script_text += 'mkdir -p "${DESTDIR}include/libposix"'
 uspace_lib_devel_install_script_text += 'cp -R -L -T "@0@" "${DESTDIR}include/libposix"'.format(_sdir)
+uspace_lib_devel_install_script_text += 'ln -s -r "${DESTDIR}include/libc" "${DESTDIR}/include/common"'
 
-# TODO: exports
+exported_devel_files += [ 'include', meson.current_source_dir() / 'include' / 'posix', 'libposix' ]
+exported_devel_files += [ 'includesymlink', 'libc', 'libposix' ]
Index: uspace/lib/posix/src/stdio.c
===================================================================
--- uspace/lib/posix/src/stdio.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/posix/src/stdio.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -53,7 +53,7 @@
 #include <unistd.h>
 
-#include <io/printf_core.h>
+#include <printf_core.h>
 #include <str.h>
-#include <malloc.h>
+#include <stdlib.h>
 #include <adt/list.h>
 
Index: uspace/lib/posix/src/time.c
===================================================================
--- uspace/lib/posix/src/time.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/posix/src/time.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -47,5 +47,5 @@
 
 #include <fibril.h>
-#include <malloc.h>
+#include <stdlib.h>
 #include <task.h>
 #include <stddef.h>
Index: uspace/lib/posix/src/unistd.c
===================================================================
--- uspace/lib/posix/src/unistd.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/posix/src/unistd.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -50,5 +50,5 @@
 #include <task.h>
 #include <stats.h>
-#include <malloc.h>
+#include <stdlib.h>
 #include <vfs/vfs.h>
 
Index: uspace/lib/riff/src/chunk.c
===================================================================
--- uspace/lib/riff/src/chunk.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/riff/src/chunk.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -146,4 +146,6 @@
 	long pos;
 	long cksize;
+	uint8_t pad;
+	size_t nw;
 	errno_t rc;
 
@@ -153,6 +155,11 @@
 
 	cksize = pos - wchunk->ckstart;
-	if (pos % 2 != 0)
+	if (pos % 2 != 0) {
 		++pos;
+		pad = 0;
+		nw = fwrite(&pad, 1, sizeof(pad), rw->f);
+		if (nw != sizeof(pad))
+			return EIO;
+	}
 
 	if (fseek(rw->f, wchunk->ckstart - 4, SEEK_SET) < 0)
@@ -476,5 +483,4 @@
 	uint8_t byte;
 	size_t nread;
-	errno_t rc;
 
 	ckend = riff_rchunk_get_ndpos(rchunk);
@@ -483,10 +489,9 @@
 		/* (Buffered) reading is faster than seeking */
 		while (rchunk->riffr->pos < ckend) {
-			rc = riff_read(rchunk, &byte, sizeof(byte), &nread);
-			if (rc != EOK)
-				return rc;
-
+			nread = fread(&byte, 1, sizeof(byte), rchunk->riffr->f);
 			if (nread != sizeof(byte))
 				return EIO;
+
+			rchunk->riffr->pos += sizeof(byte);
 		}
 
@@ -495,4 +500,5 @@
 		if (fseek(rchunk->riffr->f, ckend, SEEK_SET) < 0)
 			return EIO;
+
 		rchunk->riffr->pos = ckend;
 	}
Index: uspace/lib/sif/src/sif.c
===================================================================
--- uspace/lib/sif/src/sif.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/sif/src/sif.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2018 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -702,4 +702,5 @@
 {
 	char *str;
+	char *nstr;
 	size_t str_size;
 	size_t sidx;
@@ -739,9 +740,11 @@
 		if (sidx >= str_size) {
 			str_size *= 2;
-			str = realloc(str, str_size + 1);
-			if (str == NULL) {
+			nstr = realloc(str, str_size + 1);
+			if (nstr == NULL) {
 				rc = ENOMEM;
 				goto error;
 			}
+
+			str = nstr;
 		}
 
Index: uspace/lib/tbarcfg/doc/doxygroups.h
===================================================================
--- uspace/lib/tbarcfg/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/tbarcfg/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,3 @@
+/** @addtogroup libtbarcfg libtbarcfg
+ * @ingroup libs
+ */
Index: uspace/lib/tbarcfg/include/ipc/tbarcfg.h
===================================================================
--- uspace/lib/tbarcfg/include/ipc/tbarcfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/tbarcfg/include/ipc/tbarcfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/tbarcfg/include/tbarcfg/tbarcfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,75 @@
+/*
+ * 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 Start menu
+ */
+
+#ifndef _TBARCFG_TBARCFG_H
+#define _TBARCFG_TBARCFG_H
+
+#include <errno.h>
+#include <sif.h>
+#include <stdbool.h>
+#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 **);
+extern void tbarcfg_close(tbarcfg_t *);
+extern smenu_entry_t *tbarcfg_smenu_first(tbarcfg_t *);
+extern smenu_entry_t *tbarcfg_smenu_next(smenu_entry_t *);
+extern smenu_entry_t *tbarcfg_smenu_last(tbarcfg_t *);
+extern smenu_entry_t *tbarcfg_smenu_prev(smenu_entry_t *);
+extern const char *smenu_entry_get_caption(smenu_entry_t *);
+extern const char *smenu_entry_get_cmd(smenu_entry_t *);
+extern bool smenu_entry_get_terminal(smenu_entry_t *);
+extern bool smenu_entry_get_separator(smenu_entry_t *);
+extern errno_t smenu_entry_set_caption(smenu_entry_t *, const char *);
+extern errno_t smenu_entry_set_cmd(smenu_entry_t *, const char *);
+extern void smenu_entry_set_terminal(smenu_entry_t *, bool);
+extern errno_t smenu_entry_save(smenu_entry_t *);
+extern errno_t smenu_entry_create(tbarcfg_t *, const char *, const char *,
+    bool, smenu_entry_t **);
+extern errno_t smenu_entry_sep_create(tbarcfg_t *, smenu_entry_t **);
+extern errno_t smenu_entry_destroy(smenu_entry_t *);
+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 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/tbarcfg/include/types/tbarcfg/tbarcfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,51 @@
+/*
+ * 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 Start menu
+ */
+
+#ifndef _TBARCFG_TYPES_TBARCFG_H
+#define _TBARCFG_TYPES_TBARCFG_H
+
+struct tbarcfg;
+typedef struct tbarcfg tbarcfg_t;
+
+struct smenu_entry;
+typedef struct smenu_entry smenu_entry_t;
+
+struct tbarcfg_listener;
+typedef struct tbarcfg_listener tbarcfg_listener_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/tbarcfg/meson.build
===================================================================
--- uspace/lib/tbarcfg/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/tbarcfg/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,37 @@
+#
+# 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.
+#
+
+deps = [ 'sif' ]
+src = files(
+	'src/tbarcfg.c',
+)
+
+test_src = files(
+	'test/main.c',
+	'test/tbarcfg.c',
+)
Index: uspace/lib/tbarcfg/private/tbarcfg.h
===================================================================
--- uspace/lib/tbarcfg/private/tbarcfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/tbarcfg/private/tbarcfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,89 @@
+/*
+ * 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 Taskbar configuration
+ *
+ */
+
+#ifndef _TBARCFG_PRIVATE_TBARCFG_H
+#define _TBARCFG_PRIVATE_TBARCFG_H
+
+#include <adt/list.h>
+#include <sif.h>
+#include <stdbool.h>
+#include <types/tbarcfg/tbarcfg.h>
+
+/** Taskbar configuration */
+struct tbarcfg {
+	/** Repository session */
+	sif_sess_t *repo;
+	/** List of start menu entries (smenu_entry_t) */
+	list_t entries;
+	/** Entries SIF node */
+	sif_node_t *nentries;
+};
+
+/** Start menu entry */
+struct smenu_entry {
+	/** Containing start menu */
+	struct tbarcfg *smenu;
+	/** Link to @c smenu->entries */
+	link_t lentries;
+	/** SIF node (persistent storage) */
+	sif_node_t *nentry;
+	/** Is this a separator entry */
+	bool separator;
+	/** Entry caption (with accelerator markup) */
+	char *caption;
+	/** Command to run */
+	char *cmd;
+	/** Start in terminal */
+	bool terminal;
+};
+
+/** 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 **);
+extern errno_t smenu_entry_sep_new(tbarcfg_t *, sif_node_t *, smenu_entry_t **);
+extern void smenu_entry_delete(smenu_entry_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/tbarcfg/src/tbarcfg.c
===================================================================
--- uspace/lib/tbarcfg/src/tbarcfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/tbarcfg/src/tbarcfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,966 @@
+/*
+ * 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 Taskbar configuration
+ */
+
+#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.
+ *
+ * @param repopath Pathname of the new menu repository
+ * @param rtbcfg Place to store pointer to taskbar configuration
+ * @return EOK on success or an error code
+ */
+errno_t tbarcfg_create(const char *repopath, tbarcfg_t **rtbcfg)
+{
+	tbarcfg_t *tbcfg;
+	sif_sess_t *repo = NULL;
+	sif_node_t *rnode;
+	errno_t rc;
+	sif_trans_t *trans = NULL;
+
+	tbcfg = calloc(1, sizeof(tbarcfg_t));
+	if (tbcfg == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	list_initialize(&tbcfg->entries);
+
+	rc = sif_create(repopath, &repo);
+	if (rc != EOK)
+		goto error;
+
+	tbcfg->repo = repo;
+
+	rnode = sif_get_root(repo);
+
+	rc = sif_trans_begin(repo, &trans);
+	if (rc != EOK)
+		goto error;
+
+	rc = sif_node_append_child(trans, rnode, "entries", &tbcfg->nentries);
+	if (rc != EOK)
+		goto error;
+
+	rc = sif_trans_end(trans);
+	if (rc != EOK)
+		goto error;
+
+	*rtbcfg = tbcfg;
+	return EOK;
+error:
+	if (trans != NULL)
+		sif_trans_abort(trans);
+	if (repo != NULL)
+		sif_close(repo);
+	if (tbcfg != NULL)
+		free(tbcfg);
+	return rc;
+}
+
+/** Open taskbar configuration.
+ *
+ * @param repopath Pathname of the menu repository
+ * @param rtbcfg Place to store pointer to taskbar configuration
+ * @return EOK on success or an error code
+ */
+errno_t tbarcfg_open(const char *repopath, tbarcfg_t **rtbcfg)
+{
+	tbarcfg_t *tbcfg;
+	sif_sess_t *repo = NULL;
+	sif_node_t *rnode;
+	sif_node_t *nentry;
+	const char *ntype;
+	const char *separator;
+	const char *caption;
+	const char *cmd;
+	const char *terminal = NULL;
+	errno_t rc;
+
+	tbcfg = calloc(1, sizeof(tbarcfg_t));
+	if (tbcfg == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	list_initialize(&tbcfg->entries);
+
+	rc = sif_open(repopath, &repo);
+	if (rc != EOK)
+		goto error;
+
+	tbcfg->repo = repo;
+
+	rnode = sif_get_root(repo);
+	tbcfg->nentries = sif_node_first_child(rnode);
+	ntype = sif_node_get_type(tbcfg->nentries);
+	if (str_cmp(ntype, "entries") != 0) {
+		rc = EIO;
+		goto error;
+	}
+
+	nentry = sif_node_first_child(tbcfg->nentries);
+	while (nentry != NULL) {
+		ntype = sif_node_get_type(nentry);
+		if (str_cmp(ntype, "entry") != 0) {
+			rc = EIO;
+			goto error;
+		}
+
+		separator = sif_node_get_attr(nentry, "separator");
+		if (separator != NULL && str_cmp(separator, "y") != 0) {
+			rc = EIO;
+			goto error;
+		}
+
+		if (separator == NULL) {
+			caption = sif_node_get_attr(nentry, "caption");
+			if (caption == NULL) {
+				rc = EIO;
+				goto error;
+			}
+
+			cmd = sif_node_get_attr(nentry, "cmd");
+			if (cmd == NULL) {
+				rc = EIO;
+				goto error;
+			}
+
+			terminal = sif_node_get_attr(nentry, "terminal");
+			if (terminal == NULL)
+				terminal = "n";
+
+			rc = smenu_entry_new(tbcfg, nentry, caption, cmd,
+			    str_cmp(terminal, "y") == 0, NULL);
+			if (rc != EOK)
+				goto error;
+		} else {
+			rc = smenu_entry_sep_new(tbcfg, nentry, NULL);
+			if (rc != EOK)
+				goto error;
+		}
+
+		nentry = sif_node_next_child(nentry);
+	}
+
+	*rtbcfg = tbcfg;
+	return EOK;
+error:
+	if (repo != NULL)
+		sif_close(repo);
+	if (tbcfg != NULL)
+		free(tbcfg);
+	return rc;
+}
+
+/** Close taskbar configuration.
+ *
+ * @param tbcfg Start menu
+ */
+void tbarcfg_close(tbarcfg_t *tbcfg)
+{
+	smenu_entry_t *entry;
+
+	entry = tbarcfg_smenu_first(tbcfg);
+	while (entry != NULL) {
+		smenu_entry_delete(entry);
+		entry = tbarcfg_smenu_first(tbcfg);
+	}
+
+	(void)sif_close(tbcfg->repo);
+	free(tbcfg);
+}
+
+/** Get first start menu entry.
+ *
+ * @param tbcfg Taskbar configuration
+ * @return First entry or @c NULL if the menu is empty
+ */
+smenu_entry_t *tbarcfg_smenu_first(tbarcfg_t *tbcfg)
+{
+	link_t *link;
+
+	link = list_first(&tbcfg->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, smenu_entry_t, lentries);
+}
+
+/** Get next start menu entry.
+ *
+ * @param cur Current entry
+ * @return Next entry or @c NULL if @a cur is the last entry
+ */
+smenu_entry_t *tbarcfg_smenu_next(smenu_entry_t *cur)
+{
+	link_t *link;
+
+	link = list_next(&cur->lentries, &cur->smenu->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, smenu_entry_t, lentries);
+}
+
+/** Get last start menu entry.
+ *
+ * @param tbcfg Taskbar configuration
+ * @return Previous entry or @c NULL if the menu is empty
+ */
+smenu_entry_t *tbarcfg_smenu_last(tbarcfg_t *tbcfg)
+{
+	link_t *link;
+
+	link = list_last(&tbcfg->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, smenu_entry_t, lentries);
+}
+
+/** Get previous start menu entry.
+ *
+ * @param cur Current entry
+ * @return Previous entry or @c NULL if @a cur is the last entry
+ */
+smenu_entry_t *tbarcfg_smenu_prev(smenu_entry_t *cur)
+{
+	link_t *link;
+
+	link = list_prev(&cur->lentries, &cur->smenu->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, smenu_entry_t, lentries);
+}
+
+/** Get start menu entry caption.
+ *
+ * @param entry Start menu entry
+ * @return Caption (with accelerator markup)
+ */
+const char *smenu_entry_get_caption(smenu_entry_t *entry)
+{
+	assert(!entry->separator);
+	return entry->caption;
+}
+
+/** Get start menu entry command.
+ *
+ * @param entry Start menu entry
+ * @return Command to run
+ */
+const char *smenu_entry_get_cmd(smenu_entry_t *entry)
+{
+	assert(!entry->separator);
+	return entry->cmd;
+}
+
+/** Get start menu entry start in terminal flag.
+ *
+ * @param entry Start menu entry
+ * @return Start in terminal flag
+ */
+bool smenu_entry_get_terminal(smenu_entry_t *entry)
+{
+	assert(!entry->separator);
+	return entry->terminal;
+}
+
+/** Get start menu entry separator flag.
+ *
+ * @param entry Start menu entry
+ * @return Separator flag
+ */
+bool smenu_entry_get_separator(smenu_entry_t *entry)
+{
+	return entry->separator;
+}
+
+/** Set start menu entry caption.
+ *
+ * Note: To make the change visible to others and persistent,
+ * you must call @c smenu_entry_save()
+ *
+ * @param entry Start menu entry
+ * @param caption New caption
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t smenu_entry_set_caption(smenu_entry_t *entry, const char *caption)
+{
+	char *dcap;
+
+	assert(!entry->separator);
+
+	dcap = str_dup(caption);
+	if (dcap == NULL)
+		return ENOMEM;
+
+	free(entry->caption);
+	entry->caption = dcap;
+	return EOK;
+}
+
+/** Set start menu entry command.
+ *
+ * Note: To make the change visible to others and persistent,
+ * you must call @c smenu_entry_save()
+ *
+ * @param entry Start menu entry
+ * @param cmd New command
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t smenu_entry_set_cmd(smenu_entry_t *entry, const char *cmd)
+{
+	char *dcmd;
+
+	assert(!entry->separator);
+
+	dcmd = str_dup(cmd);
+	if (dcmd == NULL)
+		return ENOMEM;
+
+	free(entry->cmd);
+	entry->cmd = dcmd;
+	return EOK;
+}
+
+/** Set start menu entry start in terminal flag.
+ *
+ * Note: To make the change visible to others and persistent,
+ * you must call @c smenu_entry_save()
+ *
+ * @param entry Start menu entry
+ * @param terminal Start in terminal flag
+ */
+void smenu_entry_set_terminal(smenu_entry_t *entry, bool terminal)
+{
+	assert(!entry->separator);
+	entry->terminal = terminal;
+}
+
+/** Save start menu entry using transaction.
+ *
+ * @param entry Start menu entry
+ * @param trans Transaction
+ */
+static errno_t smenu_entry_save_trans(smenu_entry_t *entry, sif_trans_t *trans)
+{
+	errno_t rc;
+
+	if (entry->separator) {
+		rc = sif_node_set_attr(trans, entry->nentry, "separator", "y");
+		if (rc != EOK)
+			goto error;
+	} else {
+		sif_node_unset_attr(trans, entry->nentry, "separator");
+
+		rc = sif_node_set_attr(trans, entry->nentry, "cmd", entry->cmd);
+		if (rc != EOK)
+			goto error;
+
+		rc = sif_node_set_attr(trans, entry->nentry, "caption",
+		    entry->caption);
+		if (rc != EOK)
+			goto error;
+
+		rc = sif_node_set_attr(trans, entry->nentry, "terminal",
+		    entry->terminal ? "y" : "n");
+		if (rc != EOK)
+			goto error;
+	}
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Save any changes to start menu entry.
+ *
+ * @param entry Start menu entry
+ */
+errno_t smenu_entry_save(smenu_entry_t *entry)
+{
+	sif_trans_t *trans = NULL;
+	errno_t rc;
+
+	rc = sif_trans_begin(entry->smenu->repo, &trans);
+	if (rc != EOK)
+		goto error;
+
+	rc = smenu_entry_save_trans(entry, trans);
+	if (rc != EOK)
+		goto error;
+
+	rc = sif_trans_end(trans);
+	if (rc != EOK)
+		goto error;
+
+	return EOK;
+error:
+	if (trans != NULL)
+		sif_trans_abort(trans);
+	return rc;
+}
+
+/** Allocate a start menu entry and append it to the start menu (internal).
+ *
+ * This only creates the entry in memory, but does not update the repository.
+ *
+ * @param smenu Start menu
+ * @param nentry Backing SIF node
+ * @param caption Caption
+ * @param cmd Command to run
+ * @param terminal Start in terminal
+ * @param rentry Place to store pointer to new entry or @c NULL
+ */
+errno_t smenu_entry_new(tbarcfg_t *smenu, sif_node_t *nentry,
+    const char *caption, const char *cmd, bool terminal, smenu_entry_t **rentry)
+{
+	smenu_entry_t *entry;
+	errno_t rc;
+
+	entry = calloc(1, sizeof(smenu_entry_t));
+	if (entry == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	entry->nentry = nentry;
+
+	entry->caption = str_dup(caption);
+	if (entry->caption == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	entry->cmd = str_dup(cmd);
+	if (entry->cmd == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	entry->terminal = terminal;
+
+	entry->smenu = smenu;
+	list_append(&entry->lentries, &smenu->entries);
+	if (rentry != NULL)
+		*rentry = entry;
+	return EOK;
+error:
+	if (entry != NULL) {
+		if (entry->caption != NULL)
+			free(entry->caption);
+		if (entry->cmd != NULL)
+			free(entry->cmd);
+		free(entry);
+	}
+
+	return rc;
+}
+
+/** Allocate a start menu separator entry and append it to the start menu
+ * (internal).
+ *
+ * This only creates the entry in memory, but does not update the repository.
+ *
+ * @param smenu Start menu
+ * @param nentry Backing SIF node
+ * @param rentry Place to store pointer to new entry or @c NULL
+ */
+errno_t smenu_entry_sep_new(tbarcfg_t *smenu, sif_node_t *nentry,
+    smenu_entry_t **rentry)
+{
+	smenu_entry_t *entry;
+	errno_t rc;
+
+	entry = calloc(1, sizeof(smenu_entry_t));
+	if (entry == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	entry->nentry = nentry;
+	entry->separator = true;
+
+	entry->smenu = smenu;
+	list_append(&entry->lentries, &smenu->entries);
+	if (rentry != NULL)
+		*rentry = entry;
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Delete start menu entry.
+ *
+ * This only deletes the entry from, but does not update the
+ * repository.
+ *
+ * @param entry Start menu entry
+ */
+void smenu_entry_delete(smenu_entry_t *entry)
+{
+	list_remove(&entry->lentries);
+	if (entry->caption != NULL)
+		free(entry->caption);
+	if (entry->cmd != NULL)
+		free(entry->cmd);
+	free(entry);
+}
+
+/** Create new start menu entry.
+ *
+ * @param smenu Start menu
+ * @param nentry Backing SIF node
+ * @param caption Caption
+ * @param cmd Command to run
+ * @param terminal Start in terminal
+ * @param rentry Place to store pointer to new entry or @c NULL
+ */
+errno_t smenu_entry_create(tbarcfg_t *smenu, const char *caption,
+    const char *cmd, bool terminal, smenu_entry_t **rentry)
+{
+	sif_node_t *nentry;
+	smenu_entry_t *entry;
+	errno_t rc;
+	sif_trans_t *trans = NULL;
+
+	rc = sif_trans_begin(smenu->repo, &trans);
+	if (rc != EOK)
+		goto error;
+
+	rc = sif_node_append_child(trans, smenu->nentries, "entry",
+	    &nentry);
+	if (rc != EOK)
+		goto error;
+
+	rc = sif_node_set_attr(trans, nentry, "cmd", cmd);
+	if (rc != EOK)
+		goto error;
+
+	rc = sif_node_set_attr(trans, nentry, "caption", caption);
+	if (rc != EOK)
+		goto error;
+
+	rc = sif_node_set_attr(trans, nentry, "terminal", terminal ? "y" : "n");
+	if (rc != EOK)
+		goto error;
+
+	rc = smenu_entry_new(smenu, nentry, caption, cmd, terminal, &entry);
+	if (rc != EOK)
+		goto error;
+
+	rc = sif_trans_end(trans);
+	if (rc != EOK)
+		goto error;
+
+	if (rentry != NULL)
+		*rentry = entry;
+	return EOK;
+error:
+	if (trans != NULL)
+		sif_trans_abort(trans);
+	return rc;
+}
+
+/** Create new start menu separator entry.
+ *
+ * @param smenu Start menu
+ * @param nentry Backing SIF node
+ * @param rentry Place to store pointer to new entry or @c NULL
+ */
+errno_t smenu_entry_sep_create(tbarcfg_t *smenu, smenu_entry_t **rentry)
+{
+	sif_node_t *nentry;
+	smenu_entry_t *entry;
+	errno_t rc;
+	sif_trans_t *trans = NULL;
+
+	rc = sif_trans_begin(smenu->repo, &trans);
+	if (rc != EOK)
+		goto error;
+
+	rc = sif_node_append_child(trans, smenu->nentries, "entry",
+	    &nentry);
+	if (rc != EOK)
+		goto error;
+
+	rc = sif_node_set_attr(trans, nentry, "separator", "y");
+	if (rc != EOK)
+		goto error;
+
+	rc = smenu_entry_sep_new(smenu, nentry, &entry);
+	if (rc != EOK)
+		goto error;
+
+	rc = sif_trans_end(trans);
+	if (rc != EOK)
+		goto error;
+
+	if (rentry != NULL)
+		*rentry = entry;
+	return EOK;
+error:
+	if (trans != NULL)
+		sif_trans_abort(trans);
+	return rc;
+}
+
+/** Destroy start menu entry.
+ *
+ * @param entry Start menu entry
+ * @return EOK on success or an error code
+ */
+errno_t smenu_entry_destroy(smenu_entry_t *entry)
+{
+	errno_t rc;
+	sif_trans_t *trans = NULL;
+
+	rc = sif_trans_begin(entry->smenu->repo, &trans);
+	if (rc != EOK)
+		goto error;
+
+	sif_node_destroy(trans, entry->nentry);
+
+	rc = sif_trans_end(trans);
+	if (rc != EOK)
+		goto error;
+
+	smenu_entry_delete(entry);
+	return EOK;
+error:
+	if (trans != NULL)
+		sif_trans_abort(trans);
+	return rc;
+}
+
+/** Move start menu entry up.
+ *
+ * @param entry Start menu entry
+ * @return EOK on success or an error code
+ */
+errno_t smenu_entry_move_up(smenu_entry_t *entry)
+{
+	errno_t rc;
+	sif_trans_t *trans = NULL;
+	sif_node_t *nnode = NULL;
+	sif_node_t *old_node;
+	smenu_entry_t *prev;
+
+	rc = sif_trans_begin(entry->smenu->repo, &trans);
+	if (rc != EOK)
+		goto error;
+
+	prev = tbarcfg_smenu_prev(entry);
+	if (prev == NULL) {
+		/* Entry is already at first position, nothing to do. */
+		return EOK;
+	}
+
+	rc = sif_node_insert_before(trans, prev->nentry, "entry", &nnode);
+	if (rc != EOK)
+		goto error;
+
+	old_node = entry->nentry;
+	entry->nentry = nnode;
+
+	rc = smenu_entry_save_trans(entry, trans);
+	if (rc != EOK) {
+		entry->nentry = old_node;
+		goto error;
+	}
+
+	sif_node_destroy(trans, old_node);
+
+	rc = sif_trans_end(trans);
+	if (rc != EOK) {
+		entry->nentry = old_node;
+		goto error;
+	}
+
+	list_remove(&entry->lentries);
+	list_insert_before(&entry->lentries, &prev->lentries);
+	return EOK;
+error:
+	if (nnode != NULL)
+		sif_node_destroy(trans, nnode);
+	if (trans != NULL)
+		sif_trans_abort(trans);
+	return rc;
+}
+
+/** Move start menu entry down.
+ *
+ * @param entry Start menu entry
+ * @return EOK on success or an error code
+ */
+errno_t smenu_entry_move_down(smenu_entry_t *entry)
+{
+	errno_t rc;
+	sif_trans_t *trans = NULL;
+	sif_node_t *nnode = NULL;
+	sif_node_t *old_node;
+	smenu_entry_t *next;
+
+	rc = sif_trans_begin(entry->smenu->repo, &trans);
+	if (rc != EOK)
+		goto error;
+
+	next = tbarcfg_smenu_next(entry);
+	if (next == NULL) {
+		/* Entry is already at last position, nothing to do. */
+		return EOK;
+	}
+
+	rc = sif_node_insert_after(trans, next->nentry, "entry", &nnode);
+	if (rc != EOK)
+		goto error;
+
+	old_node = entry->nentry;
+	entry->nentry = nnode;
+
+	rc = smenu_entry_save_trans(entry, trans);
+	if (rc != EOK) {
+		entry->nentry = old_node;
+		goto error;
+	}
+
+	sif_node_destroy(trans, old_node);
+
+	rc = sif_trans_end(trans);
+	if (rc != EOK) {
+		entry->nentry = old_node;
+		goto error;
+	}
+
+	list_remove(&entry->lentries);
+	list_insert_after(&entry->lentries, &next->lentries);
+	return EOK;
+error:
+	if (nnode != NULL)
+		sif_node_destroy(trans, nnode);
+	if (trans != NULL)
+		sif_trans_abort(trans);
+	return rc;
+}
+
+/** 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/main.c
===================================================================
--- uspace/lib/tbarcfg/test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/tbarcfg/test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+#include <pcut/pcut.h>
+
+PCUT_INIT;
+
+PCUT_IMPORT(tbarcfg);
+
+PCUT_MAIN();
Index: uspace/lib/tbarcfg/test/tbarcfg.c
===================================================================
--- uspace/lib/tbarcfg/test/tbarcfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/tbarcfg/test/tbarcfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,592 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <pcut/pcut.h>
+#include <tbarcfg/tbarcfg.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(tbarcfg);
+
+typedef struct {
+	bool notified;
+} tbarcfg_test_resp_t;
+
+static void test_cb(void *);
+
+/** Creating, opening and closing taskbar configuration */
+PCUT_TEST(create_open_close)
+{
+	errno_t rc;
+	tbarcfg_t *tbcfg;
+	char fname[L_tmpnam], *p;
+
+	p = tmpnam(fname);
+	PCUT_ASSERT_NOT_NULL(p);
+
+	/* Create new repository */
+	rc = tbarcfg_create(fname, &tbcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	tbarcfg_close(tbcfg);
+
+	/* Re-open the repository */
+
+	rc = tbarcfg_open(fname, &tbcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	tbarcfg_close(tbcfg);
+	remove(fname);
+}
+
+/** Iterating over start menu entries */
+PCUT_TEST(first_next)
+{
+	errno_t rc;
+	tbarcfg_t *tbcfg;
+	char fname[L_tmpnam], *p;
+	smenu_entry_t *e1 = NULL, *e2 = NULL;
+	smenu_entry_t *e;
+
+	p = tmpnam(fname);
+	PCUT_ASSERT_NOT_NULL(p);
+
+	rc = tbarcfg_create(fname, &tbcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = smenu_entry_create(tbcfg, "A", "a", false, &e1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(e1);
+
+	rc = smenu_entry_create(tbcfg, "B", "b", false, &e2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(e2);
+
+	/* Create entry without getting a pointer to it */
+	rc = smenu_entry_create(tbcfg, "C", "c", false, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	e = tbarcfg_smenu_first(tbcfg);
+	PCUT_ASSERT_EQUALS(e1, e);
+	e = tbarcfg_smenu_next(e);
+	PCUT_ASSERT_EQUALS(e2, e);
+	e = tbarcfg_smenu_next(e);
+	PCUT_ASSERT_NOT_NULL(e);
+	e = tbarcfg_smenu_next(e);
+	PCUT_ASSERT_NULL(e);
+
+	tbarcfg_close(tbcfg);
+	remove(fname);
+}
+
+/** Iterating over start menu entries backwards */
+PCUT_TEST(last_prev)
+{
+	errno_t rc;
+	tbarcfg_t *tbcfg;
+	char fname[L_tmpnam], *p;
+	smenu_entry_t *e1 = NULL, *e2 = NULL;
+	smenu_entry_t *e;
+
+	p = tmpnam(fname);
+	PCUT_ASSERT_NOT_NULL(p);
+
+	rc = tbarcfg_create(fname, &tbcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = smenu_entry_create(tbcfg, "A", "a", false, &e1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(e1);
+
+	rc = smenu_entry_create(tbcfg, "B", "b", false, &e2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(e2);
+
+	e = tbarcfg_smenu_last(tbcfg);
+	PCUT_ASSERT_EQUALS(e2, e);
+	e = tbarcfg_smenu_prev(e);
+	PCUT_ASSERT_EQUALS(e1, e);
+	e = tbarcfg_smenu_prev(e);
+	PCUT_ASSERT_NULL(e);
+
+	tbarcfg_close(tbcfg);
+	remove(fname);
+}
+
+/** Separator entry */
+PCUT_TEST(separator)
+{
+	errno_t rc;
+	tbarcfg_t *tbcfg;
+	char fname[L_tmpnam], *p;
+	const char *caption;
+	const char *cmd;
+	smenu_entry_t *e1 = NULL, *e2 = NULL;
+	smenu_entry_t *e;
+
+	p = tmpnam(fname);
+	PCUT_ASSERT_NOT_NULL(p);
+
+	rc = tbarcfg_create(fname, &tbcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = smenu_entry_create(tbcfg, "A", "a", false, &e1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(e1);
+
+	rc = smenu_entry_sep_create(tbcfg, &e2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(e2);
+
+	PCUT_ASSERT_FALSE(smenu_entry_get_separator(e1));
+	PCUT_ASSERT_TRUE(smenu_entry_get_separator(e2));
+
+	tbarcfg_close(tbcfg);
+
+	/* Re-open repository */
+
+	rc = tbarcfg_open(fname, &tbcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	e = tbarcfg_smenu_first(tbcfg);
+	PCUT_ASSERT_NOT_NULL(e);
+
+	/* Check that new values of properties have persisted */
+	PCUT_ASSERT_FALSE(smenu_entry_get_separator(e));
+	caption = smenu_entry_get_caption(e);
+	PCUT_ASSERT_STR_EQUALS("A", caption);
+	cmd = smenu_entry_get_cmd(e);
+	PCUT_ASSERT_STR_EQUALS("a", cmd);
+
+	e = tbarcfg_smenu_next(e);
+
+	/* Check that entry is still a separator */
+	PCUT_ASSERT_TRUE(smenu_entry_get_separator(e));
+
+	tbarcfg_close(tbcfg);
+	remove(fname);
+}
+
+/** Getting menu entry properties */
+PCUT_TEST(get_caption_cmd_term)
+{
+	errno_t rc;
+	tbarcfg_t *tbcfg;
+	char fname[L_tmpnam], *p;
+	smenu_entry_t *e;
+	const char *caption;
+	const char *cmd;
+	bool terminal;
+
+	p = tmpnam(fname);
+	PCUT_ASSERT_NOT_NULL(p);
+
+	rc = tbarcfg_create(fname, &tbcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = smenu_entry_create(tbcfg, "A", "a", false, &e);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	caption = smenu_entry_get_caption(e);
+	PCUT_ASSERT_STR_EQUALS("A", caption);
+	cmd = smenu_entry_get_cmd(e);
+	PCUT_ASSERT_STR_EQUALS("a", cmd);
+	terminal = smenu_entry_get_terminal(e);
+	PCUT_ASSERT_FALSE(terminal);
+
+	tbarcfg_close(tbcfg);
+	remove(fname);
+}
+
+/** Setting menu entry properties */
+PCUT_TEST(set_caption_cmd_term)
+{
+	errno_t rc;
+	tbarcfg_t *tbcfg;
+	char fname[L_tmpnam], *p;
+	smenu_entry_t *e;
+	const char *caption;
+	const char *cmd;
+	bool terminal;
+
+	p = tmpnam(fname);
+	PCUT_ASSERT_NOT_NULL(p);
+
+	rc = tbarcfg_create(fname, &tbcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = smenu_entry_create(tbcfg, "A", "a", false, &e);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	caption = smenu_entry_get_caption(e);
+	PCUT_ASSERT_STR_EQUALS("A", caption);
+	cmd = smenu_entry_get_cmd(e);
+	PCUT_ASSERT_STR_EQUALS("a", cmd);
+	terminal = smenu_entry_get_terminal(e);
+	PCUT_ASSERT_FALSE(terminal);
+
+	/* Set properties */
+	rc = smenu_entry_set_caption(e, "B");
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	rc = smenu_entry_set_cmd(e, "b");
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	smenu_entry_set_terminal(e, true);
+
+	rc = smenu_entry_save(e);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Check that properties have been set */
+	caption = smenu_entry_get_caption(e);
+	PCUT_ASSERT_STR_EQUALS("B", caption);
+	cmd = smenu_entry_get_cmd(e);
+	PCUT_ASSERT_STR_EQUALS("b", cmd);
+	terminal = smenu_entry_get_terminal(e);
+	PCUT_ASSERT_TRUE(terminal);
+
+	tbarcfg_close(tbcfg);
+
+	/* Re-open repository */
+
+	rc = tbarcfg_open(fname, &tbcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	e = tbarcfg_smenu_first(tbcfg);
+	PCUT_ASSERT_NOT_NULL(e);
+
+	/* Check that new values of properties have persisted */
+	caption = smenu_entry_get_caption(e);
+	PCUT_ASSERT_STR_EQUALS("B", caption);
+	cmd = smenu_entry_get_cmd(e);
+	PCUT_ASSERT_STR_EQUALS("b", cmd);
+
+	tbarcfg_close(tbcfg);
+	remove(fname);
+}
+
+/** Create start menu entry */
+PCUT_TEST(entry_create)
+{
+	errno_t rc;
+	tbarcfg_t *tbcfg;
+	char fname[L_tmpnam], *p;
+	smenu_entry_t *e;
+	const char *caption;
+	const char *cmd;
+	bool terminal;
+
+	p = tmpnam(fname);
+	PCUT_ASSERT_NOT_NULL(p);
+
+	rc = tbarcfg_create(fname, &tbcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = smenu_entry_create(tbcfg, "A", "a", false, &e);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(e);
+
+	caption = smenu_entry_get_caption(e);
+	PCUT_ASSERT_STR_EQUALS("A", caption);
+	cmd = smenu_entry_get_cmd(e);
+	PCUT_ASSERT_STR_EQUALS("a", cmd);
+	terminal = smenu_entry_get_terminal(e);
+	PCUT_ASSERT_FALSE(terminal);
+
+	smenu_entry_destroy(e);
+
+	rc = smenu_entry_create(tbcfg, "B", "b", true, &e);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(e);
+
+	caption = smenu_entry_get_caption(e);
+	PCUT_ASSERT_STR_EQUALS("B", caption);
+	cmd = smenu_entry_get_cmd(e);
+	PCUT_ASSERT_STR_EQUALS("b", cmd);
+	terminal = smenu_entry_get_terminal(e);
+	PCUT_ASSERT_TRUE(terminal);
+
+	smenu_entry_destroy(e);
+
+	tbarcfg_close(tbcfg);
+	remove(fname);
+}
+
+/** Destroy start menu entry */
+PCUT_TEST(entry_destroy)
+{
+	errno_t rc;
+	tbarcfg_t *tbcfg;
+	char fname[L_tmpnam], *p;
+	smenu_entry_t *e, *f;
+
+	p = tmpnam(fname);
+	PCUT_ASSERT_NOT_NULL(p);
+
+	rc = tbarcfg_create(fname, &tbcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = smenu_entry_create(tbcfg, "A", "a", false, &e);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	f = tbarcfg_smenu_first(tbcfg);
+	PCUT_ASSERT_EQUALS(e, f);
+
+	rc = smenu_entry_destroy(e);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	f = tbarcfg_smenu_first(tbcfg);
+	PCUT_ASSERT_NULL(f);
+
+	tbarcfg_close(tbcfg);
+	remove(fname);
+}
+
+/** Move start menu entry up */
+PCUT_TEST(entry_move_up)
+{
+	errno_t rc;
+	tbarcfg_t *tbcfg;
+	char fname[L_tmpnam], *p;
+	smenu_entry_t *e1, *e2, *e3;
+	smenu_entry_t *f;
+	const char *caption;
+	const char *cmd;
+
+	p = tmpnam(fname);
+	PCUT_ASSERT_NOT_NULL(p);
+
+	rc = tbarcfg_create(fname, &tbcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = smenu_entry_create(tbcfg, "A", "a", false, &e1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = smenu_entry_create(tbcfg, "B", "b", false, &e2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = smenu_entry_create(tbcfg, "C", "c", false, &e3);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	f = tbarcfg_smenu_first(tbcfg);
+	PCUT_ASSERT_EQUALS(e1, f);
+
+	/* Moving the first entry up should have no effect */
+
+	rc = smenu_entry_move_up(e1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	f = tbarcfg_smenu_first(tbcfg);
+	PCUT_ASSERT_EQUALS(e1, f);
+
+	/* Moving the second entry up should move it to first position */
+
+	rc = smenu_entry_move_up(e2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	f = tbarcfg_smenu_first(tbcfg);
+	PCUT_ASSERT_EQUALS(e2, f);
+
+	/* Moving the last entry up should move it to second position */
+
+	rc = smenu_entry_move_up(e3);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	f = tbarcfg_smenu_first(tbcfg);
+	PCUT_ASSERT_EQUALS(e2, f);
+
+	f = tbarcfg_smenu_next(f);
+	PCUT_ASSERT_EQUALS(e3, f);
+
+	f = tbarcfg_smenu_next(f);
+	PCUT_ASSERT_EQUALS(e1, f);
+
+	tbarcfg_close(tbcfg);
+
+	/* Re-open repository */
+
+	rc = tbarcfg_open(fname, &tbcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Check that new order of entries persisted */
+
+	f = tbarcfg_smenu_first(tbcfg);
+	PCUT_ASSERT_NOT_NULL(f);
+
+	caption = smenu_entry_get_caption(f);
+	PCUT_ASSERT_STR_EQUALS("B", caption);
+	cmd = smenu_entry_get_cmd(f);
+	PCUT_ASSERT_STR_EQUALS("b", cmd);
+
+	f = tbarcfg_smenu_next(f);
+	PCUT_ASSERT_NOT_NULL(f);
+
+	caption = smenu_entry_get_caption(f);
+	PCUT_ASSERT_STR_EQUALS("C", caption);
+	cmd = smenu_entry_get_cmd(f);
+	PCUT_ASSERT_STR_EQUALS("c", cmd);
+
+	f = tbarcfg_smenu_next(f);
+	PCUT_ASSERT_NOT_NULL(f);
+
+	caption = smenu_entry_get_caption(f);
+	PCUT_ASSERT_STR_EQUALS("A", caption);
+	cmd = smenu_entry_get_cmd(f);
+	PCUT_ASSERT_STR_EQUALS("a", cmd);
+
+	tbarcfg_close(tbcfg);
+	remove(fname);
+}
+
+/** Move start menu entry down */
+PCUT_TEST(entry_move_down)
+{
+	errno_t rc;
+	tbarcfg_t *tbcfg;
+	char fname[L_tmpnam], *p;
+	smenu_entry_t *e1, *e2, *e3;
+	smenu_entry_t *f;
+	const char *caption;
+	const char *cmd;
+
+	p = tmpnam(fname);
+	PCUT_ASSERT_NOT_NULL(p);
+
+	rc = tbarcfg_create(fname, &tbcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = smenu_entry_create(tbcfg, "A", "a", false, &e1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = smenu_entry_create(tbcfg, "B", "b", false, &e2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = smenu_entry_create(tbcfg, "C", "c", false, &e3);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	f = tbarcfg_smenu_last(tbcfg);
+	PCUT_ASSERT_EQUALS(e3, f);
+
+	/* Moving the last entry down should have no effect */
+
+	rc = smenu_entry_move_down(e3);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	f = tbarcfg_smenu_last(tbcfg);
+	PCUT_ASSERT_EQUALS(e3, f);
+
+	/* Moving the second entry down should move it to last position */
+
+	rc = smenu_entry_move_down(e2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	f = tbarcfg_smenu_last(tbcfg);
+	PCUT_ASSERT_EQUALS(e2, f);
+
+	/* Moving the first entry down should move it to second position */
+
+	rc = smenu_entry_move_down(e1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	f = tbarcfg_smenu_last(tbcfg);
+	PCUT_ASSERT_EQUALS(e2, f);
+
+	f = tbarcfg_smenu_prev(f);
+	PCUT_ASSERT_EQUALS(e1, f);
+
+	f = tbarcfg_smenu_prev(f);
+	PCUT_ASSERT_EQUALS(e3, f);
+
+	tbarcfg_close(tbcfg);
+
+	/* Re-open repository */
+
+	rc = tbarcfg_open(fname, &tbcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Check that new order of entries persisted */
+
+	f = tbarcfg_smenu_first(tbcfg);
+	PCUT_ASSERT_NOT_NULL(f);
+
+	caption = smenu_entry_get_caption(f);
+	PCUT_ASSERT_STR_EQUALS("C", caption);
+	cmd = smenu_entry_get_cmd(f);
+	PCUT_ASSERT_STR_EQUALS("c", cmd);
+
+	f = tbarcfg_smenu_next(f);
+	PCUT_ASSERT_NOT_NULL(f);
+
+	caption = smenu_entry_get_caption(f);
+	PCUT_ASSERT_STR_EQUALS("A", caption);
+	cmd = smenu_entry_get_cmd(f);
+	PCUT_ASSERT_STR_EQUALS("a", cmd);
+
+	f = tbarcfg_smenu_next(f);
+	PCUT_ASSERT_NOT_NULL(f);
+
+	caption = smenu_entry_get_caption(f);
+	PCUT_ASSERT_STR_EQUALS("B", caption);
+	cmd = smenu_entry_get_cmd(f);
+	PCUT_ASSERT_STR_EQUALS("b", cmd);
+
+	tbarcfg_close(tbcfg);
+	remove(fname);
+}
+
+/** 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/types/ui/clickmatic.h
===================================================================
--- uspace/lib/ui/include/types/ui/clickmatic.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/types/ui/clickmatic.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libui
+ * @{
+ */
+/**
+ * @file Clickmatic
+ */
+
+#ifndef _UI_TYPES_CLICKMATIC_H
+#define _UI_TYPES_CLICKMATIC_H
+
+struct ui_clickmatic;
+typedef struct ui_clickmatic ui_clickmatic_t;
+
+/** Clickmatic callbacks */
+typedef struct ui_clickmatic_cb {
+	void (*clicked)(ui_clickmatic_t *, void *);
+} ui_clickmatic_cb_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/types/ui/control.h
===================================================================
--- uspace/lib/ui/include/types/ui/control.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/types/ui/control.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -56,5 +56,5 @@
 	ui_evclaim_t (*pos_event)(void *, pos_event_t *);
 	/** Unfocus */
-	void (*unfocus)(void *);
+	void (*unfocus)(void *, unsigned);
 } ui_control_ops_t;
 
Index: uspace/lib/ui/include/types/ui/filelist.h
===================================================================
--- uspace/lib/ui/include/types/ui/filelist.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/types/ui/filelist.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libui
+ * @{
+ */
+/**
+ * @file File list
+ */
+
+#ifndef _UI_TYPES_FILELIST_H
+#define _UI_TYPES_FILELIST_H
+
+struct ui_file_list;
+typedef struct ui_file_list ui_file_list_t;
+
+struct ui_file_list_entry;
+typedef struct ui_file_list_entry ui_file_list_entry_t;
+
+struct ui_file_list_entry_attr;
+typedef struct ui_file_list_entry_attr ui_file_list_entry_attr_t;
+
+/** File list callbacks */
+typedef struct ui_file_list_cb {
+	/** Request file list activation */
+	void (*activate_req)(ui_file_list_t *, void *);
+	/** File was selected */
+	void (*selected)(ui_file_list_t *, void *, const char *);
+} ui_file_list_cb_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/types/ui/list.h
===================================================================
--- uspace/lib/ui/include/types/ui/list.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/types/ui/list.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,72 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file List control
+ */
+
+#ifndef _UI_TYPES_LIST_H
+#define _UI_TYPES_LIST_H
+
+#include <gfx/color.h>
+
+struct ui_list;
+typedef struct ui_list ui_list_t;
+
+struct ui_list_entry;
+typedef struct ui_list_entry ui_list_entry_t;
+
+/** List entry attributes */
+typedef struct ui_list_entry_attr {
+	/** Entry caption */
+	const char *caption;
+	/** User argument */
+	void *arg;
+	/** Entry color or @c NULL to use default color */
+	gfx_color_t *color;
+	/** Entry background color or @c NULL to use default color */
+	gfx_color_t *bgcolor;
+} ui_list_entry_attr_t;
+
+/** List callbacks */
+typedef struct ui_list_cb {
+	/** Request list activation */
+	void (*activate_req)(ui_list_t *, void *);
+	/** Entry was selected */
+	void (*selected)(ui_list_entry_t *, void *);
+	/** Compare two entries (for sorting) */
+	int (*compare)(ui_list_entry_t *, ui_list_entry_t *);
+} ui_list_cb_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/types/ui/menu.h
===================================================================
--- uspace/lib/ui/include/types/ui/menu.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/types/ui/menu.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -37,6 +37,20 @@
 #define _UI_TYPES_MENU_H
 
+#include <types/common.h>
+
 struct ui_menu;
 typedef struct ui_menu ui_menu_t;
+
+/** Menu callbacks */
+typedef struct ui_menu_cb {
+	/** Left arrow pressed */
+	void (*left)(ui_menu_t *, void *, sysarg_t);
+	/** Right arrow pressed */
+	void (*right)(ui_menu_t *, void *, sysarg_t);
+	/** Request menu closure */
+	void (*close_req)(ui_menu_t *, void *);
+	/** Accelerator key pressed */
+	void (*press_accel)(ui_menu_t *, void *, char32_t, sysarg_t);
+} ui_menu_cb_t;
 
 #endif
Index: uspace/lib/ui/include/types/ui/menubar.h
===================================================================
--- uspace/lib/ui/include/types/ui/menubar.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/types/ui/menubar.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -40,4 +40,12 @@
 typedef struct ui_menu_bar ui_menu_bar_t;
 
+/** Menu bar callbacks */
+typedef struct ui_menu_bar_cb {
+	/** Activated */
+	void (*activate)(ui_menu_bar_t *, void *);
+	/** Deactivated */
+	void (*deactivate)(ui_menu_bar_t *, void *);
+} ui_menu_bar_cb_t;
+
 #endif
 
Index: uspace/lib/ui/include/types/ui/menudd.h
===================================================================
--- uspace/lib/ui/include/types/ui/menudd.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/types/ui/menudd.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,45 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Menu
+ */
+
+#ifndef _UI_TYPES_MENUDD_H
+#define _UI_TYPES_MENUDD_H
+
+struct ui_menu_dd;
+typedef struct ui_menu_dd ui_menu_dd_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/types/ui/paint.h
===================================================================
--- uspace/lib/ui/include/types/ui/paint.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/types/ui/paint.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,4 +36,7 @@
 #ifndef _UI_TYPES_PAINT_H
 #define _UI_TYPES_PAINT_H
+
+#include <gfx/color.h>
+#include <gfx/font.h>
 
 /** Filled circle parts */
@@ -81,4 +84,20 @@
 } ui_box_style_t;
 
+/** UI text formatting */
+typedef struct {
+	/** Text font */
+	gfx_font_t *font;
+	/** Standard color */
+	gfx_color_t *color;
+	/** Highlight color */
+	gfx_color_t *hgl_color;
+	/** Horizontal alignment */
+	gfx_halign_t halign;
+	/** Width available for the text */
+	gfx_coord_t width;
+	/** Vertical alignment */
+	gfx_valign_t valign;
+} ui_text_fmt_t;
+
 #endif
 
Index: uspace/lib/ui/include/types/ui/pbutton.h
===================================================================
--- uspace/lib/ui/include/types/ui/pbutton.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/types/ui/pbutton.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -37,11 +37,27 @@
 #define _UI_TYPES_PBUTTON_H
 
+#include <errno.h>
+#include <gfx/coord.h>
+
 struct ui_pbutton;
 typedef struct ui_pbutton ui_pbutton_t;
+
+/** UI push button flags */
+typedef enum {
+	/** Do not depress the button in text mode */
+	ui_pbf_no_text_depress = 0x1
+} ui_pbutton_flags_t;
 
 /** Push button callbacks */
 typedef struct ui_pbutton_cb {
 	void (*clicked)(ui_pbutton_t *, void *);
+	void (*down)(ui_pbutton_t *, void *);
+	void (*up)(ui_pbutton_t *, void *);
 } ui_pbutton_cb_t;
+
+/** Push button decoration ops */
+typedef struct ui_pbutton_decor_ops {
+	errno_t (*paint)(ui_pbutton_t *, void *, gfx_coord2_t *);
+} ui_pbutton_decor_ops_t;
 
 #endif
Index: uspace/lib/ui/include/types/ui/popup.h
===================================================================
--- uspace/lib/ui/include/types/ui/popup.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/types/ui/popup.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -40,4 +40,5 @@
 #include <io/kbd_event.h>
 #include <io/pos_event.h>
+#include <types/common.h>
 
 struct ui_popup;
@@ -50,4 +51,6 @@
 	/** Placement rectangle close to which popup should be placed */
 	gfx_rect_t place;
+	/** Input device ID associated with the popup's seat */
+	sysarg_t idev_id;
 } ui_popup_params_t;
 
Index: uspace/lib/ui/include/types/ui/scrollbar.h
===================================================================
--- uspace/lib/ui/include/types/ui/scrollbar.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/types/ui/scrollbar.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libui
+ * @{
+ */
+/**
+ * @file Scrollbar
+ */
+
+#ifndef _UI_TYPES_SCROLLBAR_H
+#define _UI_TYPES_SCROLLBAR_H
+
+#include <gfx/coord.h>
+
+struct ui_scrollbar;
+typedef struct ui_scrollbar ui_scrollbar_t;
+
+/** Scrollbar direction */
+typedef enum {
+	/** Horizontal */
+	ui_sbd_horiz,
+	/** Vertical */
+	ui_sbd_vert
+} ui_scrollbar_dir_t;
+
+/** Scrollbar callbacks */
+typedef struct ui_scrollbar_cb {
+	void (*up)(ui_scrollbar_t *, void *);
+	void (*down)(ui_scrollbar_t *, void *);
+	void (*page_up)(ui_scrollbar_t *, void *);
+	void (*page_down)(ui_scrollbar_t *, void *);
+	void (*moved)(ui_scrollbar_t *, void *, gfx_coord_t);
+} ui_scrollbar_cb_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/types/ui/selectdialog.h
===================================================================
--- uspace/lib/ui/include/types/ui/selectdialog.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/types/ui/selectdialog.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,67 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Select dialog
+ */
+
+#ifndef _UI_TYPES_SELECTDIALOG_H
+#define _UI_TYPES_SELECTDIALOG_H
+
+#include <errno.h>
+#include <io/kbd_event.h>
+#include <io/pos_event.h>
+
+struct ui_select_dialog;
+typedef struct ui_select_dialog ui_select_dialog_t;
+
+/** Select dialog parameters */
+typedef struct {
+	/** Window caption */
+	const char *caption;
+	/** Prompt text */
+	const char *prompt;
+} ui_select_dialog_params_t;
+
+/** Select dialog callback */
+typedef struct ui_select_dialog_cb {
+	/** OK button was pressed */
+	void (*bok)(ui_select_dialog_t *, void *, void *);
+	/** Cancel button was pressed */
+	void (*bcancel)(ui_select_dialog_t *, void *);
+	/** Window closure requested (e.g. via close button) */
+	void (*close)(ui_select_dialog_t *, void *);
+} ui_select_dialog_cb_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/types/ui/tab.h
===================================================================
--- uspace/lib/ui/include/types/ui/tab.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/types/ui/tab.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,45 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Tab
+ */
+
+#ifndef _UI_TYPES_TAB_H
+#define _UI_TYPES_TAB_H
+
+struct ui_menu;
+typedef struct ui_tab ui_tab_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/types/ui/tabset.h
===================================================================
--- uspace/lib/ui/include/types/ui/tabset.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/types/ui/tabset.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,45 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Menu bar
+ */
+
+#ifndef _UI_TYPES_TABSET_H
+#define _UI_TYPES_TABSET_H
+
+struct ui_tab_set;
+typedef struct ui_tab_set ui_tab_set_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/types/ui/testctl.h
===================================================================
--- uspace/lib/ui/include/types/ui/testctl.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/types/ui/testctl.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,74 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Label
+ */
+
+#ifndef _UI_TYPES_TESTCTL_H
+#define _UI_TYPES_TESTCTL_H
+
+struct ui_test_ctl;
+typedef struct ui_test_ctl ui_test_ctl_t;
+
+/** Test control response */
+typedef struct {
+	/** Claim to return */
+	ui_evclaim_t claim;
+	/** Result code to return */
+	errno_t rc;
+
+	/** @c true iff destroy was called */
+	bool destroy;
+
+	/** @c true iff paint was called */
+	bool paint;
+
+	/** @c true iff kbd_event was called */
+	bool kbd;
+	/** Keyboard event that was sent */
+	kbd_event_t kevent;
+
+	/** @c true iff pos_event was called */
+	bool pos;
+	/** Position event that was sent */
+	pos_event_t pevent;
+
+	/** @c true iff unfocus was called */
+	bool unfocus;
+	/** Number of remaining foci that was sent */
+	unsigned unfocus_nfocus;
+} ui_tc_resp_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/types/ui/ui.h
===================================================================
--- uspace/lib/ui/include/types/ui/ui.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/types/ui/ui.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -43,7 +43,9 @@
 
 /** Use the default display service (argument to ui_create()) */
-#define UI_DISPLAY_DEFAULT NULL
+#define UI_DISPLAY_DEFAULT "disp@"
 /** Use the default console service (argument to ui_create()) */
 #define UI_CONSOLE_DEFAULT "cons@"
+/** Use any available service (argument to ui_create()) */
+#define UI_ANY_DEFAULT "@"
 /** Use dummy output (argument to ui_create()) */
 #define UI_DISPLAY_NULL "null@"
@@ -57,4 +59,6 @@
 	/** Console */
 	ui_ws_console,
+	/** Any non-dummy output backend */
+	ui_ws_any,
 	/** Dummy output */
 	ui_ws_null
Index: uspace/lib/ui/include/types/ui/wdecor.h
===================================================================
--- uspace/lib/ui/include/types/ui/wdecor.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/types/ui/wdecor.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -38,4 +38,5 @@
 
 #include <gfx/coord.h>
+#include <types/common.h>
 #include <types/ui/cursor.h>
 
@@ -51,10 +52,17 @@
 	/** Window has a title bar */
 	ui_wds_titlebar = 0x2,
+	/** Window has a system menu handle */
+	ui_wds_sysmenu_hdl = 0x4,
+	/** Window has a minimize button */
+	ui_wds_minimize_btn = 0x8,
+	/** Window has a maximize button */
+	ui_wds_maximize_btn = 0x10,
 	/** Window has a close button */
-	ui_wds_close_btn = 0x4,
+	ui_wds_close_btn = 0x20,
 	/** Window is resizable */
-	ui_wds_resizable = 0x8,
+	ui_wds_resizable = 0x40,
 	/** Window is decorated (default decoration) */
-	ui_wds_decorated = ui_wds_frame | ui_wds_titlebar | ui_wds_close_btn
+	ui_wds_decorated = ui_wds_frame | ui_wds_titlebar | ui_wds_sysmenu_hdl |
+	    ui_wds_minimize_btn | ui_wds_close_btn
 } ui_wdecor_style_t;
 
@@ -76,8 +84,15 @@
 /** Window decoration callbacks */
 typedef struct ui_wdecor_cb {
+	void (*sysmenu_open)(ui_wdecor_t *, void *, sysarg_t);
+	void (*sysmenu_left)(ui_wdecor_t *, void *, sysarg_t);
+	void (*sysmenu_right)(ui_wdecor_t *, void *, sysarg_t);
+	void (*sysmenu_accel)(ui_wdecor_t *, void *, char32_t, sysarg_t);
+	void (*minimize)(ui_wdecor_t *, void *);
+	void (*maximize)(ui_wdecor_t *, void *);
+	void (*unmaximize)(ui_wdecor_t *, void *);
 	void (*close)(ui_wdecor_t *, void *);
-	void (*move)(ui_wdecor_t *, void *, gfx_coord2_t *);
+	void (*move)(ui_wdecor_t *, void *, gfx_coord2_t *, sysarg_t);
 	void (*resize)(ui_wdecor_t *, void *, ui_wdecor_rsztype_t,
-	    gfx_coord2_t *);
+	    gfx_coord2_t *, sysarg_t);
 	void (*set_cursor)(ui_wdecor_t *, void *, ui_stock_cursor_t);
 } ui_wdecor_cb_t;
Index: uspace/lib/ui/include/types/ui/window.h
===================================================================
--- uspace/lib/ui/include/types/ui/window.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/types/ui/window.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -40,4 +40,5 @@
 #include <io/kbd_event.h>
 #include <io/pos_event.h>
+#include <types/common.h>
 #include <types/ui/wdecor.h>
 
@@ -66,5 +67,11 @@
 typedef enum {
 	/** Popup window */
-	ui_wndf_popup = 0x1
+	ui_wndf_popup = 0x1,
+	/** Topmost window */
+	ui_wndf_topmost = 0x2,
+	/** Special system window */
+	ui_wndf_system = 0x4,
+	/** Maximized windows should avoid this window */
+	ui_wndf_avoid = 0x8
 } ui_wnd_flags_t;
 
@@ -83,14 +90,20 @@
 	/** Parent rectangle for popup windows */
 	gfx_rect_t prect;
+	/** Input device associated with the window's seat */
+	sysarg_t idev_id;
 } ui_wnd_params_t;
 
 /** Window callbacks */
 typedef struct ui_window_cb {
+	void (*sysmenu)(ui_window_t *, void *, sysarg_t);
+	void (*minimize)(ui_window_t *, void *);
+	void (*maximize)(ui_window_t *, void *);
+	void (*unmaximize)(ui_window_t *, void *);
 	void (*close)(ui_window_t *, void *);
-	void (*focus)(ui_window_t *, void *);
+	void (*focus)(ui_window_t *, void *, unsigned);
 	void (*kbd)(ui_window_t *, void *, kbd_event_t *);
 	errno_t (*paint)(ui_window_t *, void *);
 	void (*pos)(ui_window_t *, void *, pos_event_t *);
-	void (*unfocus)(ui_window_t *, void *);
+	void (*unfocus)(ui_window_t *, void *, unsigned);
 } ui_window_cb_t;
 
Index: uspace/lib/ui/include/ui/accel.h
===================================================================
--- uspace/lib/ui/include/ui/accel.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/ui/accel.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libui
+ * @{
+ */
+/**
+ * @file Accelerator processing
+ */
+
+#ifndef _UI_ACCEL_H
+#define _UI_ACCEL_H
+
+#include <errno.h>
+#include <uchar.h>
+
+extern errno_t ui_accel_process(const char *, char **, char **);
+extern char32_t ui_accel_get(const char *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/ui/checkbox.h
===================================================================
--- uspace/lib/ui/include/ui/checkbox.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/ui/checkbox.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -52,4 +52,6 @@
 extern void ui_checkbox_set_cb(ui_checkbox_t *, ui_checkbox_cb_t *, void *);
 extern void ui_checkbox_set_rect(ui_checkbox_t *, gfx_rect_t *);
+extern bool ui_checkbox_get_checked(ui_checkbox_t *);
+extern void ui_checkbox_set_checked(ui_checkbox_t *, bool);
 extern errno_t ui_checkbox_paint(ui_checkbox_t *);
 extern void ui_checkbox_press(ui_checkbox_t *);
Index: uspace/lib/ui/include/ui/clickmatic.h
===================================================================
--- uspace/lib/ui/include/ui/clickmatic.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/ui/clickmatic.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libui
+ * @{
+ */
+/**
+ * @file Clickmatic
+ */
+
+#ifndef _UI_CLICKMATIC_H
+#define _UI_CLICKMATIC_H
+
+#include <errno.h>
+#include <gfx/coord.h>
+#include <types/ui/clickmatic.h>
+#include <types/ui/ui.h>
+
+extern errno_t ui_clickmatic_create(ui_t *, ui_clickmatic_t **);
+extern void ui_clickmatic_set_cb(ui_clickmatic_t *, ui_clickmatic_cb_t *,
+    void *);
+extern void ui_clickmatic_destroy(ui_clickmatic_t *);
+extern void ui_clickmatic_press(ui_clickmatic_t *);
+extern void ui_clickmatic_release(ui_clickmatic_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/ui/control.h
===================================================================
--- uspace/lib/ui/include/ui/control.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/ui/control.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -49,5 +49,5 @@
 extern ui_evclaim_t ui_control_kbd_event(ui_control_t *, kbd_event_t *);
 extern ui_evclaim_t ui_control_pos_event(ui_control_t *, pos_event_t *);
-extern void ui_control_unfocus(ui_control_t *);
+extern void ui_control_unfocus(ui_control_t *, unsigned);
 
 #endif
Index: uspace/lib/ui/include/ui/entry.h
===================================================================
--- uspace/lib/ui/include/ui/entry.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/ui/entry.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -54,4 +54,5 @@
 extern const char *ui_entry_get_text(ui_entry_t *);
 extern errno_t ui_entry_paint(ui_entry_t *);
+extern errno_t ui_entry_insert_str(ui_entry_t *, const char *);
 extern void ui_entry_activate(ui_entry_t *);
 extern void ui_entry_deactivate(ui_entry_t *);
Index: uspace/lib/ui/include/ui/filelist.h
===================================================================
--- uspace/lib/ui/include/ui/filelist.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/ui/filelist.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,60 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file File list
+ */
+
+#ifndef _UI_FILELIST_H
+#define _UI_FILELIST_H
+
+#include <errno.h>
+#include <gfx/coord.h>
+#include <ui/control.h>
+#include <ui/window.h>
+#include <stdbool.h>
+#include <types/ui/filelist.h>
+
+extern errno_t ui_file_list_create(ui_window_t *, bool, ui_file_list_t **);
+extern void ui_file_list_destroy(ui_file_list_t *);
+extern ui_control_t *ui_file_list_ctl(ui_file_list_t *);
+extern void ui_file_list_set_cb(ui_file_list_t *, ui_file_list_cb_t *, void *);
+extern void ui_file_list_set_rect(ui_file_list_t *, gfx_rect_t *);
+extern errno_t ui_file_list_read_dir(ui_file_list_t *, const char *);
+extern errno_t ui_file_list_activate(ui_file_list_t *);
+extern void ui_file_list_deactivate(ui_file_list_t *);
+extern errno_t ui_file_list_open(ui_file_list_t *, ui_file_list_entry_t *);
+extern ui_file_list_entry_t *ui_file_list_get_cursor(ui_file_list_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/ui/fixed.h
===================================================================
--- uspace/lib/ui/include/ui/fixed.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/ui/fixed.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -51,5 +51,5 @@
 extern ui_evclaim_t ui_fixed_kbd_event(ui_fixed_t *, kbd_event_t *);
 extern ui_evclaim_t ui_fixed_pos_event(ui_fixed_t *, pos_event_t *);
-extern void ui_fixed_unfocus(ui_fixed_t *);
+extern void ui_fixed_unfocus(ui_fixed_t *, unsigned);
 
 #endif
Index: uspace/lib/ui/include/ui/list.h
===================================================================
--- uspace/lib/ui/include/ui/list.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/ui/list.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,77 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file List
+ */
+
+#ifndef _UI_LIST_H
+#define _UI_LIST_H
+
+#include <errno.h>
+#include <gfx/coord.h>
+#include <ui/control.h>
+#include <ui/window.h>
+#include <stdbool.h>
+#include <types/ui/list.h>
+
+extern errno_t ui_list_create(ui_window_t *, bool, ui_list_t **);
+extern void ui_list_destroy(ui_list_t *);
+extern ui_control_t *ui_list_ctl(ui_list_t *);
+extern void ui_list_set_cb(ui_list_t *, ui_list_cb_t *, void *);
+extern void *ui_list_get_cb_arg(ui_list_t *);
+extern void ui_list_set_rect(ui_list_t *, gfx_rect_t *);
+extern errno_t ui_list_activate(ui_list_t *);
+extern void ui_list_deactivate(ui_list_t *);
+extern ui_list_entry_t *ui_list_get_cursor(ui_list_t *);
+extern void ui_list_set_cursor(ui_list_t *, ui_list_entry_t *);
+extern void ui_list_entry_attr_init(ui_list_entry_attr_t *);
+extern errno_t ui_list_entry_append(ui_list_t *,
+    ui_list_entry_attr_t *, ui_list_entry_t **);
+extern void ui_list_entry_move_up(ui_list_entry_t *);
+extern void ui_list_entry_move_down(ui_list_entry_t *);
+extern void ui_list_entry_delete(ui_list_entry_t *);
+extern void *ui_list_entry_get_arg(ui_list_entry_t *);
+extern ui_list_t *ui_list_entry_get_list(ui_list_entry_t *);
+extern errno_t ui_list_entry_set_caption(ui_list_entry_t *, const char *);
+extern size_t ui_list_entries_cnt(ui_list_t *);
+extern errno_t ui_list_sort(ui_list_t *);
+extern void ui_list_cursor_center(ui_list_t *, ui_list_entry_t *);
+extern ui_list_entry_t *ui_list_first(ui_list_t *);
+extern ui_list_entry_t *ui_list_last(ui_list_t *);
+extern ui_list_entry_t *ui_list_next(ui_list_entry_t *);
+extern ui_list_entry_t *ui_list_prev(ui_list_entry_t *);
+extern bool ui_list_is_active(ui_list_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/ui/menu.h
===================================================================
--- uspace/lib/ui/include/ui/menu.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/ui/menu.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,20 +39,25 @@
 #include <errno.h>
 #include <gfx/coord.h>
+#include <io/kbd_event.h>
 #include <io/pos_event.h>
+#include <stdbool.h>
+#include <types/common.h>
+#include <types/ui/event.h>
 #include <types/ui/menu.h>
 #include <types/ui/menubar.h>
-#include <types/ui/event.h>
+#include <types/ui/window.h>
+#include <uchar.h>
 
-extern errno_t ui_menu_create(ui_menu_bar_t *, const char *, ui_menu_t **);
+extern errno_t ui_menu_create(ui_window_t *, ui_menu_t **);
 extern void ui_menu_destroy(ui_menu_t *);
-extern ui_menu_t *ui_menu_first(ui_menu_bar_t *);
-extern ui_menu_t *ui_menu_next(ui_menu_t *);
-extern const char *ui_menu_caption(ui_menu_t *);
-extern void ui_menu_get_rect(ui_menu_t *, gfx_coord2_t *, gfx_rect_t *);
-extern errno_t ui_menu_open(ui_menu_t *, gfx_rect_t *);
+extern void ui_menu_set_cb(ui_menu_t *, ui_menu_cb_t *, void *);
+extern errno_t ui_menu_open(ui_menu_t *, gfx_rect_t *, sysarg_t);
 extern void ui_menu_close(ui_menu_t *);
+extern bool ui_menu_is_open(ui_menu_t *);
 extern errno_t ui_menu_paint(ui_menu_t *, gfx_coord2_t *);
+extern ui_evclaim_t ui_menu_kbd_event(ui_menu_t *, kbd_event_t *);
 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/menubar.h
===================================================================
--- uspace/lib/ui/include/ui/menubar.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/ui/menubar.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -40,4 +40,5 @@
 #include <gfx/coord.h>
 #include <io/pos_event.h>
+#include <types/common.h>
 #include <types/ui/menubar.h>
 #include <types/ui/control.h>
@@ -46,13 +47,21 @@
 #include <types/ui/ui.h>
 #include <types/ui/window.h>
+#include <uchar.h>
 
 extern errno_t ui_menu_bar_create(ui_t *, ui_window_t *,
     ui_menu_bar_t **);
 extern void ui_menu_bar_destroy(ui_menu_bar_t *);
+extern void ui_menu_bar_set_cb(ui_menu_bar_t *, ui_menu_bar_cb_t *, void *);
 extern ui_control_t *ui_menu_bar_ctl(ui_menu_bar_t *);
 extern void ui_menu_bar_set_rect(ui_menu_bar_t *, gfx_rect_t *);
 extern errno_t ui_menu_bar_paint(ui_menu_bar_t *);
+extern ui_evclaim_t ui_menu_bar_kbd_event(ui_menu_bar_t *, kbd_event_t *);
 extern ui_evclaim_t ui_menu_bar_pos_event(ui_menu_bar_t *, pos_event_t *);
+extern void ui_menu_bar_press_accel(ui_menu_bar_t *, char32_t, sysarg_t);
 extern void ui_menu_bar_unfocus(ui_menu_bar_t *);
+extern void ui_menu_bar_activate(ui_menu_bar_t *);
+extern void ui_menu_bar_deactivate(ui_menu_bar_t *);
+extern void ui_menu_bar_select_first(ui_menu_bar_t *, bool, sysarg_t);
+extern void ui_menu_bar_select_last(ui_menu_bar_t *, bool, sysarg_t);
 
 #endif
Index: uspace/lib/ui/include/ui/menudd.h
===================================================================
--- uspace/lib/ui/include/ui/menudd.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/ui/menudd.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,68 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Menu drop-down
+ */
+
+#ifndef _UI_MENUDD_H
+#define _UI_MENUDD_H
+
+#include <errno.h>
+#include <gfx/coord.h>
+#include <io/kbd_event.h>
+#include <io/pos_event.h>
+#include <stdbool.h>
+#include <types/common.h>
+#include <types/ui/menu.h>
+#include <types/ui/menudd.h>
+#include <types/ui/menubar.h>
+#include <types/ui/event.h>
+#include <uchar.h>
+
+extern errno_t ui_menu_dd_create(ui_menu_bar_t *, const char *, ui_menu_dd_t **,
+    ui_menu_t **);
+extern void ui_menu_dd_destroy(ui_menu_dd_t *);
+extern ui_menu_dd_t *ui_menu_dd_first(ui_menu_bar_t *);
+extern ui_menu_dd_t *ui_menu_dd_next(ui_menu_dd_t *);
+extern ui_menu_dd_t *ui_menu_dd_last(ui_menu_bar_t *);
+extern ui_menu_dd_t *ui_menu_dd_prev(ui_menu_dd_t *);
+extern const char *ui_menu_dd_caption(ui_menu_dd_t *);
+extern void ui_menu_dd_get_rect(ui_menu_dd_t *, gfx_coord2_t *, gfx_rect_t *);
+extern char32_t ui_menu_dd_get_accel(ui_menu_dd_t *);
+extern errno_t ui_menu_dd_open(ui_menu_dd_t *, gfx_rect_t *, sysarg_t);
+extern void ui_menu_dd_close(ui_menu_dd_t *);
+extern bool ui_menu_dd_is_open(ui_menu_dd_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/ui/menuentry.h
===================================================================
--- uspace/lib/ui/include/ui/menuentry.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/ui/menuentry.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,4 +39,6 @@
 #include <errno.h>
 #include <gfx/coord.h>
+#include <io/pos_event.h>
+#include <stdbool.h>
 #include <types/ui/menu.h>
 #include <types/ui/menuentry.h>
@@ -49,6 +51,10 @@
 extern void ui_menu_entry_set_cb(ui_menu_entry_t *, ui_menu_entry_cb_t,
     void *);
+extern void ui_menu_entry_set_disabled(ui_menu_entry_t *, bool);
+extern bool ui_menu_entry_is_disabled(ui_menu_entry_t *);
 extern ui_menu_entry_t *ui_menu_entry_first(ui_menu_t *);
+extern ui_menu_entry_t *ui_menu_entry_last(ui_menu_t *);
 extern ui_menu_entry_t *ui_menu_entry_next(ui_menu_entry_t *);
+extern ui_menu_entry_t *ui_menu_entry_prev(ui_menu_entry_t *);
 extern gfx_coord_t ui_menu_entry_calc_width(ui_menu_t *,
     gfx_coord_t, gfx_coord_t);
@@ -56,9 +62,12 @@
     gfx_coord_t *, gfx_coord_t *);
 extern gfx_coord_t ui_menu_entry_height(ui_menu_entry_t *);
+extern char32_t ui_menu_entry_get_accel(ui_menu_entry_t *);
 extern errno_t ui_menu_entry_paint(ui_menu_entry_t *, gfx_coord2_t *);
+extern bool ui_menu_entry_selectable(ui_menu_entry_t *);
 extern void ui_menu_entry_press(ui_menu_entry_t *, gfx_coord2_t *);
 extern void ui_menu_entry_release(ui_menu_entry_t *);
 extern void ui_menu_entry_enter(ui_menu_entry_t *, gfx_coord2_t *);
 extern void ui_menu_entry_leave(ui_menu_entry_t *, gfx_coord2_t *);
+extern void ui_menu_entry_activate(ui_menu_entry_t *);
 extern ui_evclaim_t ui_menu_entry_pos_event(ui_menu_entry_t *, gfx_coord2_t *,
     pos_event_t *);
Index: uspace/lib/ui/include/ui/paint.h
===================================================================
--- uspace/lib/ui/include/ui/paint.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/ui/paint.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -55,8 +55,31 @@
 extern errno_t ui_paint_filled_circle(gfx_context_t *, gfx_coord2_t *,
     gfx_coord_t, ui_fcircle_part_t);
+extern errno_t ui_paint_up_triangle(gfx_context_t *, gfx_coord2_t *,
+    gfx_coord_t);
+extern errno_t ui_paint_down_triangle(gfx_context_t *, gfx_coord2_t *,
+    gfx_coord_t);
+extern errno_t ui_paint_left_triangle(gfx_context_t *, gfx_coord2_t *,
+    gfx_coord_t);
+extern errno_t ui_paint_right_triangle(gfx_context_t *, gfx_coord2_t *,
+    gfx_coord_t);
+extern errno_t ui_paint_cross(gfx_context_t *, gfx_coord2_t *, gfx_coord_t,
+    gfx_coord_t, gfx_coord_t);
+extern errno_t ui_paint_minicon(ui_resource_t *, gfx_coord2_t *, gfx_coord_t,
+    gfx_coord_t);
+extern errno_t ui_paint_maxicon(ui_resource_t *, gfx_coord2_t *, gfx_coord_t,
+    gfx_coord_t);
+extern errno_t ui_paint_unmaxicon(ui_resource_t *, gfx_coord2_t *, gfx_coord_t,
+    gfx_coord_t, gfx_coord_t, gfx_coord_t);
+extern errno_t ui_paint_text_box_custom(ui_resource_t *, gfx_rect_t *,
+    ui_box_chars_t *, gfx_color_t *);
 extern errno_t ui_paint_text_box(ui_resource_t *, gfx_rect_t *,
     ui_box_style_t, gfx_color_t *);
 extern errno_t ui_paint_text_hbrace(ui_resource_t *, gfx_rect_t *,
     ui_box_style_t, gfx_color_t *);
+extern errno_t ui_paint_text_rect(ui_resource_t *, gfx_rect_t *, gfx_color_t *,
+    const char *);
+extern void ui_text_fmt_init(ui_text_fmt_t *);
+extern gfx_coord_t ui_text_width(gfx_font_t *, const char *);
+extern errno_t ui_paint_text(gfx_coord2_t *, ui_text_fmt_t *, const char *);
 
 #endif
Index: uspace/lib/ui/include/ui/pbutton.h
===================================================================
--- uspace/lib/ui/include/ui/pbutton.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/ui/pbutton.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -51,6 +51,12 @@
 extern ui_control_t *ui_pbutton_ctl(ui_pbutton_t *);
 extern void ui_pbutton_set_cb(ui_pbutton_t *, ui_pbutton_cb_t *, void *);
+extern void ui_pbutton_set_decor_ops(ui_pbutton_t *, ui_pbutton_decor_ops_t *,
+    void *);
+extern void ui_pbutton_set_flags(ui_pbutton_t *, ui_pbutton_flags_t);
 extern void ui_pbutton_set_rect(ui_pbutton_t *, gfx_rect_t *);
 extern void ui_pbutton_set_default(ui_pbutton_t *, bool);
+extern bool ui_pbutton_get_light(ui_pbutton_t *);
+extern void ui_pbutton_set_light(ui_pbutton_t *, bool);
+extern errno_t ui_pbutton_set_caption(ui_pbutton_t *, const char *);
 extern errno_t ui_pbutton_paint(ui_pbutton_t *);
 extern void ui_pbutton_press(ui_pbutton_t *);
@@ -59,4 +65,6 @@
 extern void ui_pbutton_leave(ui_pbutton_t *);
 extern void ui_pbutton_clicked(ui_pbutton_t *);
+extern void ui_pbutton_down(ui_pbutton_t *);
+extern void ui_pbutton_up(ui_pbutton_t *);
 extern ui_evclaim_t ui_pbutton_pos_event(ui_pbutton_t *, pos_event_t *);
 
Index: uspace/lib/ui/include/ui/popup.h
===================================================================
--- uspace/lib/ui/include/ui/popup.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/ui/popup.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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/resource.h
===================================================================
--- uspace/lib/ui/include/ui/resource.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/ui/resource.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -38,4 +38,5 @@
 
 #include <errno.h>
+#include <gfx/color.h>
 #include <gfx/context.h>
 #include <gfx/font.h>
@@ -49,4 +50,7 @@
 extern void ui_resource_expose(ui_resource_t *);
 extern gfx_font_t *ui_resource_get_font(ui_resource_t *);
+extern bool ui_resource_is_textmode(ui_resource_t *);
+extern gfx_color_t *ui_resource_get_wnd_face_color(ui_resource_t *);
+extern gfx_color_t *ui_resource_get_wnd_text_color(ui_resource_t *);
 
 #endif
Index: uspace/lib/ui/include/ui/scrollbar.h
===================================================================
--- uspace/lib/ui/include/ui/scrollbar.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/ui/scrollbar.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,77 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Scrollbar
+ */
+
+#ifndef _UI_SCROLLBAR_H
+#define _UI_SCROLLBAR_H
+
+#include <errno.h>
+#include <gfx/coord.h>
+#include <io/pos_event.h>
+#include <stdbool.h>
+#include <types/ui/control.h>
+#include <types/ui/event.h>
+#include <types/ui/scrollbar.h>
+#include <types/ui/ui.h>
+#include <types/ui/window.h>
+
+extern errno_t ui_scrollbar_create(ui_t *, ui_window_t *, ui_scrollbar_dir_t,
+    ui_scrollbar_t **);
+extern void ui_scrollbar_destroy(ui_scrollbar_t *);
+extern ui_control_t *ui_scrollbar_ctl(ui_scrollbar_t *);
+extern void ui_scrollbar_set_cb(ui_scrollbar_t *, ui_scrollbar_cb_t *, void *);
+extern void ui_scrollbar_set_rect(ui_scrollbar_t *, gfx_rect_t *);
+extern errno_t ui_scrollbar_paint(ui_scrollbar_t *);
+extern gfx_coord_t ui_scrollbar_trough_length(ui_scrollbar_t *);
+extern gfx_coord_t ui_scrollbar_move_length(ui_scrollbar_t *);
+extern gfx_coord_t ui_scrollbar_get_pos(ui_scrollbar_t *);
+extern void ui_scrollbar_set_thumb_length(ui_scrollbar_t *, gfx_coord_t);
+extern void ui_scrollbar_set_pos(ui_scrollbar_t *, gfx_coord_t);
+extern void ui_scrollbar_thumb_press(ui_scrollbar_t *, gfx_coord2_t *);
+extern void ui_scrollbar_upper_trough_press(ui_scrollbar_t *);
+extern void ui_scrollbar_lower_trough_press(ui_scrollbar_t *);
+extern void ui_scrollbar_release(ui_scrollbar_t *, gfx_coord2_t *);
+extern void ui_scrollbar_update(ui_scrollbar_t *, gfx_coord2_t *);
+extern void ui_scrollbar_troughs_update(ui_scrollbar_t *, gfx_coord2_t *);
+extern void ui_scrollbar_up(ui_scrollbar_t *);
+extern void ui_scrollbar_down(ui_scrollbar_t *);
+extern void ui_scrollbar_page_up(ui_scrollbar_t *);
+extern void ui_scrollbar_page_down(ui_scrollbar_t *);
+extern void ui_scrollbar_moved(ui_scrollbar_t *, gfx_coord_t);
+extern ui_evclaim_t ui_scrollbar_pos_event(ui_scrollbar_t *, pos_event_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/ui/selectdialog.h
===================================================================
--- uspace/lib/ui/include/ui/selectdialog.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/ui/selectdialog.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,58 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Select dialog
+ */
+
+#ifndef _UI_SELECTDIALOG_H
+#define _UI_SELECTDIALOG_H
+
+#include <errno.h>
+#include <types/ui/list.h>
+#include <types/ui/selectdialog.h>
+#include <types/ui/ui.h>
+
+extern void ui_select_dialog_params_init(ui_select_dialog_params_t *);
+extern errno_t ui_select_dialog_create(ui_t *, ui_select_dialog_params_t *,
+    ui_select_dialog_t **);
+extern void ui_select_dialog_set_cb(ui_select_dialog_t *, ui_select_dialog_cb_t *,
+    void *);
+extern void ui_select_dialog_destroy(ui_select_dialog_t *);
+extern errno_t ui_select_dialog_append(ui_select_dialog_t *,
+    ui_list_entry_attr_t *);
+extern errno_t ui_select_dialog_paint(ui_select_dialog_t *);
+extern ui_list_t *ui_select_dialog_list(ui_select_dialog_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/ui/slider.h
===================================================================
--- uspace/lib/ui/include/ui/slider.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/ui/slider.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -46,6 +46,5 @@
 #include <types/ui/slider.h>
 
-extern errno_t ui_slider_create(ui_resource_t *, const char *,
-    ui_slider_t **);
+extern errno_t ui_slider_create(ui_resource_t *, ui_slider_t **);
 extern void ui_slider_destroy(ui_slider_t *);
 extern ui_control_t *ui_slider_ctl(ui_slider_t *);
Index: uspace/lib/ui/include/ui/tab.h
===================================================================
--- uspace/lib/ui/include/ui/tab.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/ui/tab.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,67 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Tab
+ */
+
+#ifndef _UI_TAB_H
+#define _UI_TAB_H
+
+#include <errno.h>
+#include <gfx/coord.h>
+#include <io/kbd_event.h>
+#include <io/pos_event.h>
+#include <stdbool.h>
+#include <types/common.h>
+#include <types/ui/control.h>
+#include <types/ui/tab.h>
+#include <types/ui/tabset.h>
+#include <types/ui/event.h>
+#include <uchar.h>
+
+extern errno_t ui_tab_create(ui_tab_set_t *, const char *, ui_tab_t **);
+extern void ui_tab_destroy(ui_tab_t *);
+extern ui_tab_t *ui_tab_first(ui_tab_set_t *);
+extern ui_tab_t *ui_tab_next(ui_tab_t *);
+extern ui_tab_t *ui_tab_last(ui_tab_set_t *);
+extern ui_tab_t *ui_tab_prev(ui_tab_t *);
+extern bool ui_tab_is_selected(ui_tab_t *);
+extern void ui_tab_add(ui_tab_t *, ui_control_t *);
+extern void ui_tab_remove(ui_tab_t *, ui_control_t *);
+extern errno_t ui_tab_paint(ui_tab_t *);
+extern ui_evclaim_t ui_tab_kbd_event(ui_tab_t *, kbd_event_t *);
+extern ui_evclaim_t ui_tab_pos_event(ui_tab_t *, pos_event_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/ui/tabset.h
===================================================================
--- uspace/lib/ui/include/ui/tabset.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/ui/tabset.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,59 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Tab set
+ */
+
+#ifndef _UI_TABSET_H
+#define _UI_TABSET_H
+
+#include <errno.h>
+#include <gfx/coord.h>
+#include <io/kbd_event.h>
+#include <io/pos_event.h>
+#include <types/ui/tabset.h>
+#include <types/ui/control.h>
+#include <types/ui/event.h>
+#include <types/ui/resource.h>
+
+extern errno_t ui_tab_set_create(ui_resource_t *, ui_tab_set_t **);
+extern void ui_tab_set_destroy(ui_tab_set_t *);
+extern ui_control_t *ui_tab_set_ctl(ui_tab_set_t *);
+extern void ui_tab_set_set_rect(ui_tab_set_t *, gfx_rect_t *);
+extern errno_t ui_tab_set_paint(ui_tab_set_t *);
+extern ui_evclaim_t ui_tab_set_kbd_event(ui_tab_set_t *, kbd_event_t *);
+extern ui_evclaim_t ui_tab_set_pos_event(ui_tab_set_t *, pos_event_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/ui/testctl.h
===================================================================
--- uspace/lib/ui/include/ui/testctl.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/include/ui/testctl.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,50 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Test control
+ */
+
+#ifndef _UI_TESTCTL_H
+#define _UI_TESTCTL_H
+
+#include <errno.h>
+#include <types/ui/control.h>
+#include <types/ui/testctl.h>
+
+extern errno_t ui_test_ctl_create(ui_tc_resp_t *, ui_test_ctl_t **);
+extern void ui_test_ctl_destroy(ui_test_ctl_t *);
+extern ui_control_t *ui_test_ctl_ctl(ui_test_ctl_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/ui/ui.h
===================================================================
--- uspace/lib/ui/include/ui/ui.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/ui/ui.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,6 +39,8 @@
 #include <display.h>
 #include <errno.h>
+#include <gfx/coord.h>
 #include <io/console.h>
 #include <stdbool.h>
+#include <types/ui/clickmatic.h>
 #include <types/ui/ui.h>
 
@@ -52,4 +54,11 @@
 extern bool ui_is_textmode(ui_t *);
 extern bool ui_is_fullscreen(ui_t *);
+extern errno_t ui_get_rect(ui_t *, gfx_rect_t *);
+extern errno_t ui_suspend(ui_t *);
+extern errno_t ui_resume(ui_t *);
+extern bool ui_is_suspended(ui_t *);
+extern void ui_lock(ui_t *);
+extern void ui_unlock(ui_t *);
+extern ui_clickmatic_t *ui_get_clickmatic(ui_t *);
 
 #endif
Index: uspace/lib/ui/include/ui/wdecor.h
===================================================================
--- uspace/lib/ui/include/ui/wdecor.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/ui/wdecor.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,8 +39,10 @@
 #include <errno.h>
 #include <gfx/coord.h>
+#include <io/kbd_event.h>
 #include <io/pos_event.h>
 #include <stdbool.h>
 #include <types/ui/event.h>
 #include <types/ui/resource.h>
+#include <types/ui/ui.h>
 #include <types/ui/wdecor.h>
 
@@ -51,7 +53,11 @@
 extern void ui_wdecor_set_rect(ui_wdecor_t *, gfx_rect_t *);
 extern void ui_wdecor_set_active(ui_wdecor_t *, bool);
+extern void ui_wdecor_set_maximized(ui_wdecor_t *, bool);
+extern errno_t ui_wdecor_set_caption(ui_wdecor_t *, const char *);
+extern void ui_wdecor_sysmenu_hdl_set_active(ui_wdecor_t *, bool);
 extern errno_t ui_wdecor_paint(ui_wdecor_t *);
+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/include/ui/window.h
===================================================================
--- uspace/lib/ui/include/ui/window.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/include/ui/window.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -42,4 +42,5 @@
 #include <io/kbd_event.h>
 #include <io/pos_event.h>
+#include <types/common.h>
 #include <types/ui/control.h>
 #include <types/ui/ui.h>
@@ -51,4 +52,5 @@
     ui_window_t **);
 extern void ui_window_set_cb(ui_window_t *, ui_window_cb_t *, void *);
+extern errno_t ui_window_set_caption(ui_window_t *, const char *);
 extern void ui_window_destroy(ui_window_t *);
 extern void ui_window_add(ui_window_t *, ui_control_t *);
@@ -64,8 +66,12 @@
 extern void ui_window_set_ctl_cursor(ui_window_t *, ui_stock_cursor_t);
 extern errno_t ui_window_paint(ui_window_t *);
-extern void ui_window_def_kbd(ui_window_t *, kbd_event_t *);
+extern errno_t ui_window_def_sysmenu(ui_window_t *, sysarg_t);
+extern errno_t ui_window_def_minimize(ui_window_t *);
+extern errno_t ui_window_def_maximize(ui_window_t *);
+extern errno_t ui_window_def_unmaximize(ui_window_t *);
+extern ui_evclaim_t ui_window_def_kbd(ui_window_t *, kbd_event_t *);
 extern errno_t ui_window_def_paint(ui_window_t *);
 extern void ui_window_def_pos(ui_window_t *, pos_event_t *);
-extern void ui_window_def_unfocus(ui_window_t *);
+extern void ui_window_def_unfocus(ui_window_t *, unsigned);
 
 #endif
Index: uspace/lib/ui/meson.build
===================================================================
--- uspace/lib/ui/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 #
-# Copyright (c) 2021 Jiri Svoboda
+# Copyright (c) 2023 Jiri Svoboda
 # All rights reserved.
 #
@@ -27,16 +27,21 @@
 #
 
-deps = [ 'gfx', 'gfxfont', 'memgfx', 'display', 'congfx' ]
+deps = [ 'gfx', 'gfxfont', 'memgfx', 'display', 'congfx', 'clipboard' ]
 src = files(
+	'src/accel.c',
 	'src/checkbox.c',
+	'src/clickmatic.c',
 	'src/control.c',
 	'src/dummygc.c',
 	'src/entry.c',
 	'src/filedialog.c',
+	'src/filelist.c',
 	'src/fixed.c',
 	'src/image.c',
 	'src/label.c',
+	'src/list.c',
 	'src/menu.c',
 	'src/menubar.c',
+	'src/menudd.c',
 	'src/menuentry.c',
 	'src/msgdialog.c',
@@ -47,5 +52,10 @@
 	'src/rbutton.c',
 	'src/resource.c',
+	'src/scrollbar.c',
+	'src/selectdialog.c',
 	'src/slider.c',
+	'src/tab.c',
+	'src/tabset.c',
+	'src/testctl.c',
 	'src/ui.c',
 	'src/wdecor.c',
@@ -54,14 +64,19 @@
 
 test_src = files(
+	'test/accel.c',
 	'test/checkbox.c',
+	'test/clickmatic.c',
 	'test/control.c',
 	'test/entry.c',
 	'test/filedialog.c',
+	'test/filelist.c',
 	'test/fixed.c',
 	'test/image.c',
 	'test/label.c',
+	'test/list.c',
 	'test/main.c',
 	'test/menu.c',
 	'test/menubar.c',
+	'test/menudd.c',
 	'test/menuentry.c',
 	'test/msgdialog.c',
@@ -72,5 +87,10 @@
 	'test/rbutton.c',
 	'test/resource.c',
+	'test/scrollbar.c',
+	'test/selectdialog.c',
 	'test/slider.c',
+	'test/tab.c',
+	'test/tabset.c',
+	'test/testctl.c',
 	'test/ui.c',
 	'test/wdecor.c',
Index: uspace/lib/ui/private/clickmatic.h
===================================================================
--- uspace/lib/ui/private/clickmatic.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/private/clickmatic.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libui
+ * @{
+ */
+/**
+ * @file Clickmatic structure
+ *
+ */
+
+#ifndef _UI_PRIVATE_CLICKMATIC_H
+#define _UI_PRIVATE_CLICKMATIC_H
+
+#include <fibril_synch.h>
+#include <gfx/coord.h>
+
+/** Actual structure of clickmatic.
+ *
+ * This is private to libui.
+ */
+struct ui_clickmatic {
+	/** Containing user interface */
+	struct ui *ui;
+	/** Callbacks */
+	struct ui_clickmatic_cb *cb;
+	/** Callback argument */
+	void *arg;
+	/** Click position */
+	gfx_coord2_t pos;
+	/** Timer */
+	fibril_timer_t *timer;
+};
+
+extern void ui_clickmatic_clicked(ui_clickmatic_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/private/entry.h
===================================================================
--- uspace/lib/ui/private/entry.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/private/entry.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -92,5 +92,4 @@
 } ui_entry_geom_t;
 
-extern errno_t ui_entry_insert_str(ui_entry_t *, const char *);
 extern ui_evclaim_t ui_entry_key_press_ctrl(ui_entry_t *, kbd_event_t *);
 extern ui_evclaim_t ui_entry_key_press_shift(ui_entry_t *, kbd_event_t *);
Index: uspace/lib/ui/private/filedialog.h
===================================================================
--- uspace/lib/ui/private/filedialog.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/private/filedialog.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -47,4 +47,6 @@
 	/** File name entry */
 	struct ui_entry *ename;
+	/** File list */
+	struct ui_file_list *flist;
 	/** OK button */
 	struct ui_pbutton *bok;
Index: uspace/lib/ui/private/filelist.h
===================================================================
--- uspace/lib/ui/private/filelist.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/private/filelist.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,126 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file File list
+ */
+
+#ifndef _UI_PRIVATE_FILELIST_H
+#define _UI_PRIVATE_FILELIST_H
+
+#include <gfx/color.h>
+#include <ipc/loc.h>
+#include <ui/list.h>
+#include <ui/window.h>
+#include <stdint.h>
+#include <types/ui/filelist.h>
+
+/** File list entry attributes */
+struct ui_file_list_entry_attr {
+	/** File name */
+	const char *name;
+	/** File size */
+	uint64_t size;
+	/** @c true iff entry is a directory */
+	bool isdir;
+	/** Service number for service special entries */
+	service_id_t svc;
+};
+
+/** File list entry */
+struct ui_file_list_entry {
+	/** Containing file list */
+	struct ui_file_list *flist;
+	/** List entry */
+	ui_list_entry_t *entry;
+	/** File name */
+	char *name;
+	/** File size */
+	uint64_t size;
+	/** @c true iff entry is a directory */
+	bool isdir;
+	/** Service number for service special entries */
+	service_id_t svc;
+};
+
+/** File list.
+ *
+ * Allows browsing files and directories.
+ */
+typedef struct ui_file_list {
+	/** Base control object */
+	struct ui_control *control;
+
+	/** Containing window */
+	struct ui_window *window;
+
+	/** UI list */
+	ui_list_t *list;
+
+	/** Callbacks */
+	struct ui_file_list_cb *cb;
+
+	/** Callback argument */
+	void *cb_arg;
+
+	/** Directory-type entry color */
+	gfx_color_t *dir_color;
+
+	/** Service-type entry color */
+	gfx_color_t *svc_color;
+
+	/** Directory */
+	char *dir;
+} ui_file_list_t;
+
+extern bool ui_file_list_is_active(ui_file_list_t *);
+extern void ui_file_list_entry_destroy(ui_file_list_entry_t *);
+extern void ui_file_list_clear_entries(ui_file_list_t *);
+extern errno_t ui_file_list_sort(ui_file_list_t *);
+extern int ui_file_list_entry_ptr_cmp(const void *, const void *);
+extern void ui_file_list_entry_attr_init(ui_file_list_entry_attr_t *);
+extern errno_t ui_file_list_entry_append(ui_file_list_t *,
+    ui_file_list_entry_attr_t *);
+extern ui_file_list_entry_t *ui_file_list_first(ui_file_list_t *);
+extern ui_file_list_entry_t *ui_file_list_last(ui_file_list_t *);
+extern ui_file_list_entry_t *ui_file_list_next(ui_file_list_entry_t *);
+extern ui_file_list_entry_t *ui_file_list_prev(ui_file_list_entry_t *);
+extern errno_t ui_file_list_open_dir(ui_file_list_t *, ui_file_list_entry_t *);
+extern errno_t ui_file_list_open_file(ui_file_list_t *, ui_file_list_entry_t *);
+extern void ui_file_list_activate_req(ui_file_list_t *);
+extern void ui_file_list_selected(ui_file_list_t *, const char *);
+extern errno_t ui_file_list_paint(ui_file_list_t *);
+extern int ui_file_list_list_compare(ui_list_entry_t *, ui_list_entry_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/private/list.h
===================================================================
--- uspace/lib/ui/private/list.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/private/list.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,143 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file UI list
+ */
+
+#ifndef _UI_PRIVATE_LIST_H
+#define _UI_PRIVATE_LIST_H
+
+#include <adt/list.h>
+#include <gfx/color.h>
+#include <gfx/coord.h>
+#include <ipc/loc.h>
+#include <ui/window.h>
+#include <stdint.h>
+#include <types/ui/list.h>
+
+/** List entry */
+struct ui_list_entry {
+	/** Containing list */
+	struct ui_list *list;
+	/** Link to @c flist->entries */
+	link_t lentries;
+	/** Entry caption */
+	char *caption;
+	/** User argument */
+	void *arg;
+	/** Custom color or @c NULL to use default color */
+	gfx_color_t *color;
+	/** Custom background color or @c NULL to use default color */
+	gfx_color_t *bgcolor;
+};
+
+/** UI list.
+ *
+ * Displays a scrollable list of entries.
+ */
+typedef struct ui_list {
+	/** Base control object */
+	struct ui_control *control;
+
+	/** Containing window */
+	ui_window_t *window;
+
+	/** Callbacks */
+	struct ui_list_cb *cb;
+
+	/** Callback argument */
+	void *cb_arg;
+
+	/** List rectangle */
+	gfx_rect_t rect;
+
+	/** Scrollbar */
+	struct ui_scrollbar *scrollbar;
+
+	/** List entries (list of ui_list_entry_t) */
+	list_t entries;
+
+	/** Number of entries */
+	size_t entries_cnt;
+
+	/** First entry of current page */
+	ui_list_entry_t *page;
+
+	/** Index of first entry of current page */
+	size_t page_idx;
+
+	/** Cursor position */
+	ui_list_entry_t *cursor;
+
+	/** Index of entry under cursor */
+	size_t cursor_idx;
+
+	/** @c true iff the list is active */
+	bool active;
+} ui_list_t;
+
+extern gfx_coord_t ui_list_entry_height(ui_list_t *);
+extern errno_t ui_list_entry_paint(ui_list_entry_t *, size_t);
+extern errno_t ui_list_paint(ui_list_t *);
+extern ui_evclaim_t ui_list_kbd_event(ui_list_t *, kbd_event_t *);
+extern ui_evclaim_t ui_list_pos_event(ui_list_t *, pos_event_t *);
+extern unsigned ui_list_page_size(ui_list_t *);
+extern void ui_list_inside_rect(ui_list_t *, gfx_rect_t *);
+extern void ui_list_scrollbar_rect(ui_list_t *, gfx_rect_t *);
+extern gfx_coord_t ui_list_scrollbar_pos(ui_list_t *);
+extern void ui_list_scrollbar_update(ui_list_t *);
+extern void ui_list_clear_entries(ui_list_t *);
+extern ui_list_entry_t *ui_list_page_nth_entry(ui_list_t *,
+    size_t, size_t *);
+extern void ui_list_cursor_move(ui_list_t *, ui_list_entry_t *,
+    size_t);
+extern void ui_list_cursor_up(ui_list_t *);
+extern void ui_list_cursor_down(ui_list_t *);
+extern void ui_list_cursor_top(ui_list_t *);
+extern void ui_list_cursor_bottom(ui_list_t *);
+extern void ui_list_page_up(ui_list_t *);
+extern void ui_list_page_down(ui_list_t *);
+extern void ui_list_scroll_up(ui_list_t *);
+extern void ui_list_scroll_down(ui_list_t *);
+extern void ui_list_scroll_page_up(ui_list_t *);
+extern void ui_list_scroll_page_down(ui_list_t *);
+extern void ui_list_scroll_pos(ui_list_t *, size_t);
+extern void ui_list_activate_req(ui_list_t *);
+extern void ui_list_selected(ui_list_entry_t *);
+extern int ui_list_entry_ptr_cmp(const void *, const void *);
+extern size_t ui_list_entry_get_idx(ui_list_entry_t *);
+extern void ui_list_entry_destroy(ui_list_entry_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/private/menu.h
===================================================================
--- uspace/lib/ui/private/menu.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/private/menu.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -41,4 +41,5 @@
 #include <gfx/coord.h>
 #include <stdbool.h>
+#include <types/common.h>
 #include <types/ui/menu.h>
 #include <types/ui/resource.h>
@@ -49,8 +50,6 @@
  */
 struct ui_menu {
-	/** Containing menu bar */
-	struct ui_menu_bar *mbar;
-	/** Link to @c bar->menus */
-	link_t lmenus;
+	/** Parent window */
+	struct ui_window *parent;
 	/** Caption */
 	char *caption;
@@ -67,4 +66,10 @@
 	/** Menu entries (ui_menu_entry_t) */
 	list_t entries;
+	/** Callbacks */
+	struct ui_menu_cb *cb;
+	/** Callback argument */
+	void *arg;
+	/** ID of device that activated entry */
+	sysarg_t idev_id;
 };
 
@@ -84,4 +89,10 @@
 extern errno_t ui_menu_paint_bg_gfx(ui_menu_t *, gfx_coord2_t *);
 extern errno_t ui_menu_paint_bg_text(ui_menu_t *, gfx_coord2_t *);
+extern void ui_menu_up(ui_menu_t *);
+extern void ui_menu_down(ui_menu_t *);
+extern void ui_menu_left(ui_menu_t *, sysarg_t);
+extern void ui_menu_right(ui_menu_t *, sysarg_t);
+extern void ui_menu_close_req(ui_menu_t *);
+extern void ui_menu_press_accel(ui_menu_t *, char32_t, sysarg_t);
 
 #endif
Index: uspace/lib/ui/private/menubar.h
===================================================================
--- uspace/lib/ui/private/menubar.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/private/menubar.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -40,6 +40,8 @@
 #include <adt/list.h>
 #include <gfx/coord.h>
-#include <types/ui/menu.h>
+#include <stdbool.h>
+#include <types/common.h>
 #include <types/ui/menubar.h>
+#include <types/ui/menudd.h>
 
 /** Actual structure of menu bar.
@@ -56,11 +58,23 @@
 	/** Menu bar rectangle */
 	gfx_rect_t rect;
-	/** Selected menu or @c NULL */
-	struct ui_menu *selected;
-	/** List of menus (ui_menu_t) */
-	list_t menus;
+	/** Menu bar is active */
+	bool active;
+	/** Selected menu drop-down or @c NULL */
+	struct ui_menu_dd *selected;
+	/** List of menu drop-downs (ui_menu_dd_t) */
+	list_t menudds;
+	/** Callbacks */
+	struct ui_menu_bar_cb *cb;
+	/** Callback argument */
+	void *arg;
 };
 
-extern void ui_menu_bar_select(ui_menu_bar_t *, gfx_rect_t *, ui_menu_t *);
+extern void ui_menu_bar_select(ui_menu_bar_t *, ui_menu_dd_t *, bool, sysarg_t);
+extern void ui_menu_bar_select_sysmenu(ui_menu_bar_t *, bool, sysarg_t);
+extern void ui_menu_bar_left(ui_menu_bar_t *, sysarg_t);
+extern void ui_menu_bar_right(ui_menu_bar_t *, sysarg_t);
+extern ui_evclaim_t ui_menu_bar_key_press_unmod(ui_menu_bar_t *, kbd_event_t *);
+extern void ui_menu_bar_entry_rect(ui_menu_bar_t *, ui_menu_dd_t *,
+    gfx_rect_t *);
 
 #endif
Index: uspace/lib/ui/private/menudd.h
===================================================================
--- uspace/lib/ui/private/menudd.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/private/menudd.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,66 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Menu drop-down structure
+ *
+ */
+
+#ifndef _UI_PRIVATE_MENUDD_H
+#define _UI_PRIVATE_MENUDD_H
+
+#include <adt/list.h>
+#include <gfx/coord.h>
+#include <stdbool.h>
+#include <types/ui/menudd.h>
+#include <types/ui/resource.h>
+
+/** Actual structure of menu drop-down.
+ *
+ * This is private to libui.
+ */
+struct ui_menu_dd {
+	/** Containing menu bar */
+	struct ui_menu_bar *mbar;
+	/** Link to @c bar->menudds */
+	link_t lmenudds;
+	/** Caption */
+	char *caption;
+	/** Popup window or @c NULL if drop-down is not currently open */
+	struct ui_popup *popup;
+	/** Actual menu */
+	struct ui_menu *menu;
+};
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/private/menuentry.h
===================================================================
--- uspace/lib/ui/private/menuentry.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/private/menuentry.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -53,4 +53,6 @@
 	/** Callbacks */
 	ui_menu_entry_cb_t cb;
+	/** This entry is disabled */
+	bool disabled;
 	/** This entry is a separator entry */
 	bool separator;
Index: uspace/lib/ui/private/pbutton.h
===================================================================
--- uspace/lib/ui/private/pbutton.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/private/pbutton.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -40,4 +40,5 @@
 #include <gfx/coord.h>
 #include <stdbool.h>
+#include <types/ui/pbutton.h>
 
 /** Actual structure of push button.
@@ -54,14 +55,22 @@
 	/** Callback argument */
 	void *arg;
+	/** Custom decoration ops or @c NULL */
+	struct ui_pbutton_decor_ops *decor_ops;
+	/** Decoration argument */
+	void *decor_arg;
 	/** Push button rectangle */
 	gfx_rect_t rect;
 	/** Caption */
-	const char *caption;
+	char *caption;
 	/** Button is selected as default */
 	bool isdefault;
+	/** Button light is on */
+	bool light;
 	/** Button is currently held down */
 	bool held;
 	/** Pointer is currently inside */
 	bool inside;
+	/** Push button flags */
+	ui_pbutton_flags_t flags;
 };
 
Index: uspace/lib/ui/private/popup.h
===================================================================
--- uspace/lib/ui/private/popup.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/private/popup.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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/private/promptdialog.h
===================================================================
--- uspace/lib/ui/private/promptdialog.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/private/promptdialog.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -45,5 +45,5 @@
 	/** Dialog window */
 	struct ui_window *window;
-	/** File name entry */
+	/** Text entry */
 	struct ui_entry *ename;
 	/** OK button */
Index: uspace/lib/ui/private/resource.h
===================================================================
--- uspace/lib/ui/private/resource.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/private/resource.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -66,4 +66,6 @@
 	/** Button face color */
 	gfx_color_t *btn_face_color;
+	/** Lit button face color */
+	gfx_color_t *btn_face_lit_color;
 	/** Button text color */
 	gfx_color_t *btn_text_color;
@@ -77,6 +79,12 @@
 	/** Window text color */
 	gfx_color_t *wnd_text_color;
+	/** Disabled text color */
+	gfx_color_t *wnd_dis_text_color;
+	/** Window text highlight color */
+	gfx_color_t *wnd_text_hgl_color;
 	/** Window selected text color */
 	gfx_color_t *wnd_sel_text_color;
+	/** Window selected text highlight color */
+	gfx_color_t *wnd_sel_text_hgl_color;
 	/** Window selected text background color */
 	gfx_color_t *wnd_sel_text_bg_color;
@@ -110,4 +118,9 @@
 	gfx_color_t *entry_sel_text_bg_color;
 
+	/** Scrollbar trough color */
+	gfx_color_t *sbar_trough_color;
+	/** Scrollbar active trough color */
+	gfx_color_t *sbar_act_trough_color;
+
 	/** Expose callback or @c NULL */
 	ui_expose_cb_t expose_cb;
Index: uspace/lib/ui/private/scrollbar.h
===================================================================
--- uspace/lib/ui/private/scrollbar.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/private/scrollbar.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,115 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Scrollbar structure
+ *
+ */
+
+#ifndef _UI_PRIVATE_SCROLLBAR_H
+#define _UI_PRIVATE_SCROLLBAR_H
+
+#include <gfx/coord.h>
+#include <stdbool.h>
+#include <types/ui/scrollbar.h>
+
+/** Actual structure of scrollbar.
+ *
+ * This is private to libui.
+ */
+struct ui_scrollbar {
+	/** Base control object */
+	struct ui_control *control;
+	/** UI */
+	struct ui *ui;
+	/** UI window containing scrollbar */
+	struct ui_window *window;
+	/** Callbacks */
+	struct ui_scrollbar_cb *cb;
+	/** Callback argument */
+	void *arg;
+	/** Scrollbar rectangle */
+	gfx_rect_t rect;
+	/** Scrollbar direction */
+	ui_scrollbar_dir_t dir;
+	/** Thumb length */
+	gfx_coord_t thumb_len;
+	/** Up button */
+	struct ui_pbutton *up_btn;
+	/** Down button */
+	struct ui_pbutton *down_btn;
+	/** Thumb is currently held down */
+	bool thumb_held;
+	/** Upper trough is currently held down */
+	bool upper_trough_held;
+	/** Pointer is inside upper trough */
+	bool upper_trough_inside;
+	/** Lower trough is currently held down */
+	bool lower_trough_held;
+	/** Pointer is inside lower trough */
+	bool lower_trough_inside;
+	/** Position where thumb was pressed */
+	gfx_coord2_t press_pos;
+	/** Last thumb position */
+	gfx_coord_t last_pos;
+	/** Thumb position */
+	gfx_coord_t pos;
+	/** Last cursor position (when trough is held) */
+	gfx_coord2_t last_curs_pos;
+};
+
+/** Scrollbar geometry.
+ *
+ * Computed rectangles of scrollbar elements.
+ */
+typedef struct {
+	/** Up button rectangle */
+	gfx_rect_t up_btn_rect;
+	/** Trough rectangle */
+	gfx_rect_t trough_rect;
+	/** Upper trough rectangle */
+	gfx_rect_t upper_trough_rect;
+	/** Thumb rectangle */
+	gfx_rect_t thumb_rect;
+	/** Lower trough rectangle */
+	gfx_rect_t lower_trough_rect;
+	/** Down button rectangle */
+	gfx_rect_t down_btn_rect;
+} ui_scrollbar_geom_t;
+
+extern errno_t ui_scrollbar_paint_gfx(ui_scrollbar_t *);
+extern errno_t ui_scrollbar_paint_text(ui_scrollbar_t *);
+extern void ui_scrollbar_get_geom(ui_scrollbar_t *, ui_scrollbar_geom_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/private/selectdialog.h
===================================================================
--- uspace/lib/ui/private/selectdialog.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/private/selectdialog.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,62 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Select dialog structure
+ *
+ */
+
+#ifndef _UI_PRIVATE_SELECTDIALOG_H
+#define _UI_PRIVATE_SELECTDIALOG_H
+
+/** Actual structure of select dialog.
+ *
+ * This is private to libui.
+ */
+struct ui_select_dialog {
+	/** Dialog window */
+	struct ui_window *window;
+	/** List */
+	struct ui_list *list;
+	/** OK button */
+	struct ui_pbutton *bok;
+	/** Cancel button */
+	struct ui_pbutton *bcancel;
+	/** Select dialog callbacks */
+	struct ui_select_dialog_cb *cb;
+	/** Callback argument */
+	void *arg;
+};
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/private/tab.h
===================================================================
--- uspace/lib/ui/private/tab.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/private/tab.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,90 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Tab structure
+ *
+ */
+
+#ifndef _UI_PRIVATE_TAB_H
+#define _UI_PRIVATE_TAB_H
+
+#include <adt/list.h>
+#include <gfx/coord.h>
+#include <stdbool.h>
+#include <types/ui/tab.h>
+#include <types/ui/resource.h>
+
+/** Actual structure of tab.
+ *
+ * This is private to libui.
+ */
+struct ui_tab {
+	/** Containing tab set */
+	struct ui_tab_set *tabset;
+	/** Link to @c tabset->tabs */
+	link_t ltabs;
+	/** Caption */
+	char *caption;
+	/** X offset of the handle */
+	gfx_coord_t xoff;
+	/** Tab content */
+	struct ui_control *content;
+};
+
+/** Tab geometry.
+ *
+ * Computed rectangles of tab elements.
+ */
+typedef struct {
+	/** Tab handle */
+	gfx_rect_t handle;
+	/** Tab handle area including pull-up area */
+	gfx_rect_t handle_area;
+	/** Tab body */
+	gfx_rect_t body;
+	/** Text position */
+	gfx_coord2_t text_pos;
+} ui_tab_geom_t;
+
+extern gfx_coord_t ui_tab_handle_width(ui_tab_t *);
+extern gfx_coord_t ui_tab_handle_height(ui_tab_t *);
+extern void ui_tab_get_geom(ui_tab_t *, ui_tab_geom_t *);
+extern errno_t ui_tab_paint_handle_frame(gfx_context_t *, gfx_rect_t *,
+    gfx_coord_t, gfx_color_t *, gfx_color_t *, bool, gfx_rect_t *);
+extern errno_t ui_tab_paint_body_frame(ui_tab_t *);
+extern errno_t ui_tab_paint_frame(ui_tab_t *);
+extern ui_resource_t *ui_tab_get_res(ui_tab_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/private/tabset.h
===================================================================
--- uspace/lib/ui/private/tabset.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/private/tabset.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,69 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Tab set structure
+ *
+ */
+
+#ifndef _UI_PRIVATE_TABSET_H
+#define _UI_PRIVATE_TABSET_H
+
+#include <adt/list.h>
+#include <gfx/coord.h>
+#include <stdbool.h>
+#include <types/common.h>
+#include <types/ui/tab.h>
+#include <types/ui/tabset.h>
+
+/** Actual structure of tab set.
+ *
+ * This is private to libui.
+ */
+struct ui_tab_set {
+	/** Base control object */
+	struct ui_control *control;
+	/** UI reource */
+	struct ui_resource *res;
+	/** Tab set rectangle */
+	gfx_rect_t rect;
+	/** Selected tab */
+	struct ui_tab *selected;
+	/** List of tabs (ui_tab_t) */
+	list_t tabs;
+};
+
+extern void ui_tab_set_select(ui_tab_set_t *, ui_tab_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/private/testctl.h
===================================================================
--- uspace/lib/ui/private/testctl.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/private/testctl.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,58 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Test control
+ *
+ */
+
+#ifndef _UI_PRIVATE_TESTCTL_H
+#define _UI_PRIVATE_TESTCTL_H
+
+#include <types/ui/testctl.h>
+
+/** Actual structure of test control.
+ *
+ * This is private to libui.
+ */
+struct ui_test_ctl {
+	/** Base control object */
+	struct ui_control *control;
+	/** Test response structure */
+	ui_tc_resp_t *resp;
+};
+
+extern ui_control_ops_t ui_test_ctl_ops;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/private/ui.h
===================================================================
--- uspace/lib/ui/private/ui.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/private/ui.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -41,6 +41,8 @@
 #include <gfx/coord.h>
 #include <display.h>
+#include <fibril_synch.h>
 #include <io/console.h>
 #include <stdbool.h>
+#include <types/common.h>
 
 /** Actual structure of user interface.
@@ -59,8 +61,16 @@
 	/** Output owned by UI, clean up when destroying UI */
 	bool myoutput;
+	/** @c true iff UI is suspended */
+	bool suspended;
 	/** @c true if terminating */
 	bool quit;
 	/** Windows (in stacking order, ui_window_t) */
 	list_t windows;
+	/** UI lock */
+	fibril_mutex_t lock;
+	/** Clickmatic */
+	struct ui_clickmatic *clickmatic;
+	/** Default input device ID used to determine new window's seat */
+	sysarg_t idev_id;
 };
 
Index: uspace/lib/ui/private/wdecor.h
===================================================================
--- uspace/lib/ui/private/wdecor.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/private/wdecor.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -41,4 +41,5 @@
 #include <io/pos_event.h>
 #include <stdbool.h>
+#include <types/common.h>
 #include <types/ui/cursor.h>
 #include <types/ui/wdecor.h>
@@ -63,4 +64,12 @@
 	/** Window is active */
 	bool active;
+	/** Window is maximized */
+	bool maximized;
+	/** System menu handle is active */
+	bool sysmenu_hdl_active;
+	/** Minimize button */
+	struct ui_pbutton *btn_min;
+	/** Maximize button */
+	struct ui_pbutton *btn_max;
 	/** Close button */
 	struct ui_pbutton *btn_close;
@@ -76,4 +85,12 @@
 	/** Title bar rectangle */
 	gfx_rect_t title_bar_rect;
+	/** System menu handle rectangle */
+	gfx_rect_t sysmenu_hdl_rect;
+	/** Window caption rectangle */
+	gfx_rect_t caption_rect;
+	/** Minimize button rectangle */
+	gfx_rect_t btn_min_rect;
+	/** Maximize button rectangle */
+	gfx_rect_t btn_max_rect;
 	/** Close button rectangle */
 	gfx_rect_t btn_close_rect;
@@ -82,10 +99,20 @@
 } ui_wdecor_geom_t;
 
+extern void ui_wdecor_sysmenu_open(ui_wdecor_t *, sysarg_t);
+extern void ui_wdecor_sysmenu_left(ui_wdecor_t *, sysarg_t);
+extern void ui_wdecor_sysmenu_right(ui_wdecor_t *, sysarg_t);
+extern void ui_wdecor_sysmenu_accel(ui_wdecor_t *, char32_t, sysarg_t);
+extern void ui_wdecor_minimize(ui_wdecor_t *);
+extern void ui_wdecor_maximize(ui_wdecor_t *);
+extern void ui_wdecor_unmaximize(ui_wdecor_t *);
 extern void ui_wdecor_close(ui_wdecor_t *);
-extern void ui_wdecor_move(ui_wdecor_t *, gfx_coord2_t *);
+extern void ui_wdecor_move(ui_wdecor_t *, gfx_coord2_t *, sysarg_t);
 extern void ui_wdecor_resize(ui_wdecor_t *, ui_wdecor_rsztype_t,
-    gfx_coord2_t *);
+    gfx_coord2_t *, sysarg_t);
 extern void ui_wdecor_set_cursor(ui_wdecor_t *, ui_stock_cursor_t);
 extern void ui_wdecor_get_geom(ui_wdecor_t *, ui_wdecor_geom_t *);
+extern errno_t ui_wdecor_sysmenu_hdl_paint_gfx(ui_wdecor_t *, gfx_rect_t *);
+extern errno_t ui_wdecor_sysmenu_hdl_paint_text(ui_wdecor_t *, gfx_rect_t *);
+extern errno_t ui_wdecor_sysmenu_hdl_paint(ui_wdecor_t *, gfx_rect_t *);
 extern void ui_wdecor_frame_pos_event(ui_wdecor_t *, pos_event_t *);
 extern ui_wdecor_rsztype_t ui_wdecor_get_rsztype(ui_wdecor_t *,
Index: uspace/lib/ui/private/window.h
===================================================================
--- uspace/lib/ui/private/window.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/private/window.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -47,4 +47,5 @@
 #include <memgfx/memgc.h>
 #include <memgfx/xlategc.h>
+#include <types/common.h>
 #include <types/ui/cursor.h>
 #include <types/ui/window.h>
@@ -77,4 +78,6 @@
 	/** Window rectangle */
 	gfx_rect_t rect;
+	/** Normal window rectangle (when not maximized) */
+	gfx_rect_t normal_rect;
 	/** Display position (if fullscreen mode) */
 	gfx_coord2_t dpos;
@@ -91,17 +94,45 @@
 	/** Window decoration */
 	struct ui_wdecor *wdecor;
+	/** System menu */
+	struct ui_menu *sysmenu;
+	/** System menu restore entry */
+	struct ui_menu_entry *sysmenu_restore;
+	/** System menu minimize entry */
+	struct ui_menu_entry *sysmenu_minimize;
+	/** System menu maximize entry */
+	struct ui_menu_entry *sysmenu_maximize;
+	/** Menu bar */
+	struct ui_menu_bar *mbar;
 	/** Top-level control in the application area */
 	struct ui_control *control;
 	/** Current cursor */
 	ui_stock_cursor_t cursor;
+	/** Window placement */
+	ui_wnd_placement_t placement;
 };
 
+/** Size change operation */
+typedef enum {
+	/** Resize window */
+	ui_wsc_resize,
+	/** Maximize window */
+	ui_wsc_maximize,
+	/** Unmaximize window */
+	ui_wsc_unmaximize
+} ui_wnd_sc_op_t;
+
 extern display_stock_cursor_t wnd_dcursor_from_cursor(ui_stock_cursor_t);
+extern void ui_window_send_sysmenu(ui_window_t *, sysarg_t);
+extern void ui_window_send_minimize(ui_window_t *);
+extern void ui_window_send_maximize(ui_window_t *);
+extern void ui_window_send_unmaximize(ui_window_t *);
 extern void ui_window_send_close(ui_window_t *);
-extern void ui_window_send_focus(ui_window_t *);
+extern void ui_window_send_focus(ui_window_t *, unsigned);
 extern void ui_window_send_kbd(ui_window_t *, kbd_event_t *);
 extern errno_t ui_window_send_paint(ui_window_t *);
 extern void ui_window_send_pos(ui_window_t *, pos_event_t *);
-extern void ui_window_send_unfocus(ui_window_t *);
+extern void ui_window_send_unfocus(ui_window_t *, unsigned);
+extern errno_t ui_window_size_change(ui_window_t *, gfx_rect_t *,
+    ui_wnd_sc_op_t);
 
 #endif
Index: uspace/lib/ui/src/accel.c
===================================================================
--- uspace/lib/ui/src/accel.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/src/accel.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libui
+ * @{
+ */
+/**
+ * @file Accelerator processing
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <str.h>
+#include <ui/accel.h>
+
+/** Process text with accelerator markup.
+ *
+ * Parse text with tilde markup into a list of strings. @a *rbuf is set
+ * to point to a newly allocated buffer containing consecutive null-terminated
+ * strings.
+ *
+ * Each part between two '~' becomes one string. '~~' is translated into
+ * a literal '~' character. @a *endptr is set to point to the first character
+ * beyond the end of the list.
+ *
+ * @param str String with tilde markup
+ * @param rbuf Place to store pointer to newly allocated buffer.
+ * @param endptr Place to store end pointer (just after last character)
+ * @return EOK on success or an error code
+ */
+errno_t ui_accel_process(const char *str, char **rbuf, char **endptr)
+{
+	const char *sp;
+	char *dp;
+	char *buf;
+
+	buf = malloc(str_size(str) + 1);
+	if (buf == NULL)
+		return ENOMEM;
+
+	/* Break down string into list of (non)highlighted parts */
+	sp = str;
+	dp = buf;
+	while (*sp != '\0') {
+		if (*sp == '~') {
+			if (sp[1] == '~') {
+				sp += 2;
+				*dp++ = '~';
+			} else {
+				++sp;
+				*dp++ = '\0';
+			}
+		} else {
+			*dp++ = *sp++;
+		}
+	}
+
+	*dp++ = '\0';
+	*rbuf = buf;
+	*endptr = dp;
+
+	return EOK;
+}
+
+/** Get accelerator character from marked-up string.
+ *
+ * @param str String with '~' accelerator markup
+ *
+ * @return Accelerator character (lowercase) or the null character if
+ *         the menu has no accelerator.
+ */
+char32_t ui_accel_get(const char *str)
+{
+	const char *sp = str;
+	size_t off;
+
+	while (*sp != '\0') {
+		if (*sp == '~') {
+			if (sp[1] == '~') {
+				++sp;
+			} else {
+				/* Found the accelerator */
+				++sp;
+				break;
+			}
+		}
+
+		++sp;
+	}
+
+	if (*sp == '\0') {
+		/* No accelerator */
+		return '\0';
+	}
+
+	/* Decode accelerator character */
+	off = 0;
+
+	/*
+	 * NOTE: tolower is unlike to actually convert to lowercase
+	 * in case of non-ASCII characters
+	 */
+	return tolower(str_decode(sp, &off, STR_NO_LIMIT));
+}
+
+/** @}
+ */
Index: uspace/lib/ui/src/checkbox.c
===================================================================
--- uspace/lib/ui/src/checkbox.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/checkbox.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -52,4 +52,7 @@
 	checkbox_box_h = 16,
 	checkbox_label_margin = 8,
+	checkbox_cross_n = 5,
+	checkbox_cross_w = 2,
+	checkbox_cross_h = 2
 };
 
@@ -136,12 +139,32 @@
 }
 
-/** Set button rectangle.
- *
- * @param checkbox Button
- * @param rect New button rectangle
+/** Set check box rectangle.
+ *
+ * @param checkbox Check box
+ * @param rect New check box rectangle
  */
 void ui_checkbox_set_rect(ui_checkbox_t *checkbox, gfx_rect_t *rect)
 {
 	checkbox->rect = *rect;
+}
+
+/** Return if check box is checked.
+ *
+ * @param checkbox Check box
+ * @return @c true iff check box is checked
+ */
+bool ui_checkbox_get_checked(ui_checkbox_t *checkbox)
+{
+	return checkbox->checked;
+}
+
+/** Set check box checked state.
+ *
+ * @param checkbox Check box
+ * @param checked @c true iff checkbox should be checked
+ */
+void ui_checkbox_set_checked(ui_checkbox_t *checkbox, bool checked)
+{
+	checkbox->checked = checked;
 }
 
@@ -188,15 +211,16 @@
 
 	if (checkbox->checked) {
-		box_center.x = (box_inside.p0.x + box_inside.p1.x) / 2;
-		box_center.y = (box_inside.p0.y + box_inside.p1.y) / 2;
-
-		gfx_text_fmt_init(&fmt);
-		fmt.color = checkbox->res->entry_fg_color;
-		fmt.halign = gfx_halign_center;
-		fmt.valign = gfx_valign_center;
-
-		rc = gfx_puttext(checkbox->res->font, &box_center, &fmt, "X");
+		rc = gfx_set_color(checkbox->res->gc,
+		    checkbox->res->entry_fg_color);
 		if (rc != EOK)
 			goto error;
+
+		box_center.x = (box_inside.p0.x + box_inside.p1.x) / 2 - 1;
+		box_center.y = (box_inside.p0.y + box_inside.p1.y) / 2 - 1;
+
+		rc = ui_paint_cross(checkbox->res->gc, &box_center,
+		    checkbox_cross_n, checkbox_cross_w, checkbox_cross_h);
+		if (rc != EOK)
+			goto error;
 	}
 
@@ -207,9 +231,10 @@
 
 	gfx_text_fmt_init(&fmt);
+	fmt.font = checkbox->res->font;
 	fmt.color = checkbox->res->wnd_text_color;
 	fmt.halign = gfx_halign_left;
 	fmt.valign = gfx_valign_center;
 
-	rc = gfx_puttext(checkbox->res->font, &pos, &fmt, checkbox->caption);
+	rc = gfx_puttext(&pos, &fmt, checkbox->caption);
 	if (rc != EOK)
 		goto error;
@@ -244,4 +269,5 @@
 
 	gfx_text_fmt_init(&fmt);
+	fmt.font = checkbox->res->font;
 	fmt.color = depressed ? checkbox->res->entry_act_bg_color :
 	    checkbox->res->wnd_text_color;
@@ -249,6 +275,5 @@
 	fmt.valign = gfx_valign_top;
 
-	rc = gfx_puttext(checkbox->res->font, &pos, &fmt,
-	    checkbox->checked ? "[X]" : "[ ]");
+	rc = gfx_puttext(&pos, &fmt, checkbox->checked ? "[X]" : "[ ]");
 	if (rc != EOK)
 		goto error;
@@ -259,5 +284,5 @@
 	fmt.color = checkbox->res->wnd_text_color;
 
-	rc = gfx_puttext(checkbox->res->font, &pos, &fmt, checkbox->caption);
+	rc = gfx_puttext(&pos, &fmt, checkbox->caption);
 	if (rc != EOK)
 		goto error;
@@ -397,4 +422,6 @@
 		}
 		break;
+	case POS_DCLICK:
+		break;
 	}
 
Index: uspace/lib/ui/src/clickmatic.c
===================================================================
--- uspace/lib/ui/src/clickmatic.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/src/clickmatic.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,163 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Clickmatic.
+ *
+ * Clickmatic is used to periodically generate events in particular cases
+ * when a mouse button is held down, such as when holding the button or
+ * trough of a scrollbar.
+ */
+
+#include <fibril_synch.h>
+#include <stdlib.h>
+#include <ui/clickmatic.h>
+#include <ui/ui.h>
+#include "../private/clickmatic.h"
+
+enum {
+	/** Initial clickmatic delay in MS */
+	clickmatic_delay_ms = 500,
+	/** Clickmatic repeat rate in clicks per second */
+	clickmatic_rate = 10
+};
+
+static void ui_clickmatic_timer_fun(void *);
+
+/** Create clickmatic.
+ *
+ * @param ui UI
+ * @param rclickmatic Place to store pointer to new clickmatic
+ * @return EOK on success or an error code
+ */
+errno_t ui_clickmatic_create(ui_t *ui, ui_clickmatic_t **rclickmatic)
+{
+	ui_clickmatic_t *clickmatic;
+
+	clickmatic = calloc(1, sizeof(ui_clickmatic_t));
+	if (clickmatic == NULL)
+		return ENOMEM;
+
+	clickmatic->ui = ui;
+	clickmatic->timer = fibril_timer_create(NULL);
+	if (clickmatic->timer == NULL) {
+		free(clickmatic);
+		return ENOMEM;
+	}
+
+	*rclickmatic = clickmatic;
+	return EOK;
+}
+
+/** Set clickmatick callbacks.
+ *
+ * @param clickmatic Clickmatic
+ * @param cb Callbacks
+ * @param arg Argument to callbacks
+ */
+void ui_clickmatic_set_cb(ui_clickmatic_t *clickmatic, ui_clickmatic_cb_t *cb,
+    void *arg)
+{
+	clickmatic->cb = cb;
+	clickmatic->arg = arg;
+}
+
+/** Destroy clickmatic.
+ *
+ * @param clickmatic Clickmatic or @c NULL
+ */
+void ui_clickmatic_destroy(ui_clickmatic_t *clickmatic)
+{
+	if (clickmatic == NULL)
+		return;
+
+	fibril_timer_destroy(clickmatic->timer);
+	free(clickmatic);
+}
+
+/** Activate clickmatic.
+ *
+ * This generates one click event, then starts repeating after delay.
+ *
+ * @param clickmatic Clickmatic
+ */
+void ui_clickmatic_press(ui_clickmatic_t *clickmatic)
+{
+	ui_clickmatic_clicked(clickmatic);
+
+	fibril_timer_set(clickmatic->timer, 1000 * clickmatic_delay_ms,
+	    ui_clickmatic_timer_fun, (void *)clickmatic);
+}
+
+/** Deactivate clickmatic.
+ *
+ * Stops generating events.
+ *
+ * @param clickmatic Clickmatic
+ */
+void ui_clickmatic_release(ui_clickmatic_t *clickmatic)
+{
+	(void) fibril_timer_clear(clickmatic->timer);
+}
+
+/** Clickmatic clicked event.
+ *
+ * @param clickmatic Clickmatic
+ */
+void ui_clickmatic_clicked(ui_clickmatic_t *clickmatic)
+{
+	if (clickmatic->cb != NULL && clickmatic->cb->clicked != NULL)
+		clickmatic->cb->clicked(clickmatic, clickmatic->arg);
+}
+
+/** Clickmatic timer function.
+ *
+ * @param arg Argument (ui_clickmatic_t *)
+ */
+static void ui_clickmatic_timer_fun(void *arg)
+{
+	ui_clickmatic_t *clickmatic = (ui_clickmatic_t *)arg;
+
+	/*
+	 * Because we are operating in a different fibril, we must lock
+	 * the UI to ensure mutual exclusion with normal UI events
+	 */
+
+	ui_lock(clickmatic->ui);
+	ui_clickmatic_clicked(clickmatic);
+	ui_unlock(clickmatic->ui);
+
+	fibril_timer_set(clickmatic->timer, 1000000 / clickmatic_rate,
+	    ui_clickmatic_timer_fun, (void *)clickmatic);
+}
+
+/** @}
+ */
Index: uspace/lib/ui/src/control.c
===================================================================
--- uspace/lib/ui/src/control.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/control.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -130,9 +130,10 @@
  *
  * @param control Control
+ * @param nfocus Number of remaining foci
  */
-void ui_control_unfocus(ui_control_t *control)
+void ui_control_unfocus(ui_control_t *control, unsigned nfocus)
 {
 	if (control->ops->unfocus != NULL)
-		control->ops->unfocus(control->ext);
+		control->ops->unfocus(control->ext, nfocus);
 }
 
Index: uspace/lib/ui/src/entry.c
===================================================================
--- uspace/lib/ui/src/entry.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/entry.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -314,4 +314,5 @@
 
 	gfx_text_fmt_init(&fmt);
+	fmt.font = res->font;
 	fmt.color = res->entry_fg_color;
 	fmt.halign = gfx_halign_left;
@@ -329,5 +330,5 @@
 	entry->text[off1] = '\0';
 
-	rc = gfx_puttext(res->font, &pos, &fmt, entry->text);
+	rc = gfx_puttext(&pos, &fmt, entry->text);
 	if (rc != EOK) {
 		(void) gfx_set_clip_rect(res->gc, NULL);
@@ -335,5 +336,5 @@
 	}
 
-	gfx_text_cont(res->font, &pos, &fmt, entry->text, &cpos, &cfmt);
+	gfx_text_cont(&pos, &fmt, entry->text, &cpos, &cfmt);
 	entry->text[off1] = c;
 
@@ -345,5 +346,5 @@
 		cfmt.color = res->entry_sel_text_fg_color;
 
-		gfx_text_rect(res->font, &cpos, &cfmt, entry->text + off1, &sel);
+		gfx_text_rect(&cpos, &cfmt, entry->text + off1, &sel);
 		sel.p0.x -= ui_entry_sel_hpad;
 		sel.p0.y -= ui_entry_sel_vpad;
@@ -359,5 +360,5 @@
 			goto error;
 
-		rc = gfx_puttext(res->font, &cpos, &cfmt, entry->text + off1);
+		rc = gfx_puttext(&cpos, &cfmt, entry->text + off1);
 		if (rc != EOK) {
 			(void) gfx_set_clip_rect(res->gc, NULL);
@@ -365,6 +366,5 @@
 		}
 
-		gfx_text_cont(res->font, &cpos, &cfmt, entry->text + off1,
-		    &cpos, &cfmt);
+		gfx_text_cont(&cpos, &cfmt, entry->text + off1, &cpos, &cfmt);
 
 		entry->text[off2] = c;
@@ -374,5 +374,5 @@
 	cfmt.color = res->entry_fg_color;
 
-	rc = gfx_puttext(res->font, &cpos, &cfmt, entry->text + off2);
+	rc = gfx_puttext(&cpos, &cfmt, entry->text + off2);
 	if (rc != EOK) {
 		(void) gfx_set_clip_rect(res->gc, NULL);
@@ -421,9 +421,9 @@
 
 	gfx_text_fmt_init(&fmt);
+	fmt.font = res->font;
 	fmt.halign = gfx_halign_left;
 	fmt.valign = gfx_valign_top;
 
-	return gfx_text_find_pos(res->font, &geom.text_pos, &fmt,
-	    entry->text, fpos);
+	return gfx_text_find_pos(&geom.text_pos, &fmt, entry->text, fpos);
 }
 
@@ -664,5 +664,4 @@
 		break;
 	}
-
 	return ui_claimed;
 }
@@ -743,13 +742,4 @@
 	if (!entry->active)
 		return ui_unclaimed;
-
-	if (event->type == KEY_PRESS && event->c >= ' ') {
-		off = 0;
-		rc = chr_encode(event->c, buf, &off, sizeof(buf));
-		if (rc == EOK) {
-			buf[off] = '\0';
-			(void) ui_entry_insert_str(entry, buf);
-		}
-	}
 
 	/*
@@ -768,4 +758,14 @@
 	if (event->type == KEY_RELEASE && event->key == KC_RSHIFT)
 		entry->rshift_held = false;
+
+	if (event->type == KEY_PRESS &&
+	    (event->mods & (KM_CTRL | KM_ALT)) == 0 && event->c >= ' ') {
+		off = 0;
+		rc = chr_encode(event->c, buf, &off, sizeof(buf));
+		if (rc == EOK) {
+			buf[off] = '\0';
+			(void) ui_entry_insert_str(entry, buf);
+		}
+	}
 
 	if (event->type == KEY_PRESS &&
@@ -1111,7 +1111,7 @@
 		anchor.y = 0;
 		gfx_text_fmt_init(&fmt);
+		fmt.font = res->font;
 		fmt.halign = entry->halign;
-		gfx_text_start_pos(res->font, &anchor, &fmt, entry->text,
-		    &tpos);
+		gfx_text_start_pos(&anchor, &fmt, entry->text, &tpos);
 		entry->scroll_pos = tpos.x - geom.text_rect.p0.x;
 	} else if (geom.text_pos.x + width < geom.text_rect.p1.x &&
Index: uspace/lib/ui/src/filedialog.c
===================================================================
--- uspace/lib/ui/src/filedialog.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/filedialog.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -41,4 +41,5 @@
 #include <ui/label.h>
 #include <ui/filedialog.h>
+#include <ui/filelist.h>
 #include <ui/pbutton.h>
 #include <ui/resource.h>
@@ -53,4 +54,13 @@
 	.close = ui_file_dialog_wnd_close,
 	.kbd = ui_file_dialog_wnd_kbd
+};
+
+static void ui_file_dialog_flist_activate_req(ui_file_list_t *, void *);
+static void ui_file_dialog_flist_selected(ui_file_list_t *, void *,
+    const char *);
+
+ui_file_list_cb_t ui_file_dialog_flist_cb = {
+	.activate_req = ui_file_dialog_flist_activate_req,
+	.selected = ui_file_dialog_flist_selected
 };
 
@@ -96,4 +106,5 @@
 	ui_label_t *label = NULL;
 	ui_entry_t *entry = NULL;
+	ui_file_list_t *flist = NULL;
 	ui_pbutton_t *bok = NULL;
 	ui_pbutton_t *bcancel = NULL;
@@ -115,10 +126,10 @@
 		wparams.rect.p0.y = 0;
 		wparams.rect.p1.x = 40;
-		wparams.rect.p1.y = 9;
+		wparams.rect.p1.y = 20;
 	} else {
 		wparams.rect.p0.x = 0;
 		wparams.rect.p0.y = 0;
 		wparams.rect.p1.x = 300;
-		wparams.rect.p1.y = 135;
+		wparams.rect.p1.y = 335;
 	}
 
@@ -167,7 +178,7 @@
 	if (ui_is_textmode(ui)) {
 		rect.p0.x = 3;
-		rect.p0.y = 4;
+		rect.p0.y = 3;
 		rect.p1.x = 37;
-		rect.p1.y = 5;
+		rect.p1.y = 4;
 	} else {
 		rect.p0.x = 10;
@@ -192,4 +203,65 @@
 	entry = NULL;
 
+	/* Files label */
+	rc = ui_label_create(ui_res, "Files:", &label);
+	if (rc != EOK)
+		goto error;
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 3;
+		rect.p0.y = 5;
+		rect.p1.x = 17;
+		rect.p1.y = 6;
+	} else {
+		rect.p0.x = 10;
+		rect.p0.y = 90;
+		rect.p1.x = 190;
+		rect.p1.y = 105;
+	}
+
+	ui_label_set_rect(label, &rect);
+
+	rc = ui_fixed_add(fixed, ui_label_ctl(label));
+	if (rc != EOK)
+		goto error;
+
+	label = NULL;
+
+	/* File list */
+
+	rc = ui_file_list_create(window, false, &flist);
+	if (rc != EOK)
+		goto error;
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 3;
+		rect.p0.y = 6;
+		rect.p1.x = 37;
+		rect.p1.y = 16;
+	} else {
+		rect.p0.x = 10;
+		rect.p0.y = 110;
+		rect.p1.x = 290;
+		rect.p1.y = 280;
+	}
+
+	ui_file_list_set_rect(flist, &rect);
+	ui_file_list_set_cb(flist, &ui_file_dialog_flist_cb, dialog);
+
+	rc = ui_fixed_add(fixed, ui_file_list_ctl(flist));
+	if (rc != EOK)
+		goto error;
+
+	dialog->flist = flist;
+	flist = NULL;
+
+	rc = ui_file_list_read_dir(dialog->flist, ".");
+	if (rc != EOK)
+		goto error;
+
+	/* OK button */
+
 	rc = ui_pbutton_create(ui_res, "OK", &bok);
 	if (rc != EOK)
@@ -201,12 +273,12 @@
 	if (ui_is_textmode(ui)) {
 		rect.p0.x = 10;
-		rect.p0.y = 6;
+		rect.p0.y = 17;
 		rect.p1.x = 20;
-		rect.p1.y = 7;
+		rect.p1.y = 18;
 	} else {
 		rect.p0.x = 55;
-		rect.p0.y = 90;
+		rect.p0.y = 290;
 		rect.p1.x = 145;
-		rect.p1.y = 118;
+		rect.p1.y = 318;
 	}
 
@@ -222,4 +294,6 @@
 	bok = NULL;
 
+	/* Cancel button */
+
 	rc = ui_pbutton_create(ui_res, "Cancel", &bcancel);
 	if (rc != EOK)
@@ -231,12 +305,12 @@
 	if (ui_is_textmode(ui)) {
 		rect.p0.x = 22;
-		rect.p0.y = 6;
+		rect.p0.y = 17;
 		rect.p1.x = 32;
-		rect.p1.y = 7;
+		rect.p1.y = 18;
 	} else {
 		rect.p0.x = 155;
-		rect.p0.y = 90;
+		rect.p0.y = 290;
 		rect.p1.x = 245;
-		rect.p1.y = 118;
+		rect.p1.y = 318;
 	}
 
@@ -263,4 +337,6 @@
 	if (entry != NULL)
 		ui_entry_destroy(entry);
+	if (flist != NULL)
+		ui_file_list_destroy(flist);
 	if (bok != NULL)
 		ui_pbutton_destroy(bok);
@@ -328,4 +404,9 @@
 	ui_file_dialog_t *dialog = (ui_file_dialog_t *) arg;
 	const char *fname;
+	ui_evclaim_t claim;
+
+	claim = ui_window_def_kbd(window, event);
+	if (claim == ui_claimed)
+		return;
 
 	if (event->type == KEY_PRESS &&
@@ -347,5 +428,20 @@
 	}
 
-	ui_window_def_kbd(window, event);
+}
+
+static void ui_file_dialog_flist_activate_req(ui_file_list_t *flist, void *arg)
+{
+	ui_file_dialog_t *dialog = (ui_file_dialog_t *) arg;
+
+	ui_file_list_activate(dialog->flist);
+	ui_entry_deactivate(dialog->ename);
+}
+
+static void ui_file_dialog_flist_selected(ui_file_list_t *flist, void *arg,
+    const char *fname)
+{
+	ui_file_dialog_t *dialog = (ui_file_dialog_t *) arg;
+
+	dialog->cb->bok(dialog, dialog->arg, fname);
 }
 
Index: uspace/lib/ui/src/filelist.c
===================================================================
--- uspace/lib/ui/src/filelist.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/src/filelist.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,721 @@
+/*
+ * 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 libui
+ * @{
+ */
+/** @file File list control.
+ *
+ * Displays a file listing.
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <str.h>
+#include <ui/control.h>
+#include <ui/filelist.h>
+#include <ui/list.h>
+#include <ui/resource.h>
+#include <vfs/vfs.h>
+#include <qsort.h>
+#include "../private/filelist.h"
+#include "../private/list.h"
+#include "../private/resource.h"
+
+static void ui_file_list_ctl_destroy(void *);
+static errno_t ui_file_list_ctl_paint(void *);
+static ui_evclaim_t ui_file_list_ctl_kbd_event(void *, kbd_event_t *);
+static ui_evclaim_t ui_file_list_ctl_pos_event(void *, pos_event_t *);
+
+/** List control ops */
+ui_control_ops_t ui_file_list_ctl_ops = {
+	.destroy = ui_file_list_ctl_destroy,
+	.paint = ui_file_list_ctl_paint,
+	.kbd_event = ui_file_list_ctl_kbd_event,
+	.pos_event = ui_file_list_ctl_pos_event
+};
+
+static void ui_file_list_list_activate_req(ui_list_t *, void *);
+static void ui_file_list_list_selected(ui_list_entry_t *, void *);
+
+/** List callbacks */
+ui_list_cb_t ui_file_list_list_cb = {
+	.activate_req = ui_file_list_list_activate_req,
+	.selected = ui_file_list_list_selected,
+	.compare = ui_file_list_list_compare,
+};
+
+/** Create file list.
+ *
+ * @param window Containing window
+ * @param active @c true iff file list should be active
+ * @param rflist Place to store pointer to new file list
+ * @return EOK on success or an error code
+ */
+errno_t ui_file_list_create(ui_window_t *window, bool active,
+    ui_file_list_t **rflist)
+{
+	ui_file_list_t *flist;
+	errno_t rc;
+
+	flist = calloc(1, sizeof(ui_file_list_t));
+	if (flist == NULL)
+		return ENOMEM;
+
+	rc = ui_control_new(&ui_file_list_ctl_ops, (void *)flist,
+	    &flist->control);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_color_new_ega(0x0f, &flist->dir_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_color_new_ega(0x0a, &flist->svc_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = ui_list_create(window, active, &flist->list);
+	if (rc != EOK)
+		goto error;
+
+	ui_list_set_cb(flist->list, &ui_file_list_list_cb, (void *)flist);
+
+	flist->window = window;
+	*rflist = flist;
+	return EOK;
+error:
+	ui_control_delete(flist->control);
+	if (flist->dir_color != NULL)
+		gfx_color_delete(flist->dir_color);
+	if (flist->svc_color != NULL)
+		gfx_color_delete(flist->svc_color);
+	free(flist);
+	return rc;
+}
+
+/** Destroy file list.
+ *
+ * @param flist File list
+ */
+void ui_file_list_destroy(ui_file_list_t *flist)
+{
+	ui_file_list_clear_entries(flist);
+	ui_list_destroy(flist->list);
+	ui_control_delete(flist->control);
+	free(flist);
+}
+
+/** Set file list callbacks.
+ *
+ * @param flist File list
+ * @param cb Callbacks
+ * @param arg Argument to callback functions
+ */
+void ui_file_list_set_cb(ui_file_list_t *flist, ui_file_list_cb_t *cb, void *arg)
+{
+	flist->cb = cb;
+	flist->cb_arg = arg;
+}
+
+/** Get base control for file list.
+ *
+ * @param flist File list
+ * @return Base UI control
+ */
+ui_control_t *ui_file_list_ctl(ui_file_list_t *flist)
+{
+	return flist->control;
+}
+
+/** Set file list rectangle.
+ *
+ * @param flist File list
+ * @param rect Rectangle
+ */
+void ui_file_list_set_rect(ui_file_list_t *flist, gfx_rect_t *rect)
+{
+	ui_list_set_rect(flist->list, rect);
+}
+
+/** Determine if file list is active.
+ *
+ * @param flist File list
+ * @return @c true iff file list is active
+ */
+bool ui_file_list_is_active(ui_file_list_t *flist)
+{
+	return ui_list_is_active(flist->list);
+}
+
+/** Activate file list.
+ *
+ * @param flist File list
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ui_file_list_activate(ui_file_list_t *flist)
+{
+	errno_t rc;
+
+	if (flist->dir != NULL) {
+		rc = vfs_cwd_set(flist->dir);
+		if (rc != EOK)
+			return rc;
+	}
+
+	return ui_list_activate(flist->list);
+}
+
+/** Deactivate file list.
+ *
+ * @param flist File list
+ */
+void ui_file_list_deactivate(ui_file_list_t *flist)
+{
+	ui_list_deactivate(flist->list);
+}
+
+/** Initialize file list entry attributes.
+ *
+ * @param attr Attributes
+ */
+void ui_file_list_entry_attr_init(ui_file_list_entry_attr_t *attr)
+{
+	memset(attr, 0, sizeof(*attr));
+}
+
+/** Append new file list entry.
+ *
+ * @param flist File list
+ * @param attr Entry attributes
+ * @return EOK on success or an error code
+ */
+errno_t ui_file_list_entry_append(ui_file_list_t *flist, ui_file_list_entry_attr_t *attr)
+{
+	ui_file_list_entry_t *entry;
+	ui_list_entry_attr_t lattr;
+	ui_list_entry_t *lentry;
+	ui_resource_t *res;
+	char *caption;
+	errno_t rc;
+	int rv;
+
+	res = ui_window_get_res(flist->window);
+
+	entry = calloc(1, sizeof(ui_file_list_entry_t));
+	if (entry == NULL)
+		return ENOMEM;
+
+	entry->flist = flist;
+	entry->name = str_dup(attr->name);
+	if (entry->name == NULL) {
+		free(entry);
+		return ENOMEM;
+	}
+
+	entry->size = attr->size;
+	entry->isdir = attr->isdir;
+	entry->svc = attr->svc;
+
+	if (attr->isdir && !res->textmode) {
+		rv = asprintf(&caption, "[%s]", attr->name);
+		if (rv < 0)
+			caption = NULL;
+	} else {
+		caption = str_dup(attr->name);
+	}
+
+	if (caption == NULL) {
+		free(entry->name);
+		free(entry);
+		return ENOMEM;
+	}
+
+	lattr.caption = caption;
+	lattr.arg = (void *)entry;
+	lattr.color = NULL;
+	lattr.bgcolor = NULL;
+
+	if (res->textmode) {
+		if (attr->isdir) {
+			lattr.color = flist->dir_color;
+			lattr.bgcolor = flist->dir_color;
+		} else if (attr->svc != 0) {
+			lattr.color = flist->svc_color;
+			lattr.bgcolor = flist->svc_color;
+		}
+	}
+
+	rc = ui_list_entry_append(flist->list, &lattr, &lentry);
+	if (rc != EOK) {
+		free(caption);
+		free(entry->name);
+		free(entry);
+		return rc;
+	}
+
+	free(caption);
+	entry->entry = lentry;
+	return EOK;
+}
+
+/** Delete file list entry.
+ *
+ * @param entry File list entry
+ */
+void ui_file_list_entry_destroy(ui_file_list_entry_t *entry)
+{
+	ui_list_entry_destroy(entry->entry);
+	free(entry->name);
+	free(entry);
+}
+
+/** Clear file list entry list.
+ *
+ * @param flist File list
+ */
+void ui_file_list_clear_entries(ui_file_list_t *flist)
+{
+	ui_file_list_entry_t *entry;
+
+	entry = ui_file_list_first(flist);
+	while (entry != NULL) {
+		ui_file_list_entry_destroy(entry);
+		entry = ui_file_list_first(flist);
+	}
+}
+
+/** Read directory into file list entry list.
+ *
+ * @param flist File list
+ * @param dirname Directory path
+ * @return EOK on success or an error code
+ */
+errno_t ui_file_list_read_dir(ui_file_list_t *flist, const char *dirname)
+{
+	DIR *dir;
+	struct dirent *dirent;
+	vfs_stat_t finfo;
+	char newdir[256];
+	char *ndir = NULL;
+	ui_file_list_entry_attr_t attr;
+	ui_file_list_entry_t *cur;
+	ui_file_list_entry_t *next;
+	char *olddn;
+	errno_t rc;
+
+	rc = vfs_cwd_set(dirname);
+	if (rc != EOK)
+		return rc;
+
+	rc = vfs_cwd_get(newdir, sizeof(newdir));
+	if (rc != EOK)
+		return rc;
+
+	ndir = str_dup(newdir);
+	if (ndir == NULL)
+		return ENOMEM;
+
+	dir = opendir(".");
+	if (dir == NULL) {
+		rc = errno;
+		goto error;
+	}
+
+	if (str_cmp(ndir, "/") != 0) {
+		/* Need to add a synthetic up-dir entry */
+		ui_file_list_entry_attr_init(&attr);
+		attr.name = "..";
+		attr.isdir = true;
+
+		rc = ui_file_list_entry_append(flist, &attr);
+		if (rc != EOK)
+			goto error;
+	}
+
+	dirent = readdir(dir);
+	while (dirent != NULL) {
+		rc = vfs_stat_path(dirent->d_name, &finfo);
+		if (rc != EOK) {
+			/* Possibly a stale entry */
+			dirent = readdir(dir);
+			continue;
+		}
+
+		ui_file_list_entry_attr_init(&attr);
+		attr.name = dirent->d_name;
+		attr.size = finfo.size;
+		attr.isdir = finfo.is_directory;
+		attr.svc = finfo.service;
+
+		rc = ui_file_list_entry_append(flist, &attr);
+		if (rc != EOK)
+			goto error;
+
+		dirent = readdir(dir);
+	}
+
+	closedir(dir);
+
+	rc = ui_file_list_sort(flist);
+	if (rc != EOK)
+		goto error;
+
+	/* Moving up? */
+	if (str_cmp(dirname, "..") == 0) {
+		/* Get the last component of old path */
+		olddn = str_rchr(flist->dir, '/');
+		if (olddn != NULL && *olddn != '\0') {
+			/* Find corresponding entry */
+			++olddn;
+			cur = ui_file_list_first(flist);
+			next = ui_file_list_next(cur);
+			while (next != NULL && str_cmp(next->name, olddn) <= 0 &&
+			    next->isdir) {
+				cur = next;
+				next = ui_file_list_next(cur);
+			}
+
+			/* Center on the entry */
+			ui_list_cursor_center(flist->list, cur->entry);
+		}
+	}
+
+	free(flist->dir);
+	flist->dir = ndir;
+
+	return EOK;
+error:
+	(void) vfs_cwd_set(flist->dir);
+	if (ndir != NULL)
+		free(ndir);
+	if (dir != NULL)
+		closedir(dir);
+	return rc;
+}
+
+/** Sort file list entries.
+ *
+ * @param flist File list
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ui_file_list_sort(ui_file_list_t *flist)
+{
+	return ui_list_sort(flist->list);
+}
+
+/** Compare two list entries within file list entries.
+ *
+ * @param ea First UI list entry
+ * @param eb Second UI list entry
+ * @return <0, =0, >=0 if a < b, a == b, a > b, resp.
+ */
+int ui_file_list_list_compare(ui_list_entry_t *ea, ui_list_entry_t *eb)
+{
+	ui_file_list_entry_t *a;
+	ui_file_list_entry_t *b;
+	int dcmp;
+
+	a = (ui_file_list_entry_t *)ui_list_entry_get_arg(ea);
+	b = (ui_file_list_entry_t *)ui_list_entry_get_arg(eb);
+
+	/* Sort directories first */
+	dcmp = b->isdir - a->isdir;
+	if (dcmp != 0)
+		return dcmp;
+
+	return str_cmp(a->name, b->name);
+}
+
+/** Return first file list entry.
+ *
+ * @param flist File list
+ * @return First file list entry or @c NULL if there are no entries
+ */
+ui_file_list_entry_t *ui_file_list_first(ui_file_list_t *flist)
+{
+	ui_list_entry_t *lentry;
+
+	lentry = ui_list_first(flist->list);
+	if (lentry == NULL)
+		return NULL;
+
+	return (ui_file_list_entry_t *)ui_list_entry_get_arg(lentry);
+}
+
+/** Return last file list entry.
+ *
+ * @param flist File list
+ * @return Last file list entry or @c NULL if there are no entries
+ */
+ui_file_list_entry_t *ui_file_list_last(ui_file_list_t *flist)
+{
+	ui_list_entry_t *lentry;
+
+	lentry = ui_list_last(flist->list);
+	if (lentry == NULL)
+		return NULL;
+
+	return (ui_file_list_entry_t *)ui_list_entry_get_arg(lentry);
+}
+
+/** Return next file list entry.
+ *
+ * @param cur Current entry
+ * @return Next entry or @c NULL if @a cur is the last entry
+ */
+ui_file_list_entry_t *ui_file_list_next(ui_file_list_entry_t *cur)
+{
+	ui_list_entry_t *lentry;
+
+	lentry = ui_list_next(cur->entry);
+	if (lentry == NULL)
+		return NULL;
+
+	return (ui_file_list_entry_t *)ui_list_entry_get_arg(lentry);
+}
+
+/** Return previous file list entry.
+ *
+ * @param cur Current entry
+ * @return Previous entry or @c NULL if @a cur is the first entry
+ */
+ui_file_list_entry_t *ui_file_list_prev(ui_file_list_entry_t *cur)
+{
+	ui_list_entry_t *lentry;
+
+	lentry = ui_list_prev(cur->entry);
+	if (lentry == NULL)
+		return NULL;
+
+	return (ui_file_list_entry_t *)ui_list_entry_get_arg(lentry);
+}
+
+/** Get entry under cursor.
+ *
+ * @param flist File list
+ * @return Current cursor
+ */
+ui_file_list_entry_t *ui_file_list_get_cursor(ui_file_list_t *flist)
+{
+	ui_list_entry_t *entry;
+
+	entry = ui_list_get_cursor(flist->list);
+	if (entry == NULL)
+		return NULL;
+
+	return (ui_file_list_entry_t *)ui_list_entry_get_arg(entry);
+}
+
+/** Open file list entry.
+ *
+ * Perform Open action on a file list entry (e.g. switch to a subdirectory).
+ *
+ * @param flist File list
+ * @param entry File list entry
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ui_file_list_open(ui_file_list_t *flist, ui_file_list_entry_t *entry)
+{
+	if (entry->isdir)
+		return ui_file_list_open_dir(flist, entry);
+	else if (entry->svc == 0)
+		return ui_file_list_open_file(flist, entry);
+	else
+		return EOK;
+}
+
+/** Open file list directory entry.
+ *
+ * Perform Open action on a directory entry (i.e. switch to the directory).
+ *
+ * @param flist File list
+ * @param entry File list entry (which is a directory)
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ui_file_list_open_dir(ui_file_list_t *flist,
+    ui_file_list_entry_t *entry)
+{
+	char *dirname;
+	errno_t rc;
+
+	assert(entry->isdir);
+
+	/*
+	 * Need to copy out name before we free the entry below
+	 * via ui_file_list_clear_entries().
+	 */
+	dirname = str_dup(entry->name);
+	if (dirname == NULL)
+		return ENOMEM;
+
+	ui_file_list_clear_entries(flist);
+
+	rc = ui_file_list_read_dir(flist, dirname);
+	if (rc != EOK) {
+		free(dirname);
+		return rc;
+	}
+
+	free(dirname);
+
+	rc = ui_file_list_paint(flist);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Open file list file entry.
+ *
+ * Perform Open action on a file entry (i.e. try running it).
+ *
+ * @param flist File list
+ * @param entry File list entry (which is a file)
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ui_file_list_open_file(ui_file_list_t *flist,
+    ui_file_list_entry_t *entry)
+{
+	ui_file_list_selected(flist, entry->name);
+	return EOK;
+}
+
+/** Request file list activation.
+ *
+ * Call back to request file list activation.
+ *
+ * @param flist File list
+ */
+void ui_file_list_activate_req(ui_file_list_t *flist)
+{
+	if (flist->cb != NULL && flist->cb->activate_req != NULL)
+		flist->cb->activate_req(flist, flist->cb_arg);
+}
+
+/** Call back when a file is selected.
+ *
+ * @param flist File list
+ * @param fname File name
+ */
+void ui_file_list_selected(ui_file_list_t *flist, const char *fname)
+{
+	if (flist->cb != NULL && flist->cb->selected != NULL)
+		flist->cb->selected(flist, flist->cb_arg, fname);
+}
+
+/** Paint file list.
+ *
+ * @param flist File list
+ * @return EOK on success or an error code.
+ */
+errno_t ui_file_list_paint(ui_file_list_t *flist)
+{
+	return ui_control_paint(ui_list_ctl(flist->list));
+}
+
+/** Destroy file list control.
+ *
+ * @param arg Argument (ui_list_t *)
+ */
+void ui_file_list_ctl_destroy(void *arg)
+{
+	ui_file_list_t *flist = (ui_file_list_t *) arg;
+
+	ui_file_list_destroy(flist);
+}
+
+/** Paint file list control.
+ *
+ * @param arg Argument (ui_file_list_t *)
+ * @return EOK on success or an error code
+ */
+errno_t ui_file_list_ctl_paint(void *arg)
+{
+	ui_file_list_t *flist = (ui_file_list_t *) arg;
+
+	return ui_file_list_paint(flist);
+}
+
+/** Handle file list control keyboard event.
+ *
+ * @param arg Argument (ui_file_list_t *)
+ * @param kbd_event Keyboard event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t ui_file_list_ctl_kbd_event(void *arg, kbd_event_t *event)
+{
+	ui_file_list_t *flist = (ui_file_list_t *) arg;
+
+	return ui_control_kbd_event(ui_list_ctl(flist->list), event);
+}
+
+/** Handle file list control position event.
+ *
+ * @param arg Argument (ui_file_list_t *)
+ * @param pos_event Position event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t ui_file_list_ctl_pos_event(void *arg, pos_event_t *event)
+{
+	ui_file_list_t *flist = (ui_file_list_t *) arg;
+
+	return ui_control_pos_event(ui_list_ctl(flist->list), event);
+}
+
+/** Activate request callback handler for UI list within file list.
+ *
+ * @param list UI list
+ * @param arg Argument (File list)
+ */
+static void ui_file_list_list_activate_req(ui_list_t *list, void *arg)
+{
+	ui_file_list_t *flist = (ui_file_list_t *)arg;
+
+	ui_file_list_activate_req(flist);
+}
+
+/** Entry selected callback handler for UI list within file list.
+ *
+ * @param entr Activated UI list entry
+ * @param arg Argument (File list entry)
+ */
+static void ui_file_list_list_selected(ui_list_entry_t *entry, void *arg)
+{
+	ui_file_list_entry_t *fentry = (void *)arg;
+
+	(void) ui_file_list_open(fentry->flist, fentry);
+}
+
+/** @}
+ */
Index: uspace/lib/ui/src/fixed.c
===================================================================
--- uspace/lib/ui/src/fixed.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/fixed.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -48,5 +48,5 @@
 static ui_evclaim_t ui_fixed_ctl_kbd_event(void *, kbd_event_t *);
 static ui_evclaim_t ui_fixed_ctl_pos_event(void *, pos_event_t *);
-static void ui_fixed_ctl_unfocus(void *);
+static void ui_fixed_ctl_unfocus(void *, unsigned);
 
 /** Push button control ops */
@@ -262,12 +262,13 @@
  *
  * @param fixed Fixed layout
- */
-void ui_fixed_unfocus(ui_fixed_t *fixed)
-{
-	ui_fixed_elem_t *elem;
-
-	elem = ui_fixed_first(fixed);
-	while (elem != NULL) {
-		ui_control_unfocus(elem->control);
+ * @param nfocus Number of remaining foci
+ */
+void ui_fixed_unfocus(ui_fixed_t *fixed, unsigned nfocus)
+{
+	ui_fixed_elem_t *elem;
+
+	elem = ui_fixed_first(fixed);
+	while (elem != NULL) {
+		ui_control_unfocus(elem->control, nfocus);
 
 		elem = ui_fixed_next(elem);
@@ -327,10 +328,11 @@
  *
  * @param arg Argument (ui_fixed_t *)
- */
-void ui_fixed_ctl_unfocus(void *arg)
-{
-	ui_fixed_t *fixed = (ui_fixed_t *) arg;
-
-	ui_fixed_unfocus(fixed);
+ * @param nfocus Number of remaining foci
+ */
+void ui_fixed_ctl_unfocus(void *arg, unsigned nfocus)
+{
+	ui_fixed_t *fixed = (ui_fixed_t *) arg;
+
+	ui_fixed_unfocus(fixed, nfocus);
 }
 
Index: uspace/lib/ui/src/label.c
===================================================================
--- uspace/lib/ui/src/label.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/label.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -215,9 +215,10 @@
 
 	gfx_text_fmt_init(&fmt);
+	fmt.font = label->res->font;
 	fmt.color = label->res->wnd_text_color;
 	fmt.halign = label->halign;
-	fmt.valign = gfx_valign_top;
-
-	rc = gfx_puttext(label->res->font, &pos, &fmt, label->text);
+	fmt.valign = label->valign;
+
+	rc = gfx_puttext(&pos, &fmt, label->text);
 	if (rc != EOK)
 		goto error;
Index: uspace/lib/ui/src/list.c
===================================================================
--- uspace/lib/ui/src/list.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/src/list.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,1655 @@
+/*
+ * 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 libui
+ * @{
+ */
+/** @file List.
+ *
+ * Simple list control.
+ */
+
+#include <errno.h>
+#include <gfx/render.h>
+#include <gfx/text.h>
+#include <stdlib.h>
+#include <str.h>
+#include <ui/control.h>
+#include <ui/list.h>
+#include <ui/paint.h>
+#include <ui/resource.h>
+#include <ui/scrollbar.h>
+#include "../private/list.h"
+#include "../private/resource.h"
+
+static void ui_list_ctl_destroy(void *);
+static errno_t ui_list_ctl_paint(void *);
+static ui_evclaim_t ui_list_ctl_kbd_event(void *, kbd_event_t *);
+static ui_evclaim_t ui_list_ctl_pos_event(void *, pos_event_t *);
+
+/** List control ops */
+ui_control_ops_t ui_list_ctl_ops = {
+	.destroy = ui_list_ctl_destroy,
+	.paint = ui_list_ctl_paint,
+	.kbd_event = ui_list_ctl_kbd_event,
+	.pos_event = ui_list_ctl_pos_event
+};
+
+enum {
+	list_entry_hpad = 2,
+	list_entry_vpad = 2,
+	list_entry_hpad_text = 1,
+	list_entry_vpad_text = 0,
+};
+
+static void ui_list_scrollbar_up(ui_scrollbar_t *, void *);
+static void ui_list_scrollbar_down(ui_scrollbar_t *, void *);
+static void ui_list_scrollbar_page_up(ui_scrollbar_t *, void *);
+static void ui_list_scrollbar_page_down(ui_scrollbar_t *, void *);
+static void ui_list_scrollbar_moved(ui_scrollbar_t *, void *, gfx_coord_t);
+
+/** List scrollbar callbacks */
+static ui_scrollbar_cb_t ui_list_scrollbar_cb = {
+	.up = ui_list_scrollbar_up,
+	.down = ui_list_scrollbar_down,
+	.page_up = ui_list_scrollbar_page_up,
+	.page_down = ui_list_scrollbar_page_down,
+	.moved = ui_list_scrollbar_moved
+};
+
+/** Create UI list.
+ *
+ * @param window Containing window
+ * @param active @c true iff list should be active
+ * @param rlist Place to store pointer to new list
+ * @return EOK on success or an error code
+ */
+errno_t ui_list_create(ui_window_t *window, bool active,
+    ui_list_t **rlist)
+{
+	ui_list_t *list;
+	errno_t rc;
+
+	list = calloc(1, sizeof(ui_list_t));
+	if (list == NULL)
+		return ENOMEM;
+
+	rc = ui_control_new(&ui_list_ctl_ops, (void *)list,
+	    &list->control);
+	if (rc != EOK) {
+		free(list);
+		return rc;
+	}
+
+	rc = ui_scrollbar_create(ui_window_get_ui(window), window,
+	    ui_sbd_vert, &list->scrollbar);
+	if (rc != EOK)
+		goto error;
+
+	ui_scrollbar_set_cb(list->scrollbar, &ui_list_scrollbar_cb,
+	    (void *) list);
+
+	list->window = window;
+	list_initialize(&list->entries);
+	list->entries_cnt = 0;
+	list->active = active;
+
+	*rlist = list;
+	return EOK;
+error:
+	ui_control_delete(list->control);
+	free(list);
+	return rc;
+}
+
+/** Destroy UI list.
+ *
+ * @param list UI list
+ */
+void ui_list_destroy(ui_list_t *list)
+{
+	ui_list_clear_entries(list);
+	ui_control_delete(list->control);
+	free(list);
+}
+
+/** Set UI list callbacks.
+ *
+ * @param list UI list
+ * @param cb Callbacks
+ * @param arg Argument to callback functions
+ */
+void ui_list_set_cb(ui_list_t *list, ui_list_cb_t *cb, void *arg)
+{
+	list->cb = cb;
+	list->cb_arg = arg;
+}
+
+/** Get UI list callback argument.
+ *
+ * @param list UI list
+ * @return Callback argument
+ */
+void *ui_list_get_cb_arg(ui_list_t *list)
+{
+	return list->cb_arg;
+}
+
+/** Get height of list entry.
+ *
+ * @param list UI list
+ * @return Entry height in pixels
+ */
+gfx_coord_t ui_list_entry_height(ui_list_t *list)
+{
+	ui_resource_t *res;
+	gfx_font_metrics_t metrics;
+	gfx_coord_t height;
+	gfx_coord_t vpad;
+
+	res = ui_window_get_res(list->window);
+
+	if (res->textmode) {
+		vpad = list_entry_vpad_text;
+	} else {
+		vpad = list_entry_vpad;
+	}
+
+	/* Normal menu entry */
+	gfx_font_get_metrics(res->font, &metrics);
+	height = metrics.ascent + metrics.descent + 1;
+
+	return height + 2 * vpad;
+}
+
+/** Paint list entry.
+ *
+ * @param entry List entry
+ * @param entry_idx Entry index (within list of entries)
+ * @return EOK on success or an error code
+ */
+errno_t ui_list_entry_paint(ui_list_entry_t *entry, size_t entry_idx)
+{
+	ui_list_t *list = entry->list;
+	gfx_context_t *gc = ui_window_get_gc(list->window);
+	ui_resource_t *res = ui_window_get_res(list->window);
+	gfx_font_t *font = ui_resource_get_font(res);
+	gfx_text_fmt_t fmt;
+	gfx_coord2_t pos;
+	gfx_rect_t rect;
+	gfx_rect_t lrect;
+	gfx_rect_t crect;
+	gfx_color_t *bgcolor;
+	gfx_coord_t hpad, vpad;
+	gfx_coord_t line_height;
+	size_t rows;
+	errno_t rc;
+
+	line_height = ui_list_entry_height(list);
+	ui_list_inside_rect(entry->list, &lrect);
+
+	gfx_text_fmt_init(&fmt);
+	fmt.font = font;
+	rows = ui_list_page_size(list) + 1;
+
+	/* Do not display entry outside of current page */
+	if (entry_idx < list->page_idx ||
+	    entry_idx >= list->page_idx + rows)
+		return EOK;
+
+	if (res->textmode) {
+		hpad = list_entry_hpad_text;
+		vpad = list_entry_vpad_text;
+	} else {
+		hpad = list_entry_hpad;
+		vpad = list_entry_vpad;
+	}
+
+	pos.x = lrect.p0.x;
+	pos.y = lrect.p0.y + line_height * (entry_idx - list->page_idx);
+
+	if (entry == list->cursor && list->active) {
+		fmt.color = res->entry_sel_text_fg_color;
+		bgcolor = res->entry_sel_text_bg_color;
+	} else {
+		if (entry->color != NULL)
+			fmt.color = entry->color;
+		else
+			fmt.color = res->entry_fg_color;
+
+		if (entry->bgcolor != NULL)
+			bgcolor = entry->bgcolor;
+		else
+			bgcolor = res->entry_bg_color;
+	}
+
+	/* Draw entry background */
+	rect.p0 = pos;
+	rect.p1.x = lrect.p1.x;
+	rect.p1.y = rect.p0.y + line_height;
+
+	/* Clip to list interior */
+	gfx_rect_clip(&rect, &lrect, &crect);
+
+	rc = gfx_set_color(gc, bgcolor);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_fill_rect(gc, &crect);
+	if (rc != EOK)
+		return rc;
+
+	/*
+	 * Make sure caption does not overflow the entry rectangle.
+	 *
+	 * XXX We probably want to measure the text width, and,
+	 * if it's too long, use gfx_text_find_pos() to find where
+	 * it should be cut off (and append some sort of overflow
+	 * marker.
+	 */
+	rc = gfx_set_clip_rect(gc, &crect);
+	if (rc != EOK)
+		return rc;
+
+	pos.x += hpad;
+	pos.y += vpad;
+
+	rc = gfx_puttext(&pos, &fmt, entry->caption);
+	if (rc != EOK) {
+		(void) gfx_set_clip_rect(gc, NULL);
+		return rc;
+	}
+
+	return gfx_set_clip_rect(gc, NULL);
+}
+
+/** Paint UI list.
+ *
+ * @param list UI list
+ */
+errno_t ui_list_paint(ui_list_t *list)
+{
+	gfx_context_t *gc = ui_window_get_gc(list->window);
+	ui_resource_t *res = ui_window_get_res(list->window);
+	ui_list_entry_t *entry;
+	int i, lines;
+	errno_t rc;
+
+	rc = gfx_set_color(gc, res->entry_bg_color);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_fill_rect(gc, &list->rect);
+	if (rc != EOK)
+		return rc;
+
+	if (!res->textmode) {
+		rc = ui_paint_inset_frame(res, &list->rect, NULL);
+		if (rc != EOK)
+			return rc;
+	}
+
+	lines = ui_list_page_size(list) + 1;
+	i = 0;
+
+	entry = list->page;
+	while (entry != NULL && i < lines) {
+		rc = ui_list_entry_paint(entry, list->page_idx + i);
+		if (rc != EOK)
+			return rc;
+
+		++i;
+		entry = ui_list_next(entry);
+	}
+
+	rc = ui_scrollbar_paint(list->scrollbar);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_update(gc);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Handle list keyboard event.
+ *
+ * @param list UI list
+ * @param event Keyboard event
+ * @return ui_claimed iff event was claimed
+ */
+ui_evclaim_t ui_list_kbd_event(ui_list_t *list, kbd_event_t *event)
+{
+	if (!list->active)
+		return ui_unclaimed;
+
+	if (event->type == KEY_PRESS) {
+		if ((event->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
+			switch (event->key) {
+			case KC_UP:
+				ui_list_cursor_up(list);
+				break;
+			case KC_DOWN:
+				ui_list_cursor_down(list);
+				break;
+			case KC_HOME:
+				ui_list_cursor_top(list);
+				break;
+			case KC_END:
+				ui_list_cursor_bottom(list);
+				break;
+			case KC_PAGE_UP:
+				ui_list_page_up(list);
+				break;
+			case KC_PAGE_DOWN:
+				ui_list_page_down(list);
+				break;
+			case KC_ENTER:
+				ui_list_selected(list->cursor);
+				break;
+			default:
+				break;
+			}
+		}
+	}
+
+	return ui_claimed;
+}
+
+/** Handle UI list position event.
+ *
+ * @param list UI list
+ * @param event Position event
+ * @return ui_claimed iff event was claimed
+ */
+ui_evclaim_t ui_list_pos_event(ui_list_t *list, pos_event_t *event)
+{
+	gfx_coord2_t pos;
+	gfx_rect_t irect;
+	ui_list_entry_t *entry;
+	gfx_coord_t line_height;
+	size_t entry_idx;
+	ui_evclaim_t claim;
+	int n;
+
+	claim = ui_scrollbar_pos_event(list->scrollbar, event);
+	if (claim == ui_claimed)
+		return ui_claimed;
+
+	line_height = ui_list_entry_height(list);
+
+	pos.x = event->hpos;
+	pos.y = event->vpos;
+	if (!gfx_pix_inside_rect(&pos, &list->rect))
+		return ui_unclaimed;
+
+	if (event->type == POS_PRESS || event->type == POS_DCLICK) {
+		ui_list_inside_rect(list, &irect);
+
+		/* Did we click on one of the entries? */
+		if (gfx_pix_inside_rect(&pos, &irect)) {
+			/* Index within page */
+			n = (pos.y - irect.p0.y) / line_height;
+
+			/* Entry and its index within entire listing */
+			entry = ui_list_page_nth_entry(list, n, &entry_idx);
+			if (entry == NULL)
+				return ui_claimed;
+
+			if (event->type == POS_PRESS) {
+				/* Move to the entry found */
+				ui_list_cursor_move(list, entry, entry_idx);
+			} else {
+				/* event->type == POS_DCLICK */
+				ui_list_selected(entry);
+			}
+		} else {
+			/* It's in the border. */
+			if (event->type == POS_PRESS) {
+				/* Top or bottom half? */
+				if (pos.y >= (irect.p0.y + irect.p1.y) / 2)
+					ui_list_page_down(list);
+				else
+					ui_list_page_up(list);
+			}
+		}
+	}
+
+	if (!list->active && event->type == POS_PRESS)
+		ui_list_activate_req(list);
+
+	return ui_claimed;
+}
+
+/** Get base control for UI list.
+ *
+ * @param list UI list
+ * @return Base UI control
+ */
+ui_control_t *ui_list_ctl(ui_list_t *list)
+{
+	return list->control;
+}
+
+/** Set UI list rectangle.
+ *
+ * @param list UI list
+ * @param rect Rectangle
+ */
+void ui_list_set_rect(ui_list_t *list, gfx_rect_t *rect)
+{
+	gfx_rect_t srect;
+
+	list->rect = *rect;
+
+	ui_list_scrollbar_rect(list, &srect);
+	ui_scrollbar_set_rect(list->scrollbar, &srect);
+}
+
+/** Get UI list page size.
+ *
+ * @param list UI list
+ * @return Number of entries that fit in list at the same time.
+ */
+unsigned ui_list_page_size(ui_list_t *list)
+{
+	gfx_coord_t line_height;
+	gfx_rect_t irect;
+
+	line_height = ui_list_entry_height(list);
+	ui_list_inside_rect(list, &irect);
+	return (irect.p1.y - irect.p0.y) / line_height;
+}
+
+/** Get UI list interior rectangle.
+ *
+ * @param list UI list
+ * @param irect Place to store interior rectangle
+ */
+void ui_list_inside_rect(ui_list_t *list, gfx_rect_t *irect)
+{
+	ui_resource_t *res = ui_window_get_res(list->window);
+	gfx_rect_t rect;
+	gfx_coord_t width;
+
+	if (res->textmode) {
+		rect = list->rect;
+	} else {
+		ui_paint_get_inset_frame_inside(res, &list->rect, &rect);
+	}
+
+	if (res->textmode) {
+		width = 1;
+	} else {
+		width = 23;
+	}
+
+	irect->p0 = rect.p0;
+	irect->p1.x = rect.p1.x - width;
+	irect->p1.y = rect.p1.y;
+}
+
+/** Get UI list scrollbar rectangle.
+ *
+ * @param list UI list
+ * @param irect Place to store interior rectangle
+ */
+void ui_list_scrollbar_rect(ui_list_t *list, gfx_rect_t *srect)
+{
+	ui_resource_t *res = ui_window_get_res(list->window);
+	gfx_rect_t rect;
+	gfx_coord_t width;
+
+	if (res->textmode) {
+		rect = list->rect;
+	} else {
+		ui_paint_get_inset_frame_inside(res, &list->rect, &rect);
+	}
+
+	if (res->textmode) {
+		width = 1;
+	} else {
+		width = 23;
+	}
+
+	srect->p0.x = rect.p1.x - width;
+	srect->p0.y = rect.p0.y;
+	srect->p1 = rect.p1;
+}
+
+/** Compute new position for UI list scrollbar thumb.
+ *
+ * @param list UI list
+ * @return New position
+ */
+gfx_coord_t ui_list_scrollbar_pos(ui_list_t *list)
+{
+	size_t entries;
+	size_t pglen;
+	size_t sbar_len;
+
+	entries = list_count(&list->entries);
+	pglen = ui_list_page_size(list);
+	sbar_len = ui_scrollbar_move_length(list->scrollbar);
+
+	if (entries > pglen)
+		return sbar_len * list->page_idx / (entries - pglen);
+	else
+		return 0;
+}
+
+/** Update UI list scrollbar position.
+ *
+ * @param list UI list
+ */
+void ui_list_scrollbar_update(ui_list_t *list)
+{
+	ui_scrollbar_set_pos(list->scrollbar,
+	    ui_list_scrollbar_pos(list));
+}
+
+/** Determine if UI list is active.
+ *
+ * @param list UI list
+ * @return @c true iff UI list is active
+ */
+bool ui_list_is_active(ui_list_t *list)
+{
+	return list->active;
+}
+
+/** Activate UI list.
+ *
+ * @param list UI list
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ui_list_activate(ui_list_t *list)
+{
+	list->active = true;
+	(void) ui_list_paint(list);
+	return EOK;
+}
+
+/** Deactivate UI list.
+ *
+ * @param list UI list
+ */
+void ui_list_deactivate(ui_list_t *list)
+{
+	list->active = false;
+	(void) ui_list_paint(list);
+}
+
+/** Initialize UI list entry attributes.
+ *
+ * @param attr Attributes
+ */
+void ui_list_entry_attr_init(ui_list_entry_attr_t *attr)
+{
+	memset(attr, 0, sizeof(*attr));
+}
+
+/** Destroy UI list control.
+ *
+ * @param arg Argument (ui_list_t *)
+ */
+void ui_list_ctl_destroy(void *arg)
+{
+	ui_list_t *list = (ui_list_t *) arg;
+
+	ui_list_destroy(list);
+}
+
+/** Paint UI list control.
+ *
+ * @param arg Argument (ui_list_t *)
+ * @return EOK on success or an error code
+ */
+errno_t ui_list_ctl_paint(void *arg)
+{
+	ui_list_t *list = (ui_list_t *) arg;
+
+	return ui_list_paint(list);
+}
+
+/** Handle UI list control keyboard event.
+ *
+ * @param arg Argument (ui_list_t *)
+ * @param kbd_event Keyboard event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t ui_list_ctl_kbd_event(void *arg, kbd_event_t *event)
+{
+	ui_list_t *list = (ui_list_t *) arg;
+
+	return ui_list_kbd_event(list, event);
+}
+
+/** Handle UI list control position event.
+ *
+ * @param arg Argument (ui_list_t *)
+ * @param pos_event Position event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t ui_list_ctl_pos_event(void *arg, pos_event_t *event)
+{
+	ui_list_t *list = (ui_list_t *) arg;
+
+	return ui_list_pos_event(list, event);
+}
+
+/** Append new UI list entry.
+ *
+ * @param list UI list
+ * @param attr Entry attributes
+ * @param rentry Place to store pointer to new entry or @c NULL if not
+ *               interested
+ * @return EOK on success or an error code
+ */
+errno_t ui_list_entry_append(ui_list_t *list, ui_list_entry_attr_t *attr,
+    ui_list_entry_t **rentry)
+{
+	ui_list_entry_t *entry;
+
+	entry = calloc(1, sizeof(ui_list_entry_t));
+	if (entry == NULL)
+		return ENOMEM;
+
+	entry->list = list;
+	entry->caption = str_dup(attr->caption);
+	if (entry->caption == NULL) {
+		free(entry);
+		return ENOMEM;
+	}
+
+	entry->arg = attr->arg;
+	entry->color = attr->color;
+	entry->bgcolor = attr->bgcolor;
+	link_initialize(&entry->lentries);
+	list_append(&entry->lentries, &list->entries);
+
+	if (list->entries_cnt == 0) {
+		/* Adding first entry - need to set the cursor */
+		list->cursor = entry;
+		list->cursor_idx = 0;
+		list->page = entry;
+		list->page_idx = 0;
+	}
+
+	++list->entries_cnt;
+
+	if (rentry != NULL)
+		*rentry = entry;
+	return EOK;
+}
+
+/** Move UI list entry one position up.
+ *
+ * @parm entry UI list entry
+ */
+void ui_list_entry_move_up(ui_list_entry_t *entry)
+{
+	ui_list_t *list = entry->list;
+	ui_list_entry_t *prev;
+
+	prev = ui_list_prev(entry);
+	if (prev == NULL) {
+		/* Entry is already on first position, nothing to do. */
+		return;
+	}
+
+	list_remove(&entry->lentries);
+	list_insert_before(&entry->lentries, &prev->lentries);
+
+	/* Make sure page stays on the same position/idx as it was before */
+	if (list->page == entry) {
+		list->page = prev;
+	} else if (list->page == prev) {
+		list->page = entry;
+	}
+
+	/*
+	 * Return cursor to the same position/idx as it was before,
+	 * but then move it using ui_list_cursor_move() to the new
+	 * position (this ensures scrolling when needed).
+	 */
+	if (list->cursor == entry) {
+		list->cursor = prev;
+		ui_list_cursor_move(list, entry, list->cursor_idx - 1);
+	} else if (list->cursor == prev) {
+		list->cursor = entry;
+		ui_list_cursor_move(list, prev, list->cursor_idx + 1);
+	}
+}
+
+/** Move UI list entry one position down.
+ *
+ * @parm entry UI list entry
+ */
+void ui_list_entry_move_down(ui_list_entry_t *entry)
+{
+	ui_list_t *list = entry->list;
+	ui_list_entry_t *next;
+
+	next = ui_list_next(entry);
+	if (next == NULL) {
+		/* Entry is already on last position, nothing to do. */
+		return;
+	}
+
+	list_remove(&entry->lentries);
+	list_insert_after(&entry->lentries, &next->lentries);
+
+	/* Make sure page stays on the same position/idx as it was before */
+	if (list->page == entry) {
+		list->page = next;
+	} else if (list->page == next) {
+		list->page = entry;
+	}
+
+	/*
+	 * Return cursor to the same position/idx as it was before,
+	 * but then move it using ui_list_cursor_move() to the new
+	 * position (this ensures scrolling when needed).
+	 */
+	if (list->cursor == entry) {
+		list->cursor = next;
+		ui_list_cursor_move(list, entry, list->cursor_idx + 1);
+	} else if (list->cursor == next) {
+		list->cursor = entry;
+		ui_list_cursor_move(list, next, list->cursor_idx - 1);
+	}
+}
+
+/** Destroy UI list entry.
+ *
+ * This is the quick way, but does not update cursor or page position.
+ *
+ * @param entry UI list entry
+ */
+void ui_list_entry_destroy(ui_list_entry_t *entry)
+{
+	if (entry->list->cursor == entry)
+		entry->list->cursor = NULL;
+	if (entry->list->page == entry)
+		entry->list->page = NULL;
+
+	list_remove(&entry->lentries);
+	--entry->list->entries_cnt;
+	free((char *) entry->caption);
+	free(entry);
+}
+
+/** Delete UI list entry.
+ *
+ * If required, update cursor and page position and repaint.
+ *
+ * @param entry UI list entry
+ */
+void ui_list_entry_delete(ui_list_entry_t *entry)
+{
+	ui_list_t *list = entry->list;
+
+	/* Try to make sure entry does not disappear under cursor or page */
+	if (entry->list->cursor == entry)
+		ui_list_cursor_up(entry->list);
+	if (entry->list->cursor == entry)
+		ui_list_cursor_down(entry->list);
+	if (entry->list->page == entry)
+		ui_list_scroll_up(entry->list);
+	if (entry->list->page == entry)
+		ui_list_scroll_down(entry->list);
+
+	ui_list_entry_destroy(entry);
+
+	/*
+	 * But it could still happen if there are not enough entries.
+	 * In that case just move page and/or cursor to the first
+	 * entry.
+	 */
+	if (list->page == NULL) {
+		list->page = ui_list_first(list);
+		list->page_idx = 0;
+	} else {
+		/*
+		 * Entry index might have changed if earlier entry
+		 * was deleted.
+		 */
+		list->page_idx = ui_list_entry_get_idx(list->page);
+	}
+
+	if (list->cursor == NULL) {
+		list->cursor = ui_list_first(list);
+		list->cursor_idx = 0;
+	} else {
+		/*
+		 * Entry index might have changed if earlier entry
+		 * was deleted.
+		 */
+		list->cursor_idx = ui_list_entry_get_idx(list->cursor);
+	}
+}
+
+/** Get entry argument.
+ *
+ * @param entry UI list entry
+ * @return User argument
+ */
+void *ui_list_entry_get_arg(ui_list_entry_t *entry)
+{
+	return entry->arg;
+}
+
+/** Get containing list.
+ *
+ * @param entry UI list entry
+ * @return Containing list
+ */
+ui_list_t *ui_list_entry_get_list(ui_list_entry_t *entry)
+{
+	return entry->list;
+}
+
+/** Change list entry caption.
+ *
+ * @param entry UI list entry
+ * @param caption New caption
+ *
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ui_list_entry_set_caption(ui_list_entry_t *entry, const char *caption)
+{
+	char *dcaption;
+
+	dcaption = str_dup(caption);
+	if (dcaption == NULL)
+		return ENOMEM;
+
+	free(entry->caption);
+	entry->caption = dcaption;
+
+	(void)ui_list_entry_paint(entry, ui_list_entry_get_idx(entry));
+	return EOK;
+}
+
+/** Clear UI list entry list.
+ *
+ * @param list UI list
+ */
+void ui_list_clear_entries(ui_list_t *list)
+{
+	ui_list_entry_t *entry;
+
+	entry = ui_list_first(list);
+	while (entry != NULL) {
+		ui_list_entry_destroy(entry);
+		entry = ui_list_first(list);
+	}
+}
+
+/** Get number of UI list entries.
+ *
+ * @param list UI list
+ * @return Number of entries
+ */
+size_t ui_list_entries_cnt(ui_list_t *list)
+{
+	return list->entries_cnt;
+}
+
+/** Return first UI list entry.
+ *
+ * @param list UI list
+ * @return First UI list entry or @c NULL if there are no entries
+ */
+ui_list_entry_t *ui_list_first(ui_list_t *list)
+{
+	link_t *link;
+
+	link = list_first(&list->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_list_entry_t, lentries);
+}
+
+/** Return last UI list entry.
+ *
+ * @param list UI list
+ * @return Last UI list entry or @c NULL if there are no entries
+ */
+ui_list_entry_t *ui_list_last(ui_list_t *list)
+{
+	link_t *link;
+
+	link = list_last(&list->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_list_entry_t, lentries);
+}
+
+/** Return next UI list entry.
+ *
+ * @param cur Current entry
+ * @return Next entry or @c NULL if @a cur is the last entry
+ */
+ui_list_entry_t *ui_list_next(ui_list_entry_t *cur)
+{
+	link_t *link;
+
+	link = list_next(&cur->lentries, &cur->list->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_list_entry_t, lentries);
+}
+
+/** Return previous UI list entry.
+ *
+ * @param cur Current entry
+ * @return Previous entry or @c NULL if @a cur is the first entry
+ */
+ui_list_entry_t *ui_list_prev(ui_list_entry_t *cur)
+{
+	link_t *link;
+
+	link = list_prev(&cur->lentries, &cur->list->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_list_entry_t, lentries);
+}
+
+/** Find the n-th entry of the current UI list page.
+ *
+ * @param list UI list
+ * @param n Which entry to get (starting from 0)
+ * @param ridx Place to store index (within listing) of the entry
+ * @return n-th entry of the page
+ */
+ui_list_entry_t *ui_list_page_nth_entry(ui_list_t *list,
+    size_t n, size_t *ridx)
+{
+	ui_list_entry_t *entry;
+	size_t i;
+	size_t idx;
+
+	assert(n <= ui_list_page_size(list));
+
+	entry = list->page;
+	if (entry == NULL)
+		return NULL;
+
+	idx = list->page_idx;
+	for (i = 0; i < n; i++) {
+		entry = ui_list_next(entry);
+		if (entry == NULL)
+			return NULL;
+
+		++idx;
+	}
+
+	*ridx = idx;
+	return entry;
+}
+
+/** Get entry under cursor.
+ *
+ * @param list UI list
+ * @return Current cursor
+ */
+ui_list_entry_t *ui_list_get_cursor(ui_list_t *list)
+{
+	return list->cursor;
+}
+
+/** Set new cursor position.
+ *
+ * O(N) in list size, use with caution.
+ *
+ * @param list UI list
+ * @param entry New cursor position
+ */
+void ui_list_set_cursor(ui_list_t *list, ui_list_entry_t *entry)
+{
+	size_t idx;
+
+	idx = ui_list_entry_get_idx(entry);
+	ui_list_cursor_move(list, entry, idx);
+}
+
+/** Move cursor to a new position, possibly scrolling.
+ *
+ * @param list UI list
+ * @param entry New entry under cursor
+ * @param entry_idx Index of new entry under cursor
+ */
+void ui_list_cursor_move(ui_list_t *list,
+    ui_list_entry_t *entry, size_t entry_idx)
+{
+	gfx_context_t *gc = ui_window_get_gc(list->window);
+	ui_list_entry_t *old_cursor;
+	size_t old_idx;
+	size_t rows;
+	ui_list_entry_t *e;
+	size_t i;
+
+	rows = ui_list_page_size(list);
+
+	old_cursor = list->cursor;
+	old_idx = list->cursor_idx;
+
+	list->cursor = entry;
+	list->cursor_idx = entry_idx;
+
+	if (entry_idx >= list->page_idx &&
+	    entry_idx < list->page_idx + rows) {
+		/*
+		 * If cursor is still on the current page, we're not
+		 * scrolling. Just unpaint old cursor and paint new
+		 * cursor.
+		 */
+		ui_list_entry_paint(old_cursor, old_idx);
+		ui_list_entry_paint(list->cursor, list->cursor_idx);
+
+		(void) gfx_update(gc);
+	} else {
+		/*
+		 * Need to scroll and update all rows.
+		 */
+
+		/* Scrolling up */
+		if (entry_idx < list->page_idx) {
+			list->page = entry;
+			list->page_idx = entry_idx;
+		}
+
+		/* Scrolling down */
+		if (entry_idx >= list->page_idx + rows) {
+			if (entry_idx >= rows) {
+				list->page_idx = entry_idx - rows + 1;
+				/* Find first page entry (go back rows - 1) */
+				e = entry;
+				for (i = 0; i + 1 < rows; i++) {
+					e = ui_list_prev(e);
+				}
+
+				/* Should be valid */
+				assert(e != NULL);
+				list->page = e;
+			} else {
+				list->page = ui_list_first(list);
+				list->page_idx = 0;
+			}
+		}
+
+		ui_list_scrollbar_update(list);
+		(void) ui_list_paint(list);
+	}
+}
+
+/** Move cursor one entry up.
+ *
+ * @param list UI list
+ */
+void ui_list_cursor_up(ui_list_t *list)
+{
+	ui_list_entry_t *prev;
+	size_t prev_idx;
+
+	prev = ui_list_prev(list->cursor);
+	prev_idx = list->cursor_idx - 1;
+	if (prev != NULL)
+		ui_list_cursor_move(list, prev, prev_idx);
+}
+
+/** Move cursor one entry down.
+ *
+ * @param list UI list
+ */
+void ui_list_cursor_down(ui_list_t *list)
+{
+	ui_list_entry_t *next;
+	size_t next_idx;
+
+	next = ui_list_next(list->cursor);
+	next_idx = list->cursor_idx + 1;
+	if (next != NULL)
+		ui_list_cursor_move(list, next, next_idx);
+}
+
+/** Move cursor to top.
+ *
+ * @param list UI list
+ */
+void ui_list_cursor_top(ui_list_t *list)
+{
+	ui_list_cursor_move(list, ui_list_first(list), 0);
+}
+
+/** Move cursor to bottom.
+ *
+ * @param list UI list
+ */
+void ui_list_cursor_bottom(ui_list_t *list)
+{
+	ui_list_cursor_move(list, ui_list_last(list),
+	    list->entries_cnt - 1);
+}
+
+/** Move cursor one page up.
+ *
+ * @param list UI list
+ */
+void ui_list_page_up(ui_list_t *list)
+{
+	gfx_context_t *gc = ui_window_get_gc(list->window);
+	ui_list_entry_t *old_page;
+	ui_list_entry_t *old_cursor;
+	size_t old_idx;
+	size_t rows;
+	ui_list_entry_t *entry;
+	size_t i;
+
+	rows = ui_list_page_size(list);
+
+	old_page = list->page;
+	old_cursor = list->cursor;
+	old_idx = list->cursor_idx;
+
+	/* Move page by rows entries up (if possible) */
+	for (i = 0; i < rows; i++) {
+		entry = ui_list_prev(list->page);
+		if (entry != NULL) {
+			list->page = entry;
+			--list->page_idx;
+		}
+	}
+
+	/* Move cursor by rows entries up (if possible) */
+
+	for (i = 0; i < rows; i++) {
+		entry = ui_list_prev(list->cursor);
+		if (entry != NULL) {
+			list->cursor = entry;
+			--list->cursor_idx;
+		}
+	}
+
+	if (list->page != old_page) {
+		/* We have scrolled. Need to repaint all entries */
+		ui_list_scrollbar_update(list);
+		(void) ui_list_paint(list);
+	} else if (list->cursor != old_cursor) {
+		/* No scrolling, but cursor has moved */
+		ui_list_entry_paint(old_cursor, old_idx);
+		ui_list_entry_paint(list->cursor, list->cursor_idx);
+
+		(void) gfx_update(gc);
+	}
+}
+
+/** Move cursor one page down.
+ *
+ * @param list UI list
+ */
+void ui_list_page_down(ui_list_t *list)
+{
+	gfx_context_t *gc = ui_window_get_gc(list->window);
+	ui_list_entry_t *old_page;
+	ui_list_entry_t *old_cursor;
+	size_t old_idx;
+	size_t max_idx;
+	size_t rows;
+	ui_list_entry_t *entry;
+	size_t i;
+
+	rows = ui_list_page_size(list);
+
+	old_page = list->page;
+	old_cursor = list->cursor;
+	old_idx = list->cursor_idx;
+
+	if (list->entries_cnt > rows)
+		max_idx = list->entries_cnt - rows;
+	else
+		max_idx = 0;
+
+	/* Move page by rows entries down (if possible) */
+	for (i = 0; i < rows; i++) {
+		entry = ui_list_next(list->page);
+		/* Do not scroll that results in a short page */
+		if (entry != NULL && list->page_idx < max_idx) {
+			list->page = entry;
+			++list->page_idx;
+		}
+	}
+
+	/* Move cursor by rows entries down (if possible) */
+
+	for (i = 0; i < rows; i++) {
+		entry = ui_list_next(list->cursor);
+		if (entry != NULL) {
+			list->cursor = entry;
+			++list->cursor_idx;
+		}
+	}
+
+	if (list->page != old_page) {
+		/* We have scrolled. Need to repaint all entries */
+		ui_list_scrollbar_update(list);
+		(void) ui_list_paint(list);
+	} else if (list->cursor != old_cursor) {
+		/* No scrolling, but cursor has moved */
+		ui_list_entry_paint(old_cursor, old_idx);
+		ui_list_entry_paint(list->cursor, list->cursor_idx);
+
+		(void) gfx_update(gc);
+	}
+}
+
+/** Scroll one entry up.
+ *
+ * @param list UI list
+ */
+void ui_list_scroll_up(ui_list_t *list)
+{
+	ui_list_entry_t *prev;
+
+	if (list->page == NULL)
+		return;
+
+	prev = ui_list_prev(list->page);
+	if (prev == NULL)
+		return;
+
+	list->page = prev;
+	assert(list->page_idx > 0);
+	--list->page_idx;
+
+	ui_list_scrollbar_update(list);
+	(void) ui_list_paint(list);
+}
+
+/** Scroll one entry down.
+ *
+ * @param list UI list
+ */
+void ui_list_scroll_down(ui_list_t *list)
+{
+	ui_list_entry_t *next;
+	ui_list_entry_t *pgend;
+	size_t i;
+	size_t rows;
+
+	if (list->page == NULL)
+		return;
+
+	next = ui_list_next(list->page);
+	if (next == NULL)
+		return;
+
+	rows = ui_list_page_size(list);
+
+	/* Find last page entry */
+	pgend = list->page;
+	for (i = 0; i < rows && pgend != NULL; i++) {
+		pgend = ui_list_next(pgend);
+	}
+
+	/* Scroll down by one entry, if the page remains full */
+	if (pgend != NULL) {
+		list->page = next;
+		++list->page_idx;
+	}
+
+	ui_list_scrollbar_update(list);
+	(void) ui_list_paint(list);
+}
+
+/** Scroll one page up.
+ *
+ * @param list UI list
+ */
+void ui_list_scroll_page_up(ui_list_t *list)
+{
+	ui_list_entry_t *prev;
+	size_t i;
+	size_t rows;
+
+	prev = ui_list_prev(list->page);
+	if (prev == NULL)
+		return;
+
+	rows = ui_list_page_size(list);
+
+	for (i = 0; i < rows && prev != NULL; i++) {
+		list->page = prev;
+		assert(list->page_idx > 0);
+		--list->page_idx;
+		prev = ui_list_prev(prev);
+	}
+
+	ui_list_scrollbar_update(list);
+	(void) ui_list_paint(list);
+}
+
+/** Scroll one page down.
+ *
+ * @param list UI list
+ */
+void ui_list_scroll_page_down(ui_list_t *list)
+{
+	ui_list_entry_t *next;
+	ui_list_entry_t *pgend;
+	size_t i;
+	size_t rows;
+
+	next = ui_list_next(list->page);
+	if (next == NULL)
+		return;
+
+	rows = ui_list_page_size(list);
+
+	/* Find last page entry */
+	pgend = list->page;
+	for (i = 0; i < rows && pgend != NULL; i++) {
+		pgend = ui_list_next(pgend);
+	}
+
+	/* Scroll by up to 'rows' entries, keeping the page full */
+	for (i = 0; i < rows && pgend != NULL; i++) {
+		list->page = next;
+		++list->page_idx;
+		next = ui_list_next(next);
+		pgend = ui_list_next(pgend);
+	}
+
+	ui_list_scrollbar_update(list);
+	(void) ui_list_paint(list);
+}
+
+/** Scroll to a specific entry
+ *
+ * @param list UI list
+ * @param page_idx New index of first entry on the page
+ */
+void ui_list_scroll_pos(ui_list_t *list, size_t page_idx)
+{
+	ui_list_entry_t *entry;
+	size_t i;
+
+	entry = ui_list_first(list);
+	for (i = 0; i < page_idx; i++) {
+		entry = ui_list_next(entry);
+		assert(entry != NULL);
+	}
+
+	list->page = entry;
+	list->page_idx = page_idx;
+
+	(void) ui_list_paint(list);
+}
+
+/** Request UI list activation.
+ *
+ * Call back to request UI list activation.
+ *
+ * @param list UI list
+ */
+void ui_list_activate_req(ui_list_t *list)
+{
+	if (list->cb != NULL && list->cb->activate_req != NULL) {
+		list->cb->activate_req(list, list->cb_arg);
+	} else {
+		/*
+		 * If there is no callback for activation request,
+		 * just activate the list.
+		 */
+		ui_list_activate(list);
+	}
+}
+
+/** Sort list entries.
+ *
+ * @param list UI list
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ui_list_sort(ui_list_t *list)
+{
+	ui_list_entry_t **emap;
+	ui_list_entry_t *entry;
+	size_t i;
+
+	/* Create an array to hold pointer to each entry */
+	emap = calloc(list->entries_cnt, sizeof(ui_list_entry_t *));
+	if (emap == NULL)
+		return ENOMEM;
+
+	/* Write entry pointers to array */
+	entry = ui_list_first(list);
+	i = 0;
+	while (entry != NULL) {
+		assert(i < list->entries_cnt);
+		emap[i++] = entry;
+		entry = ui_list_next(entry);
+	}
+
+	/* Sort the array of pointers */
+	qsort(emap, list->entries_cnt, sizeof(ui_list_entry_t *),
+	    ui_list_entry_ptr_cmp);
+
+	/* Unlink entries from entry list */
+	entry = ui_list_first(list);
+	while (entry != NULL) {
+		list_remove(&entry->lentries);
+		entry = ui_list_first(list);
+	}
+
+	/* Add entries back to entry list sorted */
+	for (i = 0; i < list->entries_cnt; i++)
+		list_append(&emap[i]->lentries, &list->entries);
+
+	free(emap);
+
+	list->page = ui_list_first(list);
+	list->page_idx = 0;
+	list->cursor = ui_list_first(list);
+	list->cursor_idx = 0;
+	return EOK;
+}
+
+/** Determine list entry index.
+ *
+ * @param entry List entry
+ * @return List entry index
+ */
+size_t ui_list_entry_get_idx(ui_list_entry_t *entry)
+{
+	ui_list_entry_t *ep;
+	size_t idx;
+
+	idx = 0;
+	ep = ui_list_prev(entry);
+	while (ep != NULL) {
+		++idx;
+		ep = ui_list_prev(ep);
+	}
+
+	return idx;
+}
+
+/** Center list cursor on entry.
+ *
+ * @param list UI list
+ * @param entry Entry
+ */
+void ui_list_cursor_center(ui_list_t *list, ui_list_entry_t *entry)
+{
+	ui_list_entry_t *prev;
+	size_t idx;
+	size_t max_idx;
+	size_t pg_size;
+	size_t i;
+
+	idx = ui_list_entry_get_idx(entry);
+	list->cursor = entry;
+	list->cursor_idx = idx;
+
+	/* Move page so that cursor is in the center */
+	list->page = list->cursor;
+	list->page_idx = list->cursor_idx;
+
+	pg_size = ui_list_page_size(list);
+
+	for (i = 0; i < pg_size / 2; i++) {
+		prev = ui_list_prev(list->page);
+		if (prev == NULL)
+			break;
+
+		list->page = prev;
+		--list->page_idx;
+	}
+
+	/* Make sure page is not beyond the end if possible */
+	if (list->entries_cnt > pg_size)
+		max_idx = list->entries_cnt - pg_size;
+	else
+		max_idx = 0;
+
+	while (list->page_idx > 0 && list->page_idx > max_idx) {
+		prev = ui_list_prev(list->page);
+		if (prev == NULL)
+			break;
+
+		list->page = prev;
+		--list->page_idx;
+	}
+}
+
+/** Call back when an entry is selected.
+ *
+ * @param entry UI list entry
+ * @param fname File name
+ */
+void ui_list_selected(ui_list_entry_t *entry)
+{
+	if (entry->list->cb != NULL && entry->list->cb->selected != NULL)
+		entry->list->cb->selected(entry, entry->arg);
+}
+
+/** UI list scrollbar up button pressed.
+ *
+ * @param scrollbar Scrollbar
+ * @param arg Argument (ui_list_t *)
+ */
+static void ui_list_scrollbar_up(ui_scrollbar_t *scrollbar, void *arg)
+{
+	ui_list_t *list = (ui_list_t *)arg;
+	ui_list_scroll_up(list);
+}
+
+/** UI list scrollbar down button pressed.
+ *
+ * @param scrollbar Scrollbar
+ * @param arg Argument (ui_list_t *)
+ */
+static void ui_list_scrollbar_down(ui_scrollbar_t *scrollbar, void *arg)
+{
+	ui_list_t *list = (ui_list_t *)arg;
+	ui_list_scroll_down(list);
+}
+
+/** UI list scrollbar page up pressed.
+ *
+ * @param scrollbar Scrollbar
+ * @param arg Argument (ui_list_t *)
+ */
+static void ui_list_scrollbar_page_up(ui_scrollbar_t *scrollbar, void *arg)
+{
+	ui_list_t *list = (ui_list_t *)arg;
+	ui_list_scroll_page_up(list);
+}
+
+/** UI list scrollbar page down pressed.
+ *
+ * @param scrollbar Scrollbar
+ * @param arg Argument (ui_list_t *)
+ */
+static void ui_list_scrollbar_page_down(ui_scrollbar_t *scrollbar,
+    void *arg)
+{
+	ui_list_t *list = (ui_list_t *)arg;
+	ui_list_scroll_page_down(list);
+}
+
+/** UI list scrollbar moved.
+ *
+ * @param scrollbar Scrollbar
+ * @param arg Argument (ui_list_t *)
+ */
+static void ui_list_scrollbar_moved(ui_scrollbar_t *scrollbar, void *arg,
+    gfx_coord_t pos)
+{
+	ui_list_t *list = (ui_list_t *)arg;
+	size_t entries;
+	size_t pglen;
+	size_t sbar_len;
+	size_t pgstart;
+
+	entries = list_count(&list->entries);
+	pglen = ui_list_page_size(list);
+	sbar_len = ui_scrollbar_move_length(list->scrollbar);
+
+	if (entries > pglen)
+		pgstart = (entries - pglen) * pos / (sbar_len - 1);
+	else
+		pgstart = 0;
+
+	ui_list_scroll_pos(list, pgstart);
+}
+
+/** Compare two list entries indirectly referenced by pointers.
+ *
+ * @param pa Pointer to pointer to first entry
+ * @param pb Pointer to pointer to second entry
+ * @return <0, =0, >=0 if pa < b, pa == pb, pa > pb, resp.
+ */
+int ui_list_entry_ptr_cmp(const void *pa, const void *pb)
+{
+	ui_list_entry_t *a = *(ui_list_entry_t **)pa;
+	ui_list_entry_t *b = *(ui_list_entry_t **)pb;
+
+	return a->list->cb->compare(a, b);
+}
+
+/** @}
+ */
Index: uspace/lib/ui/src/menu.c
===================================================================
--- uspace/lib/ui/src/menu.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/menu.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -35,4 +35,5 @@
 
 #include <adt/list.h>
+#include <ctype.h>
 #include <errno.h>
 #include <gfx/color.h>
@@ -43,4 +44,7 @@
 #include <stdlib.h>
 #include <str.h>
+#include <uchar.h>
+#include <ui/ui.h>
+#include <ui/accel.h>
 #include <ui/control.h>
 #include <ui/paint.h>
@@ -50,5 +54,4 @@
 #include <ui/resource.h>
 #include <ui/window.h>
-#include "../private/menubar.h"
 #include "../private/menu.h"
 #include "../private/resource.h"
@@ -63,8 +66,11 @@
 
 static void ui_menu_popup_close(ui_popup_t *, void *);
+static void ui_menu_popup_kbd(ui_popup_t *, void *, kbd_event_t *);
 static void ui_menu_popup_pos(ui_popup_t *, void *, pos_event_t *);
+static void ui_menu_key_press_unmod(ui_menu_t *, kbd_event_t *);
 
 static ui_popup_cb_t ui_menu_popup_cb = {
 	.close = ui_menu_popup_close,
+	.kbd = ui_menu_popup_kbd,
 	.pos = ui_menu_popup_pos
 };
@@ -72,11 +78,10 @@
 /** Create new menu.
  *
+ * @param parent Parent window
  * @param mbar Menu bar
- * @param caption Caption
  * @param rmenu Place to store pointer to new menu
  * @return EOK on success, ENOMEM if out of memory
  */
-errno_t ui_menu_create(ui_menu_bar_t *mbar, const char *caption,
-    ui_menu_t **rmenu)
+errno_t ui_menu_create(ui_window_t *parent, ui_menu_t **rmenu)
 {
 	ui_menu_t *menu;
@@ -86,12 +91,5 @@
 		return ENOMEM;
 
-	menu->caption = str_dup(caption);
-	if (menu->caption == NULL) {
-		free(menu);
-		return ENOMEM;
-	}
-
-	menu->mbar = mbar;
-	list_append(&menu->lmenus, &mbar->menus);
+	menu->parent = parent;
 	list_initialize(&menu->entries);
 
@@ -118,49 +116,18 @@
 	}
 
-	list_remove(&menu->lmenus);
 	free(menu->caption);
 	free(menu);
 }
 
-/** Get first menu in menu bar.
- *
- * @param mbar Menu bar
- * @return First menu or @c NULL if there is none
- */
-ui_menu_t *ui_menu_first(ui_menu_bar_t *mbar)
-{
-	link_t *link;
-
-	link = list_first(&mbar->menus);
-	if (link == NULL)
-		return NULL;
-
-	return list_get_instance(link, ui_menu_t, lmenus);
-}
-
-/** Get next menu in menu bar.
- *
- * @param cur Current menu
- * @return Next menu or @c NULL if @a cur is the last one
- */
-ui_menu_t *ui_menu_next(ui_menu_t *cur)
-{
-	link_t *link;
-
-	link = list_next(&cur->lmenus, &cur->mbar->menus);
-	if (link == NULL)
-		return NULL;
-
-	return list_get_instance(link, ui_menu_t, lmenus);
-}
-
-/** Get menu caption.
- *
- * @param menu Menu
- * @return Caption (owned by @a menu)
- */
-const char *ui_menu_caption(ui_menu_t *menu)
-{
-	return menu->caption;
+/** Set menu callbacks.
+ *
+ * @param menu Menu
+ * @param cb Callbacks
+ * @param arg Callback argument
+ */
+void ui_menu_set_cb(ui_menu_t *menu, ui_menu_cb_t *cb, void *arg)
+{
+	menu->cb = cb;
+	menu->arg = arg;
 }
 
@@ -174,10 +141,10 @@
     ui_menu_geom_t *geom)
 {
-	ui_resource_t *res;
 	gfx_coord2_t edim;
 	gfx_coord_t frame_w;
 	gfx_coord_t frame_h;
-
-	res = ui_window_get_res(menu->mbar->window);
+	ui_resource_t *res;
+
+	res = ui_window_get_res(menu->parent);
 
 	if (res->textmode) {
@@ -203,18 +170,4 @@
 }
 
-/** Get menu rectangle.
- *
- * @param menu Menu
- * @param spos Starting position (top-left corner)
- * @param rect Place to store menu rectangle
- */
-void ui_menu_get_rect(ui_menu_t *menu, gfx_coord2_t *spos, gfx_rect_t *rect)
-{
-	ui_menu_geom_t geom;
-
-	ui_menu_get_geom(menu, spos, &geom);
-	*rect = geom.outer_rect;
-}
-
 /** Get UI resource from menu.
  *
@@ -231,6 +184,7 @@
  * @param menu Menu
  * @param prect Parent rectangle around which the menu should be placed
- */
-errno_t ui_menu_open(ui_menu_t *menu, gfx_rect_t *prect)
+ * @param idev_id Input device associated with the menu's seat
+ */
+errno_t ui_menu_open(ui_menu_t *menu, gfx_rect_t *prect, sysarg_t idev_id)
 {
 	ui_popup_t *popup = NULL;
@@ -240,4 +194,7 @@
 	errno_t rc;
 
+	/* Select first entry */
+	menu->selected = ui_menu_entry_first(menu);
+
 	/* Determine menu dimensions */
 
@@ -249,7 +206,8 @@
 	params.rect = geom.outer_rect;
 	params.place = *prect;
-
-	rc = ui_popup_create(menu->mbar->ui, menu->mbar->window, &params,
-	    &popup);
+	params.idev_id = idev_id;
+
+	rc = ui_popup_create(ui_window_get_ui(menu->parent), menu->parent,
+	    &params, &popup);
 	if (rc != EOK)
 		return rc;
@@ -269,4 +227,14 @@
 	ui_popup_destroy(menu->popup);
 	menu->popup = NULL;
+}
+
+/** Determine if menu is open.
+ *
+ * @param menu Menu
+ * @return @c true iff menu is open
+ */
+bool ui_menu_is_open(ui_menu_t *menu)
+{
+	return menu->popup != NULL;
 }
 
@@ -439,8 +407,150 @@
 		/* Press outside menu - close it */
 		if (event->type == POS_PRESS)
-			ui_menu_bar_select(menu->mbar, NULL, NULL);
+			ui_menu_close_req(menu);
 	}
 
 	return ui_unclaimed;
+}
+
+/** Handle keyboard event in menu.
+ *
+ * @param menu Menu
+ * @param event Keyboard event
+ * @return ui_claimed iff the event was claimed
+ */
+ui_evclaim_t ui_menu_kbd_event(ui_menu_t *menu, kbd_event_t *event)
+{
+	if (event->type == KEY_PRESS && (event->mods &
+	    (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
+		ui_menu_key_press_unmod(menu, event);
+	}
+
+	if (event->type == KEY_PRESS && (event->mods & KM_ALT) != 0 &&
+	    (event->mods & (KM_CTRL | KM_SHIFT)) == 0 && event->c != '\0')
+		ui_menu_press_accel(menu, event->c, event->kbd_id);
+
+	return ui_claimed;
+}
+
+/** Move one entry up.
+ *
+ * Non-selectable entries are skipped. If we are already at the top,
+ * we wrap around.
+ *
+ * @param menu Menu
+ */
+void ui_menu_up(ui_menu_t *menu)
+{
+	gfx_coord2_t mpos;
+	ui_menu_entry_t *nentry;
+
+	if (menu->selected == NULL)
+		return;
+
+	nentry = ui_menu_entry_prev(menu->selected);
+	if (nentry == NULL)
+		nentry = ui_menu_entry_last(menu);
+
+	/* Need to find a selectable entry */
+	while (!ui_menu_entry_selectable(nentry)) {
+		nentry = ui_menu_entry_prev(nentry);
+		if (nentry == NULL)
+			nentry = ui_menu_entry_last(menu);
+
+		/* Went completely around and found nothing? */
+		if (nentry == menu->selected)
+			return;
+	}
+
+	menu->selected = nentry;
+
+	mpos.x = 0;
+	mpos.y = 0;
+	(void) ui_menu_paint(menu, &mpos);
+}
+
+/** Move one entry down.
+ *
+ * Non-selectable entries are skipped. If we are already at the bottom,
+ * we wrap around.
+ *
+ * @param menu Menu
+ */
+void ui_menu_down(ui_menu_t *menu)
+{
+	gfx_coord2_t mpos;
+	ui_menu_entry_t *nentry;
+
+	if (menu->selected == NULL)
+		return;
+
+	nentry = ui_menu_entry_next(menu->selected);
+	if (nentry == NULL)
+		nentry = ui_menu_entry_first(menu);
+
+	/* Need to find a selectable entry */
+	while (!ui_menu_entry_selectable(nentry)) {
+		nentry = ui_menu_entry_next(nentry);
+		if (nentry == NULL)
+			nentry = ui_menu_entry_first(menu);
+
+		/* Went completely around and found nothing? */
+		if (nentry == menu->selected)
+			return;
+	}
+
+	menu->selected = nentry;
+
+	mpos.x = 0;
+	mpos.y = 0;
+	(void) ui_menu_paint(menu, &mpos);
+}
+
+/** Handle key press without modifiers in menu popup window.
+ *
+ * @param menu Menu
+ * @param event Keyboard event
+ */
+static void ui_menu_key_press_unmod(ui_menu_t *menu, kbd_event_t *event)
+{
+	ui_menu_entry_t *mentry;
+	char32_t c;
+
+	switch (event->key) {
+	case KC_ESCAPE:
+		ui_menu_close_req(menu);
+		break;
+	case KC_LEFT:
+		ui_menu_left(menu, event->kbd_id);
+		break;
+	case KC_RIGHT:
+		ui_menu_right(menu, event->kbd_id);
+		break;
+	case KC_UP:
+		ui_menu_up(menu);
+		break;
+	case KC_DOWN:
+		ui_menu_down(menu);
+		break;
+	case KC_ENTER:
+		if (menu->selected != NULL &&
+		    !ui_menu_entry_is_disabled(menu->selected))
+			ui_menu_entry_activate(menu->selected);
+		break;
+	default:
+		if (event->c != '\0') {
+			mentry = ui_menu_entry_first(menu);
+			while (mentry != NULL) {
+				c = ui_menu_entry_get_accel(mentry);
+				if (c == (char32_t)tolower(event->c) &&
+				    !ui_menu_entry_is_disabled(mentry)) {
+					ui_menu_entry_activate(mentry);
+					break;
+				}
+				mentry = ui_menu_entry_next(mentry);
+			}
+		}
+		break;
+	}
 }
 
@@ -454,6 +564,20 @@
 	ui_menu_t *menu = (ui_menu_t *)arg;
 
-	/* Close the menu */
-	ui_menu_bar_select(menu->mbar, NULL, NULL);
+	/* Forward close request to caller */
+	ui_menu_close_req(menu);
+}
+
+/** Handle keyboard event in menu popup window.
+ *
+ * @param popup Menu popup window
+ * @param arg Argument (ui_menu_t *)
+ * @param event Keyboard event
+ */
+static void ui_menu_popup_kbd(ui_popup_t *popup, void *arg, kbd_event_t *event)
+{
+	ui_menu_t *menu = (ui_menu_t *)arg;
+
+	menu->idev_id = ui_popup_get_idev_id(menu->popup);
+	ui_menu_kbd_event(menu, event);
 }
 
@@ -469,4 +593,6 @@
 	gfx_coord2_t spos;
 
+	menu->idev_id = ui_popup_get_idev_id(menu->popup);
+
 	spos.x = 0;
 	spos.y = 0;
@@ -474,4 +600,58 @@
 }
 
+/** Send menu left event.
+ *
+ * @param menu Menu
+ * @param idev_id Input device ID
+ */
+void ui_menu_left(ui_menu_t *menu, sysarg_t idev_id)
+{
+	if (menu->cb != NULL && menu->cb->left != NULL)
+		menu->cb->left(menu, menu->arg, idev_id);
+}
+
+/** Send menu right event.
+ *
+ * @param menu Menu
+ * @param idev_id Input device ID
+ */
+void ui_menu_right(ui_menu_t *menu, sysarg_t idev_id)
+{
+	if (menu->cb != NULL && menu->cb->right != NULL)
+		menu->cb->right(menu, menu->arg, idev_id);
+}
+
+/** Send menu close request event.
+ *
+ * @param menu Menu
+ */
+void ui_menu_close_req(ui_menu_t *menu)
+{
+	if (menu->cb != NULL && menu->cb->close_req != NULL)
+		menu->cb->close_req(menu, menu->arg);
+}
+
+/** Send menu accelerator key press event.
+ *
+ * @param menu Menu
+ * @param c Character
+ * @param kbd_id Keyboard ID
+ */
+void ui_menu_press_accel(ui_menu_t *menu, char32_t c, sysarg_t kbd_id)
+{
+	if (menu->cb != NULL && menu->cb->press_accel != NULL)
+		menu->cb->press_accel(menu, menu->arg, c, kbd_id);
+}
+
+/** 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/menubar.c
===================================================================
--- uspace/lib/ui/src/menubar.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/menubar.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -35,4 +35,5 @@
 
 #include <adt/list.h>
+#include <ctype.h>
 #include <errno.h>
 #include <gfx/color.h>
@@ -45,9 +46,12 @@
 #include <ui/control.h>
 #include <ui/paint.h>
-#include <ui/menu.h>
 #include <ui/menubar.h>
+#include <ui/menudd.h>
+#include <ui/wdecor.h>
 #include <ui/window.h>
 #include "../private/menubar.h"
 #include "../private/resource.h"
+#include "../private/wdecor.h"
+#include "../private/window.h"
 
 enum {
@@ -60,5 +64,8 @@
 static void ui_menu_bar_ctl_destroy(void *);
 static errno_t ui_menu_bar_ctl_paint(void *);
+static ui_evclaim_t ui_menu_bar_ctl_kbd_event(void *, kbd_event_t *);
 static ui_evclaim_t ui_menu_bar_ctl_pos_event(void *, pos_event_t *);
+static void ui_menu_bar_activate_ev(ui_menu_bar_t *);
+static void ui_menu_bar_deactivate_ev(ui_menu_bar_t *);
 
 /** Menu bar control ops */
@@ -66,5 +73,6 @@
 	.destroy = ui_menu_bar_ctl_destroy,
 	.paint = ui_menu_bar_ctl_paint,
-	.pos_event = ui_menu_bar_ctl_pos_event,
+	.kbd_event = ui_menu_bar_ctl_kbd_event,
+	.pos_event = ui_menu_bar_ctl_pos_event
 };
 
@@ -93,5 +101,9 @@
 	mbar->ui = ui;
 	mbar->window = window;
-	list_initialize(&mbar->menus);
+	list_initialize(&mbar->menudds);
+
+	if (window->mbar == NULL)
+		window->mbar = mbar;
+
 	*rmbar = mbar;
 	return EOK;
@@ -104,14 +116,17 @@
 void ui_menu_bar_destroy(ui_menu_bar_t *mbar)
 {
-	ui_menu_t *menu;
+	ui_menu_dd_t *mdd;
 
 	if (mbar == NULL)
 		return;
 
-	/* Destroy menus */
-	menu = ui_menu_first(mbar);
-	while (menu != NULL) {
-		ui_menu_destroy(menu);
-		menu = ui_menu_first(mbar);
+	if (mbar->window->mbar == mbar)
+		mbar->window->mbar = NULL;
+
+	/* Destroy menu drop-downs */
+	mdd = ui_menu_dd_first(mbar);
+	while (mdd != NULL) {
+		ui_menu_dd_destroy(mdd);
+		mdd = ui_menu_dd_first(mbar);
 	}
 
@@ -120,4 +135,16 @@
 }
 
+/** Set menu bar callbacks.
+ *
+ * @param mbar Menu bar
+ * @param cb Callbacks
+ * @param arg Callback argument
+ */
+void ui_menu_bar_set_cb(ui_menu_bar_t *mbar, ui_menu_bar_cb_t *cb, void *arg)
+{
+	mbar->cb = cb;
+	mbar->arg = arg;
+}
+
 /** Get base control from menu bar.
  *
@@ -148,10 +175,10 @@
 {
 	ui_resource_t *res;
-	gfx_text_fmt_t fmt;
+	ui_text_fmt_t fmt;
 	gfx_coord2_t pos;
 	gfx_coord2_t tpos;
 	gfx_rect_t rect;
 	gfx_color_t *bg_color;
-	ui_menu_t *menu;
+	ui_menu_dd_t *mdd;
 	const char *caption;
 	gfx_coord_t width;
@@ -182,12 +209,13 @@
 	pos = mbar->rect.p0;
 
-	gfx_text_fmt_init(&fmt);
+	ui_text_fmt_init(&fmt);
+	fmt.font = res->font;
 	fmt.halign = gfx_halign_left;
 	fmt.valign = gfx_valign_top;
 
-	menu = ui_menu_first(mbar);
-	while (menu != NULL) {
-		caption = ui_menu_caption(menu);
-		width = gfx_text_width(res->font, caption) + 2 * hpad;
+	mdd = ui_menu_dd_first(mbar);
+	while (mdd != NULL) {
+		caption = ui_menu_dd_caption(mdd);
+		width = ui_text_width(res->font, caption) + 2 * hpad;
 		tpos.x = pos.x + hpad;
 		tpos.y = pos.y + vpad;
@@ -197,9 +225,11 @@
 		rect.p1.y = mbar->rect.p1.y;
 
-		if (menu == mbar->selected) {
+		if (mdd == mbar->selected) {
 			fmt.color = res->wnd_sel_text_color;
+			fmt.hgl_color = res->wnd_sel_text_hgl_color;
 			bg_color = res->wnd_sel_text_bg_color;
 		} else {
 			fmt.color = res->wnd_text_color;
+			fmt.hgl_color = res->wnd_text_hgl_color;
 			bg_color = res->wnd_face_color;
 		}
@@ -213,10 +243,10 @@
 			goto error;
 
-		rc = gfx_puttext(res->font, &tpos, &fmt, caption);
+		rc = ui_paint_text(&tpos, &fmt, caption);
 		if (rc != EOK)
 			goto error;
 
 		pos.x += width;
-		menu = ui_menu_next(menu);
+		mdd = ui_menu_dd_next(mdd);
 	}
 
@@ -236,27 +266,270 @@
  *
  * @param mbar Menu bar
- * @param rect Menu bar entry rectangle
- * @param menu Menu to select (or deselect if selected) or @c NULL
- */
-void ui_menu_bar_select(ui_menu_bar_t *mbar, gfx_rect_t *rect,
-    ui_menu_t *menu)
-{
-	ui_menu_t *old_menu;
-
-	old_menu = mbar->selected;
-
-	if (mbar->selected != menu)
-		mbar->selected = menu;
-	else
-		mbar->selected = NULL;
-
-	/* Close previously open menu */
-	if (old_menu != NULL)
-		(void) ui_menu_close(old_menu);
+ * @param mdd Menu drop-down to select (or deselect if selected) or @c NULL
+ * @param openup Open menu even if not currently open
+ * @param idev_id Input device ID associated with the selecting seat
+ */
+void ui_menu_bar_select(ui_menu_bar_t *mbar, ui_menu_dd_t *mdd, bool openup,
+    sysarg_t idev_id)
+{
+	ui_menu_dd_t *old_mdd;
+	gfx_rect_t rect;
+	bool was_open;
+
+	old_mdd = mbar->selected;
+
+	mbar->selected = mdd;
+
+	/* Close previously open menu drop-down */
+	if (old_mdd != NULL && ui_menu_dd_is_open(old_mdd)) {
+		was_open = true;
+		(void) ui_menu_dd_close(old_mdd);
+	} else {
+		was_open = false;
+	}
 
 	(void) ui_menu_bar_paint(mbar);
 
 	if (mbar->selected != NULL) {
-		(void) ui_menu_open(mbar->selected, rect);
+		ui_menu_bar_entry_rect(mbar, mbar->selected, &rect);
+		if (openup || was_open) {
+			/*
+			 * Open the newly selected menu drop-down if either
+			 * the old menu drop-down was open or @a openup was
+			 * specified.
+			 */
+			(void) ui_menu_dd_open(mbar->selected, &rect, idev_id);
+		}
+
+		if (!mbar->active)
+			ui_menu_bar_activate_ev(mbar);
+		mbar->active = true;
+	} else {
+		if (mbar->active)
+			ui_menu_bar_deactivate_ev(mbar);
+		mbar->active = false;
+	}
+}
+
+/** Select first drop-down.
+ *
+ * @param mbar Menu bar
+ * @param openup @c true to open drop-down if it was not open
+ * @param idev_id Input device ID
+ */
+void ui_menu_bar_select_first(ui_menu_bar_t *mbar, bool openup,
+    sysarg_t idev_id)
+{
+	ui_menu_dd_t *mdd;
+
+	mdd = ui_menu_dd_first(mbar);
+	ui_menu_bar_select(mbar, mdd, openup, idev_id);
+}
+
+/** Select last drop-down.
+ *
+ * @param mbar Menu bar
+ * @param openup @c true to open drop-down if it was not open
+ * @param idev_id Input device ID
+ */
+void ui_menu_bar_select_last(ui_menu_bar_t *mbar, bool openup,
+    sysarg_t idev_id)
+{
+	ui_menu_dd_t *mdd;
+
+	mdd = ui_menu_dd_last(mbar);
+	ui_menu_bar_select(mbar, mdd, openup, idev_id);
+}
+
+/** Select system menu.
+ *
+ * @param mbar Menu bar
+ * @param openup @c true to open drop-down if it was not open
+ * @param idev_id Input device ID
+ */
+void ui_menu_bar_select_sysmenu(ui_menu_bar_t *mbar, bool openup,
+    sysarg_t idev_id)
+{
+	ui_wdecor_sysmenu_hdl_set_active(mbar->window->wdecor, true);
+
+	if (openup)
+		ui_window_send_sysmenu(mbar->window, idev_id);
+}
+
+/** Move one entry left.
+ *
+ * If the selected menu is open, the newly selected menu will be open
+ * as well. If we are already at the first entry, we wrap around.
+ *
+ * @param mbar Menu bar
+ * @param idev_id Input device ID
+ */
+void ui_menu_bar_left(ui_menu_bar_t *mbar, sysarg_t idev_id)
+{
+	ui_menu_dd_t *nmdd;
+	bool sel_sysmenu = false;
+	bool was_open;
+
+	if (mbar->selected == NULL)
+		return;
+
+	nmdd = ui_menu_dd_prev(mbar->selected);
+	if (nmdd == NULL) {
+		if ((mbar->window->wdecor->style & ui_wds_sysmenu_hdl) != 0) {
+			sel_sysmenu = true;
+		} else {
+			nmdd = ui_menu_dd_last(mbar);
+		}
+	}
+
+	was_open = mbar->selected != NULL &&
+	    ui_menu_dd_is_open(mbar->selected);
+
+	if (nmdd != mbar->selected)
+		ui_menu_bar_select(mbar, nmdd, false, idev_id);
+
+	/*
+	 * Only open sysmenu *after* closing the previous menu, avoid
+	 * having multiple popup windows at the same time.
+	 */
+	if (sel_sysmenu)
+		ui_menu_bar_select_sysmenu(mbar, was_open, idev_id);
+}
+
+/** Move one entry right.
+ *
+ * If the selected menu is open, the newly selected menu will be open
+ * as well. If we are already at the last entry, we wrap around.
+ *
+ * @param mbar Menu bar
+ * @param idev_id Input device ID
+ */
+void ui_menu_bar_right(ui_menu_bar_t *mbar, sysarg_t idev_id)
+{
+	ui_menu_dd_t *nmdd;
+	bool sel_sysmenu = false;
+	bool was_open;
+
+	if (mbar->selected == NULL)
+		return;
+
+	nmdd = ui_menu_dd_next(mbar->selected);
+	if (nmdd == NULL) {
+		if ((mbar->window->wdecor->style & ui_wds_sysmenu_hdl) != 0) {
+			sel_sysmenu = true;
+		} else {
+			nmdd = ui_menu_dd_first(mbar);
+		}
+	}
+
+	was_open = mbar->selected != NULL &&
+	    ui_menu_dd_is_open(mbar->selected);
+
+	if (nmdd != mbar->selected)
+		ui_menu_bar_select(mbar, nmdd, false, idev_id);
+
+	/*
+	 * Only open sysmenu *after* closing the previous menu, avoid
+	 * having multiple popup windows at the same time.
+	 */
+	if (sel_sysmenu)
+		ui_menu_bar_select_sysmenu(mbar, was_open, idev_id);
+}
+
+/** Handle menu bar key press without modifiers.
+ *
+ * @param mbar Menu bar
+ * @param kbd_event Keyboard event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t ui_menu_bar_key_press_unmod(ui_menu_bar_t *mbar, kbd_event_t *event)
+{
+	gfx_rect_t rect;
+
+	if (event->key == KC_F10) {
+		ui_menu_bar_activate(mbar);
+		return ui_claimed;
+	}
+
+	if (!mbar->active)
+		return ui_unclaimed;
+
+	if (event->key == KC_ESCAPE) {
+		ui_menu_bar_deactivate(mbar);
+		return ui_claimed;
+	}
+
+	if (event->key == KC_LEFT)
+		ui_menu_bar_left(mbar, event->kbd_id);
+
+	if (event->key == KC_RIGHT)
+		ui_menu_bar_right(mbar, event->kbd_id);
+
+	if (event->key == KC_ENTER || event->key == KC_DOWN) {
+		if (mbar->selected != NULL &&
+		    !ui_menu_dd_is_open(mbar->selected)) {
+			ui_menu_bar_entry_rect(mbar, mbar->selected,
+			    &rect);
+			ui_menu_dd_open(mbar->selected, &rect, event->kbd_id);
+		}
+
+		return ui_claimed;
+	}
+
+	if (event->c != '\0' && !ui_menu_dd_is_open(mbar->selected)) {
+		/* Check if it is an accelerator. */
+		ui_menu_bar_press_accel(mbar, event->c, event->kbd_id);
+	}
+
+	return ui_claimed;
+}
+
+/** Handle menu bar keyboard event.
+ *
+ * @param mbar Menu bar
+ * @param kbd_event Keyboard event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t ui_menu_bar_kbd_event(ui_menu_bar_t *mbar, kbd_event_t *event)
+{
+	if ((event->mods & KM_ALT) != 0 &&
+	    (event->mods & (KM_CTRL | KM_SHIFT)) == 0 && event->c != '\0') {
+		/* Check if it is an accelerator. */
+		ui_menu_bar_press_accel(mbar, event->c, event->kbd_id);
+	}
+
+	if (event->type == KEY_PRESS && (event->mods &
+	    (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
+		return ui_menu_bar_key_press_unmod(mbar, event);
+	}
+
+	if (mbar->active)
+		return ui_claimed;
+
+	return ui_unclaimed;
+}
+
+/** Accelerator key press.
+ *
+ * If @a c matches an accelerator key, open the respective menu.
+ *
+ * @param mbar Menu bar
+ * @param c Character
+ * @param kbd_id Keyboard ID
+ */
+void ui_menu_bar_press_accel(ui_menu_bar_t *mbar, char32_t c, sysarg_t kbd_id)
+{
+	ui_menu_dd_t *mdd;
+	char32_t maccel;
+
+	mdd = ui_menu_dd_first(mbar);
+	while (mdd != NULL) {
+		maccel = ui_menu_dd_get_accel(mdd);
+		if ((char32_t)tolower(c) == maccel) {
+			ui_menu_bar_select(mbar, mdd, true, kbd_id);
+			return;
+		}
+
+		mdd = ui_menu_dd_next(mdd);
 	}
 }
@@ -273,9 +546,10 @@
 	gfx_coord2_t pos;
 	gfx_rect_t rect;
-	ui_menu_t *menu;
+	ui_menu_dd_t *mdd;
 	const char *caption;
 	gfx_coord_t width;
 	gfx_coord_t hpad;
 	gfx_coord2_t ppos;
+	sysarg_t pos_id;
 
 	res = ui_window_get_res(mbar->window);
@@ -291,9 +565,10 @@
 
 	pos = mbar->rect.p0;
-
-	menu = ui_menu_first(mbar);
-	while (menu != NULL) {
-		caption = ui_menu_caption(menu);
-		width = gfx_text_width(res->font, caption) + 2 * hpad;
+	pos_id = event->pos_id;
+
+	mdd = ui_menu_dd_first(mbar);
+	while (mdd != NULL) {
+		caption = ui_menu_dd_caption(mdd);
+		width = ui_text_width(res->font, caption) + 2 * hpad;
 
 		rect.p0 = pos;
@@ -304,13 +579,94 @@
 		if (event->type == POS_PRESS &&
 		    gfx_pix_inside_rect(&ppos, &rect)) {
-			ui_menu_bar_select(mbar, &rect, menu);
+			mbar->active = true;
+
+			/* Open the menu, if not already open. */
+			if (mdd != mbar->selected)
+				ui_menu_bar_select(mbar, mdd, true, pos_id);
+
 			return ui_claimed;
 		}
 
 		pos.x += width;
-		menu = ui_menu_next(menu);
+		mdd = ui_menu_dd_next(mdd);
 	}
 
 	return ui_unclaimed;
+}
+
+/** Handle menu bar position event.
+ *
+ * @param mbar Menu bar
+ * @param mdd Menu drop-down whose entry's rectangle is to be returned
+ * @param rrect Place to store entry rectangle
+ */
+void ui_menu_bar_entry_rect(ui_menu_bar_t *mbar, ui_menu_dd_t *mdd,
+    gfx_rect_t *rrect)
+{
+	ui_resource_t *res;
+	gfx_coord2_t pos;
+	gfx_rect_t rect;
+	ui_menu_dd_t *cur;
+	const char *caption;
+	gfx_coord_t width;
+	gfx_coord_t hpad;
+
+	res = ui_window_get_res(mbar->window);
+
+	if (res->textmode) {
+		hpad = menubar_hpad_text;
+	} else {
+		hpad = menubar_hpad;
+	}
+
+	pos = mbar->rect.p0;
+
+	cur = ui_menu_dd_first(mbar);
+	while (cur != NULL) {
+		caption = ui_menu_dd_caption(cur);
+		width = ui_text_width(res->font, caption) + 2 * hpad;
+
+		rect.p0 = pos;
+		rect.p1.x = rect.p0.x + width;
+		rect.p1.y = mbar->rect.p1.y;
+
+		if (cur == mdd) {
+			*rrect = rect;
+			return;
+		}
+
+		pos.x += width;
+		cur = ui_menu_dd_next(cur);
+	}
+
+	/* We should never get here */
+	assert(false);
+}
+
+/** Activate menu bar.
+ *
+ * @param mbar Menu bar
+ */
+void ui_menu_bar_activate(ui_menu_bar_t *mbar)
+{
+	if (mbar->active)
+		return;
+
+	mbar->active = true;
+	if (mbar->selected == NULL)
+		mbar->selected = ui_menu_dd_first(mbar);
+
+	(void) ui_menu_bar_paint(mbar);
+	ui_menu_bar_activate_ev(mbar);
+}
+
+/** Deactivate menu bar.
+ *
+ * @param mbar Menu bar
+ */
+void ui_menu_bar_deactivate(ui_menu_bar_t *mbar)
+{
+	ui_menu_bar_select(mbar, NULL, false, 0);
+	ui_menu_bar_deactivate_ev(mbar);
 }
 
@@ -338,5 +694,5 @@
 }
 
-/** Handle menu bar control position event.
+/** Handle menu bar control keyboard event.
  *
  * @param arg Argument (ui_menu_bar_t *)
@@ -344,4 +700,17 @@
  * @return @c ui_claimed iff the event is claimed
  */
+ui_evclaim_t ui_menu_bar_ctl_kbd_event(void *arg, kbd_event_t *event)
+{
+	ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
+
+	return ui_menu_bar_kbd_event(mbar, event);
+}
+
+/** Handle menu bar control position event.
+ *
+ * @param arg Argument (ui_menu_bar_t *)
+ * @param pos_event Position event
+ * @return @c ui_claimed iff the event is claimed
+ */
 ui_evclaim_t ui_menu_bar_ctl_pos_event(void *arg, pos_event_t *event)
 {
@@ -351,4 +720,24 @@
 }
 
+/** Send menu bar activate event.
+ *
+ * @param mbar Menu bar
+ */
+static void ui_menu_bar_activate_ev(ui_menu_bar_t *mbar)
+{
+	if (mbar->cb != NULL && mbar->cb->activate != NULL)
+		mbar->cb->activate(mbar, mbar->arg);
+}
+
+/** Send menu bar deactivate event.
+ *
+ * @param mbar Menu bar
+ */
+static void ui_menu_bar_deactivate_ev(ui_menu_bar_t *mbar)
+{
+	if (mbar->cb != NULL && mbar->cb->deactivate != NULL)
+		mbar->cb->deactivate(mbar, mbar->arg);
+}
+
 /** @}
  */
Index: uspace/lib/ui/src/menudd.c
===================================================================
--- uspace/lib/ui/src/menudd.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/src/menudd.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,299 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Menu drop-down
+ *
+ * One of the drop-down menus of a menu bar. This class takes the generic
+ * ui_menu and ties it to the menu bar.
+ */
+
+#include <adt/list.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <str.h>
+#include <ui/accel.h>
+#include <ui/menu.h>
+#include <ui/menudd.h>
+#include <ui/menubar.h>
+#include <ui/resource.h>
+#include "../private/menubar.h"
+#include "../private/menudd.h"
+
+static void ui_menu_dd_left(ui_menu_t *, void *, sysarg_t);
+static void ui_menu_dd_right(ui_menu_t *, void *, sysarg_t);
+static void ui_menu_dd_close_req(ui_menu_t *, void *);
+static void ui_menu_dd_press_accel(ui_menu_t *, void *, char32_t, sysarg_t);
+
+static ui_menu_cb_t ui_menu_dd_menu_cb = {
+	.left = ui_menu_dd_left,
+	.right = ui_menu_dd_right,
+	.close_req = ui_menu_dd_close_req,
+	.press_accel = ui_menu_dd_press_accel
+};
+
+/** Create new menu drop-down.
+ *
+ * @param mbar Menu bar
+ * @param caption Caption
+ * @param rmdd Place to store pointer to new menu drop-down or @c NULL
+ * @param rmenu Place to store pointer to new menu or @c NULL
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ui_menu_dd_create(ui_menu_bar_t *mbar, const char *caption,
+    ui_menu_dd_t **rmdd, ui_menu_t **rmenu)
+{
+	errno_t rc;
+	ui_menu_dd_t *mdd;
+
+	mdd = calloc(1, sizeof(ui_menu_dd_t));
+	if (mdd == NULL)
+		return ENOMEM;
+
+	mdd->caption = str_dup(caption);
+	if (mdd->caption == NULL) {
+		free(mdd);
+		return ENOMEM;
+	}
+
+	/* Create menu */
+	rc = ui_menu_create(mbar->window, &mdd->menu);
+	if (rc != EOK) {
+		free(mdd->caption);
+		free(mdd);
+		return rc;
+	}
+
+	mdd->mbar = mbar;
+	list_append(&mdd->lmenudds, &mbar->menudds);
+
+	ui_menu_set_cb(mdd->menu, &ui_menu_dd_menu_cb, (void *)mdd);
+
+	if (rmdd != NULL)
+		*rmdd = mdd;
+	if (rmenu != NULL)
+		*rmenu = mdd->menu;
+	return EOK;
+}
+
+/** Destroy menu drop-down.
+ *
+ * @param menu Menu or @c NULL
+ */
+void ui_menu_dd_destroy(ui_menu_dd_t *mdd)
+{
+	if (mdd == NULL)
+		return;
+
+	/* Destroy menu */
+	ui_menu_destroy(mdd->menu);
+
+	list_remove(&mdd->lmenudds);
+	free(mdd->caption);
+	free(mdd);
+}
+
+/** Get first menu drop-down in menu bar.
+ *
+ * @param mbar Menu bar
+ * @return First menu or @c NULL if there is none
+ */
+ui_menu_dd_t *ui_menu_dd_first(ui_menu_bar_t *mbar)
+{
+	link_t *link;
+
+	link = list_first(&mbar->menudds);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_menu_dd_t, lmenudds);
+}
+
+/** Get next menu drop-down in menu bar.
+ *
+ * @param cur Current menu drop-down
+ * @return Next menu drop-down or @c NULL if @a cur is the last one
+ */
+ui_menu_dd_t *ui_menu_dd_next(ui_menu_dd_t *cur)
+{
+	link_t *link;
+
+	link = list_next(&cur->lmenudds, &cur->mbar->menudds);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_menu_dd_t, lmenudds);
+}
+
+/** Get last menu drop-down in menu bar.
+ *
+ * @param mbar Menu bar
+ * @return Last menu drop-down or @c NULL if there is none
+ */
+ui_menu_dd_t *ui_menu_dd_last(ui_menu_bar_t *mbar)
+{
+	link_t *link;
+
+	link = list_last(&mbar->menudds);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_menu_dd_t, lmenudds);
+}
+
+/** Get previous menu drop-down in menu bar.
+ *
+ * @param cur Current menu drop-down
+ * @return Previous menu drop-down or @c NULL if @a cur is the fist one
+ */
+ui_menu_dd_t *ui_menu_dd_prev(ui_menu_dd_t *cur)
+{
+	link_t *link;
+
+	link = list_prev(&cur->lmenudds, &cur->mbar->menudds);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_menu_dd_t, lmenudds);
+}
+
+/** Get menu drop-down caption.
+ *
+ * @param mdd Menu drop-down
+ * @return Caption (owned by @a menu)
+ */
+const char *ui_menu_dd_caption(ui_menu_dd_t *mdd)
+{
+	return mdd->caption;
+}
+
+/** Get menu drop-down accelerator character.
+ *
+ * @param mdd Menu drop-down
+ * @return Accelerator character (lowercase) or the null character if
+ *         the menu has no accelerator.
+ */
+char32_t ui_menu_dd_get_accel(ui_menu_dd_t *mdd)
+{
+	return ui_accel_get(mdd->caption);
+}
+
+/** Open menu drop-down.
+ *
+ * @param mdd Menu drop-down
+ * @param prect Parent rectangle around which the drop-down should be placed
+ * @param idev_id Input device associated with the drop-down's seat
+ */
+errno_t ui_menu_dd_open(ui_menu_dd_t *mdd, gfx_rect_t *prect, sysarg_t idev_id)
+{
+	return ui_menu_open(mdd->menu, prect, idev_id);
+}
+
+/** Close menu drop-down.
+ *
+ * @param mdd Menu drop-down
+ */
+void ui_menu_dd_close(ui_menu_dd_t *mdd)
+{
+	ui_menu_close(mdd->menu);
+}
+
+/** Determine if menu drop-down is open.
+ *
+ * @param mdd Menu drop-down
+ * @return @c true iff menu drop-down is open
+ */
+bool ui_menu_dd_is_open(ui_menu_dd_t *mdd)
+{
+	return ui_menu_is_open(mdd->menu);
+}
+
+/** Handle menu left event.
+ *
+ * @param menu Menu
+ * @param arg Argument (ui_menu_dd_t *)
+ * @param idev_id Input device ID
+ */
+static void ui_menu_dd_left(ui_menu_t *menu, void *arg, sysarg_t idev_id)
+{
+	ui_menu_dd_t *mdd = (ui_menu_dd_t *)arg;
+
+	(void)menu;
+
+	ui_menu_bar_left(mdd->mbar, idev_id);
+}
+
+/** Handle menu right event.
+ *
+ * @param menu Menu
+ * @param arg Argument (ui_menu_dd_t *)
+ * @param idev_id Input device ID
+ */
+static void ui_menu_dd_right(ui_menu_t *menu, void *arg, sysarg_t idev_id)
+{
+	ui_menu_dd_t *mdd = (ui_menu_dd_t *)arg;
+
+	(void)menu;
+
+	ui_menu_bar_right(mdd->mbar, idev_id);
+}
+
+/** Handle menu close request.
+ *
+ * @param menu Menu
+ * @param arg Argument (ui_menu_dd_t *)
+ */
+static void ui_menu_dd_close_req(ui_menu_t *menu, void *arg)
+{
+	ui_menu_dd_t *mdd = (ui_menu_dd_t *)arg;
+
+	(void)menu;
+	ui_menu_bar_deactivate(mdd->mbar);
+}
+
+/** Handle menu accelerator key press event.
+ *
+ * @param menu Menu
+ * @param arg Argument (ui_menu_dd_t *)
+ * @param c Character
+ * @param kbd_id Keyboard ID
+ */
+static void ui_menu_dd_press_accel(ui_menu_t *menu, void *arg, char32_t c,
+    sysarg_t kbd_id)
+{
+	ui_menu_dd_t *mdd = (ui_menu_dd_t *)arg;
+
+	(void)menu;
+	ui_menu_bar_press_accel(mdd->mbar, c, kbd_id);
+}
+
+/** @}
+ */
Index: uspace/lib/ui/src/menuentry.c
===================================================================
--- uspace/lib/ui/src/menuentry.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/menuentry.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -43,6 +43,8 @@
 #include <stdlib.h>
 #include <str.h>
+#include <ui/accel.h>
 #include <ui/control.h>
 #include <ui/paint.h>
+#include <ui/menubar.h>
 #include <ui/menuentry.h>
 #include <ui/window.h>
@@ -143,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);
@@ -159,4 +176,24 @@
 	mentry->cb = cb;
 	mentry->arg = arg;
+}
+
+/** Set menu entry disabled flag.
+ *
+ * @param mentry Menu entry
+ * @param disabled @c true iff entry is to be disabled, @c false otherwise
+ */
+void ui_menu_entry_set_disabled(ui_menu_entry_t *mentry, bool disabled)
+{
+	mentry->disabled = disabled;
+}
+
+/** Get menu entry disabled flag.
+ *
+ * @param mentry Menu entry
+ * @return disabled @c true iff entry is disabled, @c false otherwise
+ */
+bool ui_menu_entry_is_disabled(ui_menu_entry_t *mentry)
+{
+	return mentry->disabled;
 }
 
@@ -177,4 +214,20 @@
 }
 
+/** Get last menu entry in menu.
+ *
+ * @param menu Menu
+ * @return Last menu entry or @c NULL if there is none
+ */
+ui_menu_entry_t *ui_menu_entry_last(ui_menu_t *menu)
+{
+	link_t *link;
+
+	link = list_last(&menu->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_menu_entry_t, lentries);
+}
+
 /** Get next menu entry in menu.
  *
@@ -193,4 +246,20 @@
 }
 
+/** Get previous menu entry in menu.
+ *
+ * @param cur Current menu entry
+ * @return Next menu entry or @c NULL if @a cur is the last one
+ */
+ui_menu_entry_t *ui_menu_entry_prev(ui_menu_entry_t *cur)
+{
+	link_t *link;
+
+	link = list_prev(&cur->lentries, &cur->menu->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_menu_entry_t, lentries);
+}
+
 /** Get width of menu entry.
  *
@@ -207,11 +276,11 @@
 	 * This needs to work even if the menu is not open, so we cannot
 	 * use the menu's resource, which is only created after the menu
-	 * is open (and its window is created). Use the menu bar's
+	 * is open (and its window is created). Use the parent window's
 	 * resource instead.
 	 */
-	res = ui_window_get_res(mentry->menu->mbar->window);
-
-	*caption_w = gfx_text_width(res->font, mentry->caption);
-	*shortcut_w = gfx_text_width(res->font, mentry->shortcut);
+	res = ui_window_get_res(mentry->menu->parent);
+
+	*caption_w = ui_text_width(res->font, mentry->caption);
+	*shortcut_w = ui_text_width(res->font, mentry->shortcut);
 }
 
@@ -233,8 +302,8 @@
 	 * This needs to work even if the menu is not open, so we cannot
 	 * use the menu's resource, which is only created after the menu
-	 * is open (and its window is created). Use the menu bar's
+	 * is open (and its window is created). Use the parent window's
 	 * resource instead.
 	 */
-	res = ui_window_get_res(menu->mbar->window);
+	res = ui_window_get_res(menu->parent);
 
 	if (res->textmode)
@@ -272,8 +341,8 @@
 	 * This needs to work even if the menu is not open, so we cannot
 	 * use the menu's resource, which is only created after the menu
-	 * is open (and its window is created). Use the menu bar's
+	 * is open (and its window is created). Use the parent window's
 	 * resource instead.
 	 */
-	res = ui_window_get_res(mentry->menu->mbar->window);
+	res = ui_window_get_res(mentry->menu->parent);
 
 	if (res->textmode) {
@@ -298,4 +367,15 @@
 }
 
+/** Get menu entry accelerator character.
+ *
+ * @param mentry Menu entry
+ * @return Accelerator character (lowercase) or the null character if
+ *         the menu entry has no accelerator.
+ */
+char32_t ui_menu_entry_get_accel(ui_menu_entry_t *mentry)
+{
+	return ui_accel_get(mentry->caption);
+}
+
 /** Paint menu entry.
  *
@@ -307,5 +387,5 @@
 {
 	ui_resource_t *res;
-	gfx_text_fmt_t fmt;
+	ui_text_fmt_t fmt;
 	gfx_color_t *bg_color;
 	ui_menu_entry_geom_t geom;
@@ -317,5 +397,6 @@
 	ui_menu_entry_get_geom(mentry, pos, &geom);
 
-	gfx_text_fmt_init(&fmt);
+	ui_text_fmt_init(&fmt);
+	fmt.font = res->font;
 	fmt.halign = gfx_halign_left;
 	fmt.valign = gfx_valign_top;
@@ -324,7 +405,13 @@
 	    mentry == mentry->menu->selected) {
 		fmt.color = res->wnd_sel_text_color;
+		fmt.hgl_color = res->wnd_sel_text_hgl_color;
 		bg_color = res->wnd_sel_text_bg_color;
+	} else if (mentry->disabled) {
+		fmt.color = res->wnd_dis_text_color;
+		fmt.hgl_color = res->wnd_dis_text_color;
+		bg_color = res->wnd_face_color;
 	} else {
 		fmt.color = res->wnd_text_color;
+		fmt.hgl_color = res->wnd_text_hgl_color;
 		bg_color = res->wnd_face_color;
 	}
@@ -338,5 +425,5 @@
 		goto error;
 
-	rc = gfx_puttext(res->font, &geom.caption_pos, &fmt, mentry->caption);
+	rc = ui_paint_text(&geom.caption_pos, &fmt, mentry->caption);
 	if (rc != EOK)
 		goto error;
@@ -344,5 +431,5 @@
 	fmt.halign = gfx_halign_right;
 
-	rc = gfx_puttext(res->font, &geom.shortcut_pos, &fmt, mentry->shortcut);
+	rc = ui_paint_text(&geom.shortcut_pos, &fmt, mentry->shortcut);
 	if (rc != EOK)
 		goto error;
@@ -378,4 +465,13 @@
 }
 
+/** Determine if entry is selectable.
+ *
+ * @return @c true iff entry is selectable
+ */
+bool ui_menu_entry_selectable(ui_menu_entry_t *mentry)
+{
+	return !mentry->separator;
+}
+
 /** Handle button press in menu entry.
  *
@@ -388,5 +484,5 @@
 		return;
 
-	if (mentry->separator)
+	if (mentry->separator || mentry->disabled)
 		return;
 
@@ -407,11 +503,19 @@
 	mentry->held = false;
 
-	if (mentry->inside) {
-		/* Close menu */
-		ui_menu_bar_select(mentry->menu->mbar, NULL, NULL);
-
-		/* Call back */
-		ui_menu_entry_cb(mentry);
-	}
+	if (mentry->inside)
+		ui_menu_entry_activate(mentry);
+}
+
+/** Activate menu entry.
+ *
+ * @param mentry Menu entry
+ */
+void ui_menu_entry_activate(ui_menu_entry_t *mentry)
+{
+	/* Close menu */
+	ui_menu_close_req(mentry->menu);
+
+	/* Call back */
+	ui_menu_entry_cb(mentry);
 }
 
@@ -497,4 +601,6 @@
 		}
 		break;
+	case POS_DCLICK:
+		break;
 	}
 
Index: uspace/lib/ui/src/paint.c
===================================================================
--- uspace/lib/ui/src/paint.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/paint.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,4 +39,5 @@
 #include <gfx/render.h>
 #include <gfx/text.h>
+#include <ui/accel.h>
 #include <ui/paint.h>
 #include <stdlib.h>
@@ -359,14 +360,289 @@
 }
 
-/** Paint a text box.
+/** Paint upward pointing triangle.
+ *
+ * @param gc Graphic context
+ * @param pos Center position
+ * @param n Length of triangle side
+ * @return EOK on success or an error code
+ */
+errno_t ui_paint_up_triangle(gfx_context_t *gc, gfx_coord2_t *pos,
+    gfx_coord_t n)
+{
+	gfx_coord_t i;
+	errno_t rc;
+	gfx_rect_t rect;
+
+	for (i = 0; i < n; i++) {
+		rect.p0.x = pos->x - i;
+		rect.p0.y = pos->y - n / 2 + i;
+		rect.p1.x = pos->x + i + 1;
+		rect.p1.y = pos->y - n / 2 + i + 1;
+		rc = gfx_fill_rect(gc, &rect);
+		if (rc != EOK)
+			return rc;
+	}
+
+	return EOK;
+}
+
+/** Paint downward pointing triangle.
+ *
+ * @param gc Graphic context
+ * @param pos Center position
+ * @param n Length of triangle side
+ * @return EOK on success or an error code
+ */
+errno_t ui_paint_down_triangle(gfx_context_t *gc, gfx_coord2_t *pos,
+    gfx_coord_t n)
+{
+	gfx_coord_t i;
+	errno_t rc;
+	gfx_rect_t rect;
+
+	for (i = 0; i < n; i++) {
+		rect.p0.x = pos->x - i;
+		rect.p0.y = pos->y + n / 2 - i;
+		rect.p1.x = pos->x + i + 1;
+		rect.p1.y = pos->y + n / 2 - i + 1;
+		rc = gfx_fill_rect(gc, &rect);
+		if (rc != EOK)
+			return rc;
+	}
+
+	return EOK;
+}
+
+/** Paint left pointing triangle.
+ *
+ * @param gc Graphic context
+ * @param pos Center position
+ * @param n Length of triangle side
+ * @return EOK on success or an error code
+ */
+errno_t ui_paint_left_triangle(gfx_context_t *gc, gfx_coord2_t *pos,
+    gfx_coord_t n)
+{
+	gfx_coord_t i;
+	errno_t rc;
+	gfx_rect_t rect;
+
+	for (i = 0; i < n; i++) {
+		rect.p0.x = pos->x - n / 2 + i;
+		rect.p0.y = pos->y - i;
+		rect.p1.x = pos->x - n / 2 + i + 1;
+		rect.p1.y = pos->y + i + 1;
+		rc = gfx_fill_rect(gc, &rect);
+		if (rc != EOK)
+			return rc;
+	}
+
+	return EOK;
+}
+
+/** Paint right pointing triangle.
+ *
+ * @param gc Graphic context
+ * @param pos Center position
+ * @param n Length of triangle side
+ * @return EOK on success or an error code
+ */
+errno_t ui_paint_right_triangle(gfx_context_t *gc, gfx_coord2_t *pos,
+    gfx_coord_t n)
+{
+	gfx_coord_t i;
+	errno_t rc;
+	gfx_rect_t rect;
+
+	for (i = 0; i < n; i++) {
+		rect.p0.x = pos->x + n / 2 - i;
+		rect.p0.y = pos->y - i;
+		rect.p1.x = pos->x + n / 2 - i + 1;
+		rect.p1.y = pos->y + i + 1;
+		rc = gfx_fill_rect(gc, &rect);
+		if (rc != EOK)
+			return rc;
+	}
+
+	return EOK;
+}
+
+/** Paint diagonal cross (X).
+ *
+ * @param gc Graphic context
+ * @param pos Center position
+ * @param n Length of each leg
+ * @param w Pen width
+ * @param h Pen height
+ * @return EOK on success or an error code
+ */
+errno_t ui_paint_cross(gfx_context_t *gc, gfx_coord2_t *pos,
+    gfx_coord_t n, gfx_coord_t w, gfx_coord_t h)
+{
+	gfx_coord_t i;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rect.p0.x = pos->x;
+	rect.p0.y = pos->y;
+	rect.p1.x = pos->x + w;
+	rect.p1.y = pos->y + h;
+	rc = gfx_fill_rect(gc, &rect);
+	if (rc != EOK)
+		return rc;
+
+	for (i = 1; i < n; i++) {
+		rect.p0.x = pos->x - i;
+		rect.p0.y = pos->y - i;
+		rect.p1.x = pos->x - i + w;
+		rect.p1.y = pos->y - i + h;
+		rc = gfx_fill_rect(gc, &rect);
+		if (rc != EOK)
+			return rc;
+
+		rect.p0.x = pos->x - i;
+		rect.p0.y = pos->y + i;
+		rect.p1.x = pos->x - i + w;
+		rect.p1.y = pos->y + i + h;
+		rc = gfx_fill_rect(gc, &rect);
+		if (rc != EOK)
+			return rc;
+
+		rect.p0.x = pos->x + i;
+		rect.p0.y = pos->y - i;
+		rect.p1.x = pos->x + i + w;
+		rect.p1.y = pos->y - i + h;
+		rc = gfx_fill_rect(gc, &rect);
+		if (rc != EOK)
+			return rc;
+
+		rect.p0.x = pos->x + i;
+		rect.p0.y = pos->y + i;
+		rect.p1.x = pos->x + i + w;
+		rect.p1.y = pos->y + i + h;
+		rc = gfx_fill_rect(gc, &rect);
+		if (rc != EOK)
+			return rc;
+	}
+
+	return EOK;
+}
+
+/** Paint minimize icon.
+ *
+ * @param resource UI resource
+ * @param pos Center position
+ * @param w Icon width
+ * @param h Icon height
+ * @return EOK on success or an error code
+ */
+errno_t ui_paint_minicon(ui_resource_t *resource, gfx_coord2_t *pos,
+    gfx_coord_t w, gfx_coord_t h)
+{
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = gfx_set_color(resource->gc, resource->btn_text_color);
+	if (rc != EOK)
+		return rc;
+
+	rect.p0.x = pos->x - w / 2;
+	rect.p0.y = pos->y + h / 2 - 2;
+	rect.p1.x = rect.p0.x + w;
+	rect.p1.y = rect.p0.y + 2;
+	rc = gfx_fill_rect(resource->gc, &rect);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Paint maximize icon.
+ *
+ * @param resource UI resource
+ * @param pos Center position
+ * @param w Icon width
+ * @param h Icon height
+ * @return EOK on success or an error code
+ */
+errno_t ui_paint_maxicon(ui_resource_t *resource, gfx_coord2_t *pos,
+    gfx_coord_t w, gfx_coord_t h)
+{
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = gfx_set_color(resource->gc, resource->btn_text_color);
+	if (rc != EOK)
+		return rc;
+
+	rect.p0.x = pos->x - w / 2;
+	rect.p0.y = pos->y - h / 2;
+	rect.p1.x = rect.p0.x + w;
+	rect.p1.y = rect.p0.y + h;
+	rc = gfx_fill_rect(resource->gc, &rect);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_set_color(resource->gc, resource->btn_face_color);
+	if (rc != EOK)
+		return rc;
+
+	rect.p0.x += 1;
+	rect.p0.y += 2;
+	rect.p1.x -= 1;
+	rect.p1.y -= 1;
+	rc = gfx_fill_rect(resource->gc, &rect);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Paint unmaximize icon.
+ *
+ * Unmaximize icon consists of two overlapping window icons.
+ *
+ * @param resource UI resource
+ * @param pos Center position
+ * @param w Window icon width
+ * @param h Window icon height
+ * @param dw Horizontal distance between window icon centers
+ * @param dh Vertical distance between window icon centers
+ * @return EOK on success or an error code
+ */
+errno_t ui_paint_unmaxicon(ui_resource_t *resource, gfx_coord2_t *pos,
+    gfx_coord_t w, gfx_coord_t h, gfx_coord_t dw, gfx_coord_t dh)
+{
+	gfx_coord2_t p;
+	errno_t rc;
+
+	p.x = pos->x + dw / 2;
+	p.y = pos->y - dh / 2;
+	rc = ui_paint_maxicon(resource, &p, w, h);
+	if (rc != EOK)
+		return rc;
+
+	p.x = pos->x - dw / 2;
+	p.y = pos->y + dh / 2;
+	rc = ui_paint_maxicon(resource, &p, w, h);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Paint a custom text box.
+ *
+ * Paint a text box using user-provided box chars.
  *
  * @param resource UI resource
  * @param rect Rectangle inside which to paint the box
  * @param style Box style
+ * @param boxc Box characters
  * @param color Color
  * @return EOK on success or an error code
  */
-errno_t ui_paint_text_box(ui_resource_t *resource, gfx_rect_t *rect,
-    ui_box_style_t style, gfx_color_t *color)
+errno_t ui_paint_text_box_custom(ui_resource_t *resource, gfx_rect_t *rect,
+    ui_box_chars_t *boxc, gfx_color_t *color)
 {
 	errno_t rc;
@@ -380,5 +656,4 @@
 	gfx_coord_t y;
 	char *str = NULL;
-	ui_box_chars_t *boxc = NULL;
 
 	gfx_rect_points_sort(rect, &srect);
@@ -388,4 +663,93 @@
 	if (dim.x < 2 || dim.y < 2)
 		return EOK;
+
+	gfx_text_fmt_init(&fmt);
+	fmt.font = resource->font;
+	fmt.color = color;
+
+	bufsz = str_size(boxc->c[0][0]) +
+	    str_size(boxc->c[0][1]) * (dim.x - 2) +
+	    str_size(boxc->c[0][2]) + 1;
+
+	str = malloc(bufsz);
+	if (str == NULL)
+		return ENOMEM;
+
+	/* Top edge and corners */
+
+	str_cpy(str, bufsz, boxc->c[0][0]);
+	off = str_size(boxc->c[0][0]);
+
+	for (i = 1; i < dim.x - 1; i++) {
+		str_cpy(str + off, bufsz - off, boxc->c[0][1]);
+		off += str_size(boxc->c[0][1]);
+	}
+
+	str_cpy(str + off, bufsz - off, boxc->c[0][2]);
+	off += str_size(boxc->c[0][2]);
+	str[off] = '\0';
+
+	pos = rect->p0;
+	rc = gfx_puttext(&pos, &fmt, str);
+	if (rc != EOK)
+		goto error;
+
+	/* Vertical edges */
+	for (y = rect->p0.y + 1; y < rect->p1.y - 1; y++) {
+		pos.y = y;
+
+		pos.x = rect->p0.x;
+		rc = gfx_puttext(&pos, &fmt, boxc->c[1][0]);
+		if (rc != EOK)
+			goto error;
+
+		pos.x = rect->p1.x - 1;
+		rc = gfx_puttext(&pos, &fmt, boxc->c[1][2]);
+		if (rc != EOK)
+			goto error;
+	}
+
+	/* Bottom edge and corners */
+
+	str_cpy(str, bufsz, boxc->c[2][0]);
+	off = str_size(boxc->c[2][0]);
+
+	for (i = 1; i < dim.x - 1; i++) {
+		str_cpy(str + off, bufsz - off, boxc->c[2][1]);
+		off += str_size(boxc->c[2][1]);
+	}
+
+	str_cpy(str + off, bufsz - off, boxc->c[2][2]);
+	off += str_size(boxc->c[2][2]);
+	str[off] = '\0';
+
+	pos.x = rect->p0.x;
+	pos.y = rect->p1.y - 1;
+	rc = gfx_puttext(&pos, &fmt, str);
+	if (rc != EOK)
+		goto error;
+
+	free(str);
+	return EOK;
+error:
+	if (str != NULL)
+		free(str);
+	return rc;
+}
+
+/** Paint a text box.
+ *
+ * Paint a text box with the specified style.
+ *
+ * @param resource UI resource
+ * @param rect Rectangle inside which to paint the box
+ * @param style Box style
+ * @param color Color
+ * @return EOK on success or an error code
+ */
+errno_t ui_paint_text_box(ui_resource_t *resource, gfx_rect_t *rect,
+    ui_box_style_t style, gfx_color_t *color)
+{
+	ui_box_chars_t *boxc = NULL;
 
 	switch (style) {
@@ -401,77 +765,5 @@
 		return EINVAL;
 
-	gfx_text_fmt_init(&fmt);
-	fmt.color = color;
-
-	bufsz = str_size(boxc->c[0][0]) +
-	    str_size(boxc->c[0][1]) * (dim.x - 2) +
-	    str_size(boxc->c[0][2]) + 1;
-
-	str = malloc(bufsz);
-	if (str == NULL)
-		return ENOMEM;
-
-	/* Top edge and corners */
-
-	str_cpy(str, bufsz, boxc->c[0][0]);
-	off = str_size(boxc->c[0][0]);
-
-	for (i = 1; i < dim.x - 1; i++) {
-		str_cpy(str + off, bufsz - off, boxc->c[0][1]);
-		off += str_size(boxc->c[0][1]);
-	}
-
-	str_cpy(str + off, bufsz - off, boxc->c[0][2]);
-	off += str_size(boxc->c[0][2]);
-	str[off] = '\0';
-
-	pos = rect->p0;
-	rc = gfx_puttext(resource->font, &pos, &fmt, str);
-	if (rc != EOK)
-		goto error;
-
-	/* Vertical edges */
-	for (y = rect->p0.y + 1; y < rect->p1.y - 1; y++) {
-		pos.y = y;
-
-		pos.x = rect->p0.x;
-		rc = gfx_puttext(resource->font, &pos, &fmt,
-		    boxc->c[1][0]);
-		if (rc != EOK)
-			goto error;
-
-		pos.x = rect->p1.x - 1;
-		rc = gfx_puttext(resource->font, &pos, &fmt,
-		    boxc->c[1][2]);
-		if (rc != EOK)
-			goto error;
-	}
-
-	/* Bottom edge and corners */
-
-	str_cpy(str, bufsz, boxc->c[2][0]);
-	off = str_size(boxc->c[2][0]);
-
-	for (i = 1; i < dim.x - 1; i++) {
-		str_cpy(str + off, bufsz - off, boxc->c[2][1]);
-		off += str_size(boxc->c[2][1]);
-	}
-
-	str_cpy(str + off, bufsz - off, boxc->c[2][2]);
-	off += str_size(boxc->c[2][2]);
-	str[off] = '\0';
-
-	pos.x = rect->p0.x;
-	pos.y = rect->p1.y - 1;
-	rc = gfx_puttext(resource->font, &pos, &fmt, str);
-	if (rc != EOK)
-		goto error;
-
-	free(str);
-	return EOK;
-error:
-	if (str != NULL)
-		free(str);
-	return rc;
+	return ui_paint_text_box_custom(resource, rect, boxc, color);
 }
 
@@ -519,4 +811,5 @@
 
 	gfx_text_fmt_init(&fmt);
+	fmt.font = resource->font;
 	fmt.color = color;
 
@@ -542,5 +835,5 @@
 
 	pos = rect->p0;
-	rc = gfx_puttext(resource->font, &pos, &fmt, str);
+	rc = gfx_puttext(&pos, &fmt, str);
 	if (rc != EOK)
 		goto error;
@@ -554,4 +847,157 @@
 }
 
+/** Fill rectangle with text character.
+ *
+ * @param resource UI resource
+ * @param rect Rectangle
+ * @param color Color
+ * @param gchar Character to fill with
+ * @return EOK on success or an error code
+ */
+errno_t ui_paint_text_rect(ui_resource_t *resource, gfx_rect_t *rect,
+    gfx_color_t *color, const char *gchar)
+{
+	gfx_coord2_t pos;
+	gfx_text_fmt_t fmt;
+	gfx_rect_t srect;
+	gfx_coord_t w, i;
+	char *buf;
+	size_t gcharsz;
+	errno_t rc;
+
+	gfx_rect_points_sort(rect, &srect);
+
+	gfx_text_fmt_init(&fmt);
+	fmt.font = resource->font;
+	fmt.color = color;
+	fmt.halign = gfx_halign_left;
+	fmt.valign = gfx_valign_top;
+
+	w = srect.p1.x - srect.p0.x;
+	if (w == 0)
+		return EOK;
+
+	gcharsz = str_size(gchar);
+
+	buf = malloc(w * gcharsz + 1);
+	if (buf == NULL)
+		return ENOMEM;
+
+	for (i = 0; i < w; i++)
+		str_cpy(buf + i * gcharsz, (w - i) * gcharsz + 1, gchar);
+	buf[w * gcharsz] = '\0';
+
+	pos.x = srect.p0.x;
+	for (pos.y = srect.p0.y; pos.y < srect.p1.y; pos.y++) {
+		rc = gfx_puttext(&pos, &fmt, buf);
+		if (rc != EOK)
+			goto error;
+	}
+
+	free(buf);
+	return EOK;
+error:
+	free(buf);
+	return rc;
+}
+
+/** Initialize UI text formatting structure.
+ *
+ * UI text formatting structure must always be initialized using this function
+ * first.
+ *
+ * @param fmt UI text formatting structure
+ */
+void ui_text_fmt_init(ui_text_fmt_t *fmt)
+{
+	memset(fmt, 0, sizeof(ui_text_fmt_t));
+}
+
+/** Compute UI text width.
+ *
+ * @param font Font
+ * @param str String
+ * @return Text width
+ */
+gfx_coord_t ui_text_width(gfx_font_t *font, const char *str)
+{
+	gfx_coord_t w;
+	gfx_coord_t tw;
+	const char *sp;
+
+	/* Text including tildes */
+	w = gfx_text_width(font, str);
+	tw = gfx_text_width(font, "~");
+
+	/* Subtract width of singular tildes */
+	sp = str;
+	while (*sp != '\0') {
+		if (*sp == '~' && sp[1] != '~')
+			w -= tw;
+		++sp;
+	}
+
+	return w;
+}
+
+/** Paint text (with highlighting markup).
+ *
+ * Paint some text with highlighted sections enclosed with tilde characters
+ * ('~'). A literal tilde can be printed with "~~". Text can be highlighted
+ * with an underline or different color, based on display capabilities.
+ *
+ * @param pos Anchor position
+ * @param fmt Text formatting
+ * @param str String containing '~' markup
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ui_paint_text(gfx_coord2_t *pos, ui_text_fmt_t *fmt, const char *str)
+{
+	gfx_coord2_t tpos;
+	gfx_text_fmt_t tfmt;
+	char *buf;
+	char *endptr;
+	const char *sp;
+	errno_t rc;
+
+	/* Break down string into list of (non)highlighted parts */
+	rc = ui_accel_process(str, &buf, &endptr);
+	if (rc != EOK)
+		return rc;
+
+	gfx_text_fmt_init(&tfmt);
+	tfmt.font = fmt->font;
+	tfmt.color = fmt->color;
+	tfmt.halign = fmt->halign;
+	tfmt.width = fmt->width;
+	tfmt.valign = fmt->valign;
+	tfmt.underline = false;
+
+	tpos = *pos;
+	sp = buf;
+	while (sp != endptr) {
+		/* Print the current string */
+		rc = gfx_puttext(&tpos, &tfmt, sp);
+		if (rc != EOK)
+			goto error;
+
+		gfx_text_cont(&tpos, &tfmt, sp, &tpos, &tfmt);
+		tfmt.underline = !tfmt.underline;
+		tfmt.color = tfmt.underline ? fmt->hgl_color : fmt->color;
+
+		/* Skip to the next string */
+		while (*sp != '\0')
+			++sp;
+		++sp;
+	}
+
+	free(buf);
+	return EOK;
+error:
+	free(buf);
+	return rc;
+}
+
 /** @}
  */
Index: uspace/lib/ui/src/pbutton.c
===================================================================
--- uspace/lib/ui/src/pbutton.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/pbutton.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -32,4 +32,7 @@
 /**
  * @file Push button
+ *
+ * Push button either uses text as decoration, or it can use a caller-provided
+ * function to paint the decoration.
  */
 
@@ -51,5 +54,7 @@
 enum {
 	ui_pb_press_dx = 1,
-	ui_pb_press_dy = 1
+	ui_pb_press_dy = 1,
+	ui_pb_pad_x = 2,
+	ui_pb_pad_x_text = 1
 };
 
@@ -111,4 +116,5 @@
 
 	ui_control_delete(pbutton->control);
+	free(pbutton->caption);
 	free(pbutton);
 }
@@ -136,4 +142,27 @@
 }
 
+/** Set push button decoration ops.
+ *
+ * @param pbutton Push button
+ * @param ops Push button decoration callbacks
+ * @param arg Decoration ops argument
+ */
+void ui_pbutton_set_decor_ops(ui_pbutton_t *pbutton,
+    ui_pbutton_decor_ops_t *ops, void *arg)
+{
+	pbutton->decor_ops = ops;
+	pbutton->decor_arg = arg;
+}
+
+/** Set push button flag.s
+ *
+ * @param pbutton Push button
+ * @param flags Flags
+ */
+void ui_pbutton_set_flags(ui_pbutton_t *pbutton, ui_pbutton_flags_t flags)
+{
+	pbutton->flags = flags;
+}
+
 /** Set button rectangle.
  *
@@ -157,4 +186,43 @@
 {
 	pbutton->isdefault = isdefault;
+}
+
+/** Get button light status.
+ *
+ * @param pbutton Button
+ * @return @c true iff light is on
+ */
+bool ui_pbutton_get_light(ui_pbutton_t *pbutton)
+{
+	return pbutton->light;
+}
+
+/** Turn button light on or off.
+ *
+ * @param pbutton Button
+ * @param light @c true iff button should be lit
+ */
+void ui_pbutton_set_light(ui_pbutton_t *pbutton, bool light)
+{
+	pbutton->light = light;
+}
+
+/** Set push button caption.
+ *
+ * @param pbutton Button
+ * @param caption New caption
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ui_pbutton_set_caption(ui_pbutton_t *pbutton, const char *caption)
+{
+	char *dcaption;
+
+	dcaption = str_dup(caption);
+	if (dcaption == NULL)
+		return ENOMEM;
+
+	free(pbutton->caption);
+	pbutton->caption = dcaption;
+	return EOK;
 }
 
@@ -277,5 +345,7 @@
 	gfx_text_fmt_t fmt;
 	gfx_rect_t rect;
+	gfx_rect_t irect;
 	gfx_coord_t thickness;
+	gfx_color_t *color;
 	bool depressed;
 	errno_t rc;
@@ -289,5 +359,8 @@
 	rect.p1.y = pbutton->rect.p1.y - thickness;
 
-	rc = gfx_set_color(pbutton->res->gc, pbutton->res->btn_face_color);
+	color = pbutton->light ? pbutton->res->btn_face_lit_color :
+	    pbutton->res->btn_face_color;
+
+	rc = gfx_set_color(pbutton->res->gc, color);
 	if (rc != EOK)
 		goto error;
@@ -306,12 +379,25 @@
 	}
 
-	gfx_text_fmt_init(&fmt);
-	fmt.color = pbutton->res->btn_text_color;
-	fmt.halign = gfx_halign_center;
-	fmt.valign = gfx_valign_center;
-
-	rc = gfx_puttext(pbutton->res->font, &pos, &fmt, pbutton->caption);
-	if (rc != EOK)
-		goto error;
+	if (pbutton->decor_ops != NULL && pbutton->decor_ops->paint != NULL) {
+		/* Custom decoration */
+		rc = pbutton->decor_ops->paint(pbutton, pbutton->decor_arg,
+		    &pos);
+		if (rc != EOK)
+			goto error;
+	} else {
+		/* Text decoration */
+		ui_paint_get_inset_frame_inside(pbutton->res, &rect, &irect);
+		gfx_text_fmt_init(&fmt);
+		fmt.font = pbutton->res->font;
+		fmt.color = pbutton->res->btn_text_color;
+		fmt.halign = gfx_halign_center;
+		fmt.valign = gfx_valign_center;
+		fmt.abbreviate = true;
+		fmt.width = irect.p1.x - irect.p0.x - 2 * ui_pb_pad_x;
+
+		rc = gfx_puttext(&pos, &fmt, pbutton->caption);
+		if (rc != EOK)
+			goto error;
+	}
 
 	rc = ui_pbutton_paint_frame(pbutton);
@@ -351,5 +437,8 @@
 	errno_t rc;
 
-	depressed = pbutton->held && pbutton->inside;
+	if ((pbutton->flags & ui_pbf_no_text_depress) == 0)
+		depressed = pbutton->held && pbutton->inside;
+	else
+		depressed = false;
 
 	rc = gfx_set_color(pbutton->res->gc, pbutton->res->wnd_face_color);
@@ -379,9 +468,14 @@
 
 	gfx_text_fmt_init(&fmt);
+	fmt.font = pbutton->res->font;
 	fmt.color = pbutton->res->btn_text_color;
 	fmt.halign = gfx_halign_center;
 	fmt.valign = gfx_valign_center;
-
-	rc = gfx_puttext(pbutton->res->font, &pos, &fmt, pbutton->caption);
+	fmt.abbreviate = true;
+	fmt.width = rect.p1.x - rect.p0.x - 2 * ui_pb_pad_x_text;
+	if (fmt.width < 1)
+		fmt.width = 1;
+
+	rc = gfx_puttext(&pos, &fmt, pbutton->caption);
 	if (rc != EOK)
 		goto error;
@@ -427,4 +521,5 @@
 	pbutton->held = true;
 	(void) ui_pbutton_paint(pbutton);
+	ui_pbutton_down(pbutton);
 }
 
@@ -439,4 +534,5 @@
 
 	pbutton->held = false;
+	ui_pbutton_up(pbutton);
 
 	if (pbutton->inside) {
@@ -474,5 +570,5 @@
 }
 
-/** Button was clicked.
+/** Send button clicked event.
  *
  * @param pbutton Push button
@@ -482,4 +578,24 @@
 	if (pbutton->cb != NULL && pbutton->cb->clicked != NULL)
 		pbutton->cb->clicked(pbutton, pbutton->arg);
+}
+
+/** Send button down event.
+ *
+ * @param pbutton Push button
+ */
+void ui_pbutton_down(ui_pbutton_t *pbutton)
+{
+	if (pbutton->cb != NULL && pbutton->cb->down != NULL)
+		pbutton->cb->down(pbutton, pbutton->arg);
+}
+
+/** Send button up event.
+ *
+ * @param pbutton Push button
+ */
+void ui_pbutton_up(ui_pbutton_t *pbutton)
+{
+	if (pbutton->cb != NULL && pbutton->cb->up != NULL)
+		pbutton->cb->up(pbutton, pbutton->arg);
 }
 
@@ -521,4 +637,6 @@
 		}
 		break;
+	case POS_DCLICK:
+		break;
 	}
 
Index: uspace/lib/ui/src/popup.c
===================================================================
--- uspace/lib/ui/src/popup.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/popup.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -36,5 +36,5 @@
 #include <errno.h>
 #include <gfx/context.h>
-//#include <io/kbd_event.h>
+#include <io/kbd_event.h>
 #include <io/pos_event.h>
 #include <mem.h>
@@ -47,8 +47,10 @@
 
 static void ui_popup_window_close(ui_window_t *, void *);
+static void ui_popup_window_kbd(ui_window_t *, void *, kbd_event_t *);
 static void ui_popup_window_pos(ui_window_t *, void *, pos_event_t *);
 
 static ui_window_cb_t ui_popup_window_cb = {
 	.close = ui_popup_window_close,
+	.kbd = ui_popup_window_kbd,
 	.pos = ui_popup_window_pos
 };
@@ -96,5 +98,6 @@
 	wparams.style &= ~ui_wds_decorated;
 	wparams.placement = ui_wnd_place_popup;
-	wparams.flags |= ui_wndf_popup;
+	wparams.flags |= ui_wndf_popup | ui_wndf_topmost;
+	wparams.idev_id = params->idev_id;
 
 	/* Compute position of parent rectangle relative to the screen */
@@ -187,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.
  *
@@ -200,4 +213,22 @@
 }
 
+/** Handle keyboard event in popup window.
+ *
+ * @param window Window
+ * @param arg Argument (ui_popup_t *)
+ * @param event Keyboard event
+ */
+static void ui_popup_window_kbd(ui_window_t *window, void *arg,
+    kbd_event_t *event)
+{
+	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);
+}
+
 /** Handle position event in popup window.
  *
@@ -211,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/promptdialog.c
===================================================================
--- uspace/lib/ui/src/promptdialog.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/promptdialog.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -327,5 +327,5 @@
 {
 	ui_prompt_dialog_t *dialog = (ui_prompt_dialog_t *) arg;
-	const char *fname;
+	const char *text;
 
 	if (event->type == KEY_PRESS &&
@@ -334,6 +334,6 @@
 			/* Confirm */
 			if (dialog->cb != NULL && dialog->cb->bok != NULL) {
-				fname = ui_entry_get_text(dialog->ename);
-				dialog->cb->bok(dialog, dialog->arg, fname);
+				text = ui_entry_get_text(dialog->ename);
+				dialog->cb->bok(dialog, dialog->arg, text);
 				return;
 			}
@@ -358,9 +358,9 @@
 {
 	ui_prompt_dialog_t *dialog = (ui_prompt_dialog_t *) arg;
-	const char *fname;
+	const char *text;
 
 	if (dialog->cb != NULL && dialog->cb->bok != NULL) {
-		fname = ui_entry_get_text(dialog->ename);
-		dialog->cb->bok(dialog, dialog->arg, fname);
+		text = ui_entry_get_text(dialog->ename);
+		dialog->cb->bok(dialog, dialog->arg, text);
 	}
 }
Index: uspace/lib/ui/src/rbutton.c
===================================================================
--- uspace/lib/ui/src/rbutton.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/rbutton.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -278,10 +278,10 @@
 
 	gfx_text_fmt_init(&fmt);
+	fmt.font = rbutton->group->res->font;
 	fmt.color = rbutton->group->res->wnd_text_color;
 	fmt.halign = gfx_halign_left;
 	fmt.valign = gfx_valign_center;
 
-	rc = gfx_puttext(rbutton->group->res->font, &pos, &fmt,
-	    rbutton->caption);
+	rc = gfx_puttext(&pos, &fmt, rbutton->caption);
 	if (rc != EOK)
 		goto error;
@@ -316,4 +316,5 @@
 
 	gfx_text_fmt_init(&fmt);
+	fmt.font = rbutton->group->res->font;
 	fmt.color = depressed ? rbutton->group->res->entry_act_bg_color :
 	    rbutton->group->res->wnd_text_color;
@@ -321,6 +322,6 @@
 	fmt.valign = gfx_valign_top;
 
-	rc = gfx_puttext(rbutton->group->res->font, &pos, &fmt,
-	    rbutton->group->selected == rbutton ? "(\u2022)" : "( )");
+	rc = gfx_puttext(&pos, &fmt, rbutton->group->selected == rbutton ?
+	    "(\u2022)" : "( )");
 	if (rc != EOK)
 		goto error;
@@ -332,6 +333,5 @@
 	fmt.color = rbutton->group->res->wnd_text_color;
 
-	rc = gfx_puttext(rbutton->group->res->font, &pos, &fmt,
-	    rbutton->caption);
+	rc = gfx_puttext(&pos, &fmt, rbutton->caption);
 	if (rc != EOK)
 		goto error;
@@ -490,4 +490,6 @@
 		}
 		break;
+	case POS_DCLICK:
+		break;
 	}
 
Index: uspace/lib/ui/src/resource.c
===================================================================
--- uspace/lib/ui/src/resource.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/resource.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -62,4 +62,5 @@
 	gfx_color_t *btn_frame_color = NULL;
 	gfx_color_t *btn_face_color = NULL;
+	gfx_color_t *btn_face_lit_color = NULL;
 	gfx_color_t *btn_text_color = NULL;
 	gfx_color_t *btn_highlight_color = NULL;
@@ -67,5 +68,8 @@
 	gfx_color_t *wnd_face_color = NULL;
 	gfx_color_t *wnd_text_color = NULL;
+	gfx_color_t *wnd_dis_text_color = NULL;
+	gfx_color_t *wnd_text_hgl_color = NULL;
 	gfx_color_t *wnd_sel_text_color = NULL;
+	gfx_color_t *wnd_sel_text_hgl_color = NULL;
 	gfx_color_t *wnd_sel_text_bg_color = NULL;
 	gfx_color_t *wnd_frame_hi_color = NULL;
@@ -82,4 +86,6 @@
 	gfx_color_t *entry_sel_text_fg_color = NULL;
 	gfx_color_t *entry_sel_text_bg_color = NULL;
+	gfx_color_t *sbar_trough_color = NULL;
+	gfx_color_t *sbar_act_trough_color = NULL;
 	errno_t rc;
 
@@ -110,4 +116,8 @@
 		goto error;
 
+	rc = gfx_color_new_rgb_i16(0xe8e8, 0xe8e8, 0xe8e8, &btn_face_lit_color);
+	if (rc != EOK)
+		goto error;
+
 	rc = gfx_color_new_rgb_i16(0, 0, 0, &btn_text_color);
 	if (rc != EOK)
@@ -131,5 +141,18 @@
 		goto error;
 
+	rc = gfx_color_new_rgb_i16(0x9696, 0x9696, 0x9696, &wnd_dis_text_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_color_new_rgb_i16(0, 0, 0, &wnd_text_hgl_color);
+	if (rc != EOK)
+		goto error;
+
 	rc = gfx_color_new_rgb_i16(0xffff, 0xffff, 0xffff, &wnd_sel_text_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_color_new_rgb_i16(0xffff, 0xffff, 0xffff,
+	    &wnd_sel_text_hgl_color);
 	if (rc != EOK)
 		goto error;
@@ -194,4 +217,14 @@
 
 	rc = gfx_color_new_rgb_i16(0, 0, 0xffff, &entry_sel_text_bg_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_color_new_rgb_i16(0xe4e4, 0xe4e4, 0xe4e4,
+	    &sbar_trough_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_color_new_rgb_i16(0x5858, 0x5858, 0x5858,
+	    &sbar_act_trough_color);
 	if (rc != EOK)
 		goto error;
@@ -204,4 +237,5 @@
 	resource->btn_frame_color = btn_frame_color;
 	resource->btn_face_color = btn_face_color;
+	resource->btn_face_lit_color = btn_face_lit_color;
 	resource->btn_text_color = btn_text_color;
 	resource->btn_highlight_color = btn_highlight_color;
@@ -210,5 +244,8 @@
 	resource->wnd_face_color = wnd_face_color;
 	resource->wnd_text_color = wnd_text_color;
+	resource->wnd_dis_text_color = wnd_dis_text_color;
+	resource->wnd_text_hgl_color = wnd_text_hgl_color;
 	resource->wnd_sel_text_color = wnd_sel_text_color;
+	resource->wnd_sel_text_hgl_color = wnd_sel_text_hgl_color;
 	resource->wnd_sel_text_bg_color = wnd_sel_text_bg_color;
 	resource->wnd_frame_hi_color = wnd_frame_hi_color;
@@ -227,4 +264,7 @@
 	resource->entry_sel_text_fg_color = entry_sel_text_fg_color;
 	resource->entry_sel_text_bg_color = entry_sel_text_bg_color;
+
+	resource->sbar_trough_color = sbar_trough_color;
+	resource->sbar_act_trough_color = sbar_act_trough_color;
 
 	*rresource = resource;
@@ -235,4 +275,6 @@
 	if (btn_face_color != NULL)
 		gfx_color_delete(btn_face_color);
+	if (btn_face_lit_color != NULL)
+		gfx_color_delete(btn_face_lit_color);
 	if (btn_text_color != NULL)
 		gfx_color_delete(btn_text_color);
@@ -246,6 +288,12 @@
 	if (wnd_text_color != NULL)
 		gfx_color_delete(wnd_text_color);
+	if (wnd_dis_text_color != NULL)
+		gfx_color_delete(wnd_dis_text_color);
+	if (wnd_text_hgl_color != NULL)
+		gfx_color_delete(wnd_text_hgl_color);
 	if (wnd_sel_text_color != NULL)
 		gfx_color_delete(wnd_sel_text_color);
+	if (wnd_sel_text_hgl_color != NULL)
+		gfx_color_delete(wnd_sel_text_hgl_color);
 	if (wnd_sel_text_bg_color != NULL)
 		gfx_color_delete(wnd_sel_text_bg_color);
@@ -279,4 +327,9 @@
 		gfx_color_delete(entry_act_bg_color);
 
+	if (sbar_trough_color != NULL)
+		gfx_color_delete(sbar_trough_color);
+	if (sbar_act_trough_color != NULL)
+		gfx_color_delete(sbar_act_trough_color);
+
 	if (tface != NULL)
 		gfx_typeface_destroy(tface);
@@ -299,4 +352,5 @@
 	gfx_color_t *btn_frame_color = NULL;
 	gfx_color_t *btn_face_color = NULL;
+	gfx_color_t *btn_face_lit_color = NULL;
 	gfx_color_t *btn_text_color = NULL;
 	gfx_color_t *btn_highlight_color = NULL;
@@ -304,5 +358,8 @@
 	gfx_color_t *wnd_face_color = NULL;
 	gfx_color_t *wnd_text_color = NULL;
+	gfx_color_t *wnd_dis_text_color = NULL;
+	gfx_color_t *wnd_text_hgl_color = NULL;
 	gfx_color_t *wnd_sel_text_color = NULL;
+	gfx_color_t *wnd_sel_text_hgl_color = NULL;
 	gfx_color_t *wnd_sel_text_bg_color = NULL;
 	gfx_color_t *wnd_frame_hi_color = NULL;
@@ -319,4 +376,6 @@
 	gfx_color_t *entry_sel_text_bg_color = NULL;
 	gfx_color_t *entry_act_bg_color = NULL;
+	gfx_color_t *sbar_trough_color = NULL;
+	gfx_color_t *sbar_act_trough_color = NULL;
 	errno_t rc;
 
@@ -342,4 +401,8 @@
 		goto error;
 
+	rc = gfx_color_new_ega(0x30, &btn_face_lit_color);
+	if (rc != EOK)
+		goto error;
+
 	rc = gfx_color_new_ega(0x20, &btn_text_color);
 	if (rc != EOK)
@@ -362,8 +425,20 @@
 		goto error;
 
+	rc = gfx_color_new_ega(0x78, &wnd_dis_text_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_color_new_ega(0x74, &wnd_text_hgl_color);
+	if (rc != EOK)
+		goto error;
+
 	rc = gfx_color_new_ega(0x07, &wnd_sel_text_color);
 	if (rc != EOK)
 		goto error;
 
+	rc = gfx_color_new_ega(0x04, &wnd_sel_text_hgl_color);
+	if (rc != EOK)
+		goto error;
+
 	rc = gfx_color_new_ega(0x07, &wnd_sel_text_bg_color);
 	if (rc != EOK)
@@ -402,21 +477,29 @@
 		goto error;
 
-	rc = gfx_color_new_ega(0x1b, &entry_fg_color);
-	if (rc != EOK)
-		goto error;
-
-	rc = gfx_color_new_ega(0x1b, &entry_bg_color);
-	if (rc != EOK)
-		goto error;
-
-	rc = gfx_color_new_ega(0x20, &entry_sel_text_fg_color);
-	if (rc != EOK)
-		goto error;
-
-	rc = gfx_color_new_ega(0x20, &entry_sel_text_bg_color);
+	rc = gfx_color_new_ega(0x07, &entry_fg_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_color_new_ega(0x07, &entry_bg_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_color_new_ega(0x1e, &entry_sel_text_fg_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_color_new_ega(0x1e, &entry_sel_text_bg_color);
 	if (rc != EOK)
 		goto error;
 
 	rc = gfx_color_new_ega(0x37, &entry_act_bg_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_color_new_ega(0x07, &sbar_trough_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_color_new_ega(0x07, &sbar_act_trough_color);
 	if (rc != EOK)
 		goto error;
@@ -429,4 +512,5 @@
 	resource->btn_frame_color = btn_frame_color;
 	resource->btn_face_color = btn_face_color;
+	resource->btn_face_lit_color = btn_face_lit_color;
 	resource->btn_text_color = btn_text_color;
 	resource->btn_highlight_color = btn_highlight_color;
@@ -435,5 +519,8 @@
 	resource->wnd_face_color = wnd_face_color;
 	resource->wnd_text_color = wnd_text_color;
+	resource->wnd_dis_text_color = wnd_dis_text_color;
+	resource->wnd_text_hgl_color = wnd_text_hgl_color;
 	resource->wnd_sel_text_color = wnd_sel_text_color;
+	resource->wnd_sel_text_hgl_color = wnd_sel_text_hgl_color;
 	resource->wnd_sel_text_bg_color = wnd_sel_text_bg_color;
 	resource->wnd_frame_hi_color = wnd_frame_hi_color;
@@ -452,4 +539,7 @@
 	resource->entry_sel_text_fg_color = entry_sel_text_fg_color;
 	resource->entry_sel_text_bg_color = entry_sel_text_bg_color;
+
+	resource->sbar_trough_color = sbar_trough_color;
+	resource->sbar_act_trough_color = sbar_act_trough_color;
 
 	*rresource = resource;
@@ -460,4 +550,6 @@
 	if (btn_face_color != NULL)
 		gfx_color_delete(btn_face_color);
+	if (btn_face_lit_color != NULL)
+		gfx_color_delete(btn_face_lit_color);
 	if (btn_text_color != NULL)
 		gfx_color_delete(btn_text_color);
@@ -471,6 +563,12 @@
 	if (wnd_text_color != NULL)
 		gfx_color_delete(wnd_text_color);
+	if (wnd_dis_text_color != NULL)
+		gfx_color_delete(wnd_dis_text_color);
+	if (wnd_text_hgl_color != NULL)
+		gfx_color_delete(wnd_text_hgl_color);
 	if (wnd_sel_text_color != NULL)
 		gfx_color_delete(wnd_sel_text_color);
+	if (wnd_sel_text_hgl_color != NULL)
+		gfx_color_delete(wnd_sel_text_hgl_color);
 	if (wnd_sel_text_bg_color != NULL)
 		gfx_color_delete(wnd_sel_text_bg_color);
@@ -503,4 +601,8 @@
 	if (entry_sel_text_bg_color != NULL)
 		gfx_color_delete(entry_sel_text_bg_color);
+	if (sbar_trough_color != NULL)
+		gfx_color_delete(sbar_trough_color);
+	if (sbar_act_trough_color != NULL)
+		gfx_color_delete(sbar_act_trough_color);
 
 	if (tface != NULL)
@@ -537,4 +639,5 @@
 	gfx_color_delete(resource->btn_frame_color);
 	gfx_color_delete(resource->btn_face_color);
+	gfx_color_delete(resource->btn_face_lit_color);
 	gfx_color_delete(resource->btn_text_color);
 	gfx_color_delete(resource->btn_highlight_color);
@@ -543,4 +646,5 @@
 	gfx_color_delete(resource->wnd_face_color);
 	gfx_color_delete(resource->wnd_text_color);
+	gfx_color_delete(resource->wnd_dis_text_color);
 	gfx_color_delete(resource->wnd_sel_text_color);
 	gfx_color_delete(resource->wnd_sel_text_bg_color);
@@ -561,4 +665,7 @@
 	gfx_color_delete(resource->entry_sel_text_bg_color);
 
+	gfx_color_delete(resource->sbar_trough_color);
+	gfx_color_delete(resource->sbar_act_trough_color);
+
 	gfx_font_close(resource->font);
 	gfx_typeface_destroy(resource->tface);
@@ -604,4 +711,34 @@
 }
 
+/** Determine if resource is textmode.
+ *
+ * @param resource UI resource
+ * @return @c true iff resource is textmode
+ */
+bool ui_resource_is_textmode(ui_resource_t *resource)
+{
+	return resource->textmode;
+}
+
+/** Get the UI window face color.
+ *
+ * @param resource UI resource
+ * @return UI window face color
+ */
+gfx_color_t *ui_resource_get_wnd_face_color(ui_resource_t *resource)
+{
+	return resource->wnd_face_color;
+}
+
+/** Get the UI window text color.
+ *
+ * @param resource UI resource
+ * @return UI window text color
+ */
+gfx_color_t *ui_resource_get_wnd_text_color(ui_resource_t *resource)
+{
+	return resource->wnd_text_color;
+}
+
 /** @}
  */
Index: uspace/lib/ui/src/scrollbar.c
===================================================================
--- uspace/lib/ui/src/scrollbar.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/src/scrollbar.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,1147 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Scrollbar
+ *
+ * Anatomy of a horizontal scrollbar:
+ *
+ *        Upper             Lower
+ *       trough            trough
+ *  +---+------+--------+---------+---+
+ *  | < |      |   |||  |         | > |
+ *  +---+------+--------+---------+---+
+ *   Up           Thumb           Down
+ * button                        button
+ *
+ *     +-------- Trough --------+
+ *
+ * Scrollbar uses the same terminology whether it is running in horizontal
+ * or vertical mode, in horizontal mode up means left, down means right
+ * (i.e. lower and higher coordinates, respectively).
+ *
+ * The thumb can be dragged to a specific position, resulting in a move
+ * event. The up/down buttons generate up/down events. Pressing a mouse
+ * button on the upper/lower trough generates page up / page down events.
+ *
+ * Pressing and holding down mouse button on up / down button or upper /
+ * lower trough will auto-scroll (using clickmatic).
+ */
+
+#include <errno.h>
+#include <gfx/color.h>
+#include <gfx/context.h>
+#include <gfx/render.h>
+#include <gfx/text.h>
+#include <io/pos_event.h>
+#include <stdlib.h>
+#include <str.h>
+#include <ui/clickmatic.h>
+#include <ui/control.h>
+#include <ui/paint.h>
+#include <ui/pbutton.h>
+#include <ui/scrollbar.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include "../private/pbutton.h"
+#include "../private/resource.h"
+#include "../private/scrollbar.h"
+
+enum {
+	/** Scrollbar button width */
+	ui_scrollbar_btn_len = 21,
+	/** Scrollbar button width in text mode */
+	ui_scrollbar_btn_len_text = 1,
+	/** Scrollbar thumb frame thickness */
+	ui_scrollbar_thumb_frame_thickness = 1,
+	/** Scrollbar thumb bevel width */
+	ui_scrollbar_thumb_bevel_width = 2,
+	/** Scrollbar default thumb length */
+	ui_scrollbar_def_thumb_len = 21,
+	/** Scrollbar default thumb length in text mode */
+	ui_scrollbar_def_thumb_len_text = 1,
+	/** Scrollbar minimum thumb length */
+	ui_scrollbar_min_thumb_len = 10,
+	/** Scrollbar minimum thumb length in text mode */
+	ui_scrollbar_min_thumb_len_text = 1,
+};
+
+static void ui_scrollbar_up_btn_down(ui_pbutton_t *, void *);
+static void ui_scrollbar_up_btn_up(ui_pbutton_t *, void *);
+static errno_t ui_scrollbar_up_btn_decor_paint(ui_pbutton_t *, void *,
+    gfx_coord2_t *);
+static errno_t ui_scrollbar_down_btn_decor_paint(ui_pbutton_t *, void *,
+    gfx_coord2_t *);
+static void ui_scrollbar_down_btn_down(ui_pbutton_t *, void *);
+static void ui_scrollbar_down_btn_up(ui_pbutton_t *, void *);
+static void ui_scrollbar_ctl_destroy(void *);
+static errno_t ui_scrollbar_ctl_paint(void *);
+static ui_evclaim_t ui_scrollbar_ctl_pos_event(void *, pos_event_t *);
+
+static ui_pbutton_cb_t ui_scrollbar_up_btn_cb = {
+	.down = ui_scrollbar_up_btn_down,
+	.up = ui_scrollbar_up_btn_up
+};
+
+static ui_pbutton_decor_ops_t ui_scrollbar_up_btn_decor_ops = {
+	.paint = ui_scrollbar_up_btn_decor_paint
+};
+
+static ui_pbutton_cb_t ui_scrollbar_down_btn_cb = {
+	.down = ui_scrollbar_down_btn_down,
+	.up = ui_scrollbar_down_btn_up
+};
+
+static ui_pbutton_decor_ops_t ui_scrollbar_down_btn_decor_ops = {
+	.paint = ui_scrollbar_down_btn_decor_paint
+};
+
+/** Scrollbar control ops */
+static ui_control_ops_t ui_scrollbar_ops = {
+	.destroy = ui_scrollbar_ctl_destroy,
+	.paint = ui_scrollbar_ctl_paint,
+	.pos_event = ui_scrollbar_ctl_pos_event
+};
+
+static void ui_scrollbar_cm_up(ui_clickmatic_t *, void *);
+static void ui_scrollbar_cm_down(ui_clickmatic_t *, void *);
+static void ui_scrollbar_cm_page_up(ui_clickmatic_t *, void *);
+static void ui_scrollbar_cm_page_down(ui_clickmatic_t *, void *);
+
+/** Scrollbar clickmatic up callbacks */
+ui_clickmatic_cb_t ui_scrollbar_clickmatic_up_cb = {
+	.clicked = ui_scrollbar_cm_up
+};
+
+/** Scrollbar clickmatic down callbacks */
+ui_clickmatic_cb_t ui_scrollbar_clickmatic_down_cb = {
+	.clicked = ui_scrollbar_cm_down
+};
+
+/** Scrollbar clickmatic page up callbacks */
+ui_clickmatic_cb_t ui_scrollbar_clickmatic_page_up_cb = {
+	.clicked = ui_scrollbar_cm_page_up
+};
+
+/** Scrollbar clickmatic page down callbacks */
+ui_clickmatic_cb_t ui_scrollbar_clickmatic_page_down_cb = {
+	.clicked = ui_scrollbar_cm_page_down
+};
+
+/** Create new scrollbar.
+ *
+ * @param ui UI
+ * @param window Window containing scrollbar
+ * @param dir Scrollbar direction
+ * @param rscrollbar Place to store pointer to new scrollbar
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ui_scrollbar_create(ui_t *ui, ui_window_t *window,
+    ui_scrollbar_dir_t dir, ui_scrollbar_t **rscrollbar)
+{
+	ui_scrollbar_t *scrollbar;
+	ui_resource_t *resource;
+	const char *up_text;
+	const char *down_text;
+	errno_t rc;
+
+	resource = ui_window_get_res(window);
+
+	scrollbar = calloc(1, sizeof(ui_scrollbar_t));
+	if (scrollbar == NULL)
+		return ENOMEM;
+
+	rc = ui_control_new(&ui_scrollbar_ops, (void *) scrollbar,
+	    &scrollbar->control);
+	if (rc != EOK) {
+		free(scrollbar);
+		return rc;
+	}
+
+	if (dir == ui_sbd_horiz) {
+		up_text = resource->textmode ? "\u25c4" : "<";
+		down_text = resource->textmode ? "\u25ba" : ">";
+	} else {
+		up_text = resource->textmode ? "\u25b2" : "^";
+		down_text = resource->textmode ? "\u25bc" : "v";
+	}
+
+	rc = ui_pbutton_create(resource, up_text, &scrollbar->up_btn);
+	if (rc != EOK)
+		goto error;
+
+	ui_pbutton_set_cb(scrollbar->up_btn, &ui_scrollbar_up_btn_cb,
+	    scrollbar);
+
+	ui_pbutton_set_decor_ops(scrollbar->up_btn,
+	    &ui_scrollbar_up_btn_decor_ops, (void *) scrollbar);
+
+	ui_pbutton_set_flags(scrollbar->up_btn, ui_pbf_no_text_depress);
+
+	rc = ui_pbutton_create(resource, down_text, &scrollbar->down_btn);
+	if (rc != EOK)
+		goto error;
+
+	ui_pbutton_set_cb(scrollbar->down_btn, &ui_scrollbar_down_btn_cb,
+	    (void *) scrollbar);
+
+	ui_pbutton_set_decor_ops(scrollbar->down_btn,
+	    &ui_scrollbar_down_btn_decor_ops, (void *) scrollbar);
+
+	ui_pbutton_set_flags(scrollbar->down_btn, ui_pbf_no_text_depress);
+
+	scrollbar->thumb_len = resource->textmode ?
+	    ui_scrollbar_def_thumb_len_text :
+	    ui_scrollbar_def_thumb_len;
+
+	scrollbar->ui = ui;
+	scrollbar->window = window;
+	scrollbar->dir = dir;
+	*rscrollbar = scrollbar;
+	return EOK;
+error:
+	ui_scrollbar_destroy(scrollbar);
+	return rc;
+}
+
+/** Destroy scrollbar.
+ *
+ * @param scrollbar Scrollbar or @c NULL
+ */
+void ui_scrollbar_destroy(ui_scrollbar_t *scrollbar)
+{
+	if (scrollbar == NULL)
+		return;
+
+	ui_pbutton_destroy(scrollbar->up_btn);
+	ui_pbutton_destroy(scrollbar->down_btn);
+	ui_control_delete(scrollbar->control);
+	free(scrollbar);
+}
+
+/** Get base control from scrollbar.
+ *
+ * @param scrollbar Scrollbar
+ * @return Control
+ */
+ui_control_t *ui_scrollbar_ctl(ui_scrollbar_t *scrollbar)
+{
+	return scrollbar->control;
+}
+
+/** Set scrollbar callbacks.
+ *
+ * @param scrollbar Scrollbar
+ * @param cb Scrollbar callbacks
+ * @param arg Callback argument
+ */
+void ui_scrollbar_set_cb(ui_scrollbar_t *scrollbar, ui_scrollbar_cb_t *cb, void *arg)
+{
+	scrollbar->cb = cb;
+	scrollbar->arg = arg;
+}
+
+/** Set scrollbar rectangle.
+ *
+ * @param scrollbar Scrollbar
+ * @param rect New scrollbar rectangle
+ */
+void ui_scrollbar_set_rect(ui_scrollbar_t *scrollbar, gfx_rect_t *rect)
+{
+	ui_scrollbar_geom_t geom;
+
+	scrollbar->rect = *rect;
+
+	ui_scrollbar_get_geom(scrollbar, &geom);
+	ui_pbutton_set_rect(scrollbar->up_btn, &geom.up_btn_rect);
+	ui_pbutton_set_rect(scrollbar->down_btn, &geom.down_btn_rect);
+}
+
+/** Paint outer thumb frame.
+ *
+ * @param scrollbar Scrollbar
+ * @return EOK on success or an error code
+ */
+static errno_t ui_scrollbar_paint_thumb_frame(ui_resource_t *res, gfx_rect_t *rect,
+    gfx_coord_t thickness, gfx_rect_t *inside)
+{
+	gfx_rect_t drect;
+	errno_t rc;
+
+	rc = gfx_set_color(res->gc, res->btn_frame_color);
+	if (rc != EOK)
+		goto error;
+
+	drect.p0.x = rect->p0.x + 1;
+	drect.p0.y = rect->p0.y;
+	drect.p1.x = rect->p1.x - 1;
+	drect.p1.y = rect->p0.y + thickness;
+	rc = gfx_fill_rect(res->gc, &drect);
+	if (rc != EOK)
+		goto error;
+
+	drect.p0.x = rect->p0.x + 1;
+	drect.p0.y = rect->p1.y - thickness;
+	drect.p1.x = rect->p1.x - 1;
+	drect.p1.y = rect->p1.y;
+	rc = gfx_fill_rect(res->gc, &drect);
+	if (rc != EOK)
+		goto error;
+
+	drect.p0.x = rect->p0.x;
+	drect.p0.y = rect->p0.y + 1;
+	drect.p1.x = rect->p0.x + thickness;
+	drect.p1.y = rect->p1.y - 1;
+	rc = gfx_fill_rect(res->gc, &drect);
+	if (rc != EOK)
+		goto error;
+
+	drect.p0.x = rect->p1.x - thickness;
+	drect.p0.y = rect->p0.y + 1;
+	drect.p1.x = rect->p1.x;
+	drect.p1.y = rect->p1.y - 1;
+	rc = gfx_fill_rect(res->gc, &drect);
+	if (rc != EOK)
+		goto error;
+
+	if (inside != NULL) {
+		inside->p0.x = rect->p0.x + thickness;
+		inside->p0.y = rect->p0.y + thickness;
+		inside->p1.x = rect->p1.x - thickness;
+		inside->p1.y = rect->p1.y - thickness;
+	}
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Paint outset scrollbar bevel.
+ *
+ * @param scrollbar Scrollbar
+ * @return EOK on success or an error code
+ */
+static errno_t ui_scrollbar_paint_outset(ui_scrollbar_t *scrollbar,
+    gfx_rect_t *rect, gfx_rect_t *inside)
+{
+	ui_resource_t *resource = ui_window_get_res(scrollbar->window);
+
+	return ui_paint_bevel(resource->gc, rect,
+	    resource->btn_highlight_color,
+	    resource->btn_shadow_color, ui_scrollbar_thumb_bevel_width, inside);
+}
+
+/** Determine scrollbar trough length.
+ *
+ * Return the size of the space within which the thumb can move
+ * (without subtracting the length of the thumb).
+ *
+ * @param scrollbar Scrollbar
+ * @return Scrollbar trough length in pixels
+ */
+gfx_coord_t ui_scrollbar_trough_length(ui_scrollbar_t *scrollbar)
+{
+	ui_resource_t *resource;
+	gfx_coord2_t dims;
+	gfx_coord_t len;
+	gfx_coord_t sblen;
+
+	resource = ui_window_get_res(scrollbar->window);
+
+	gfx_rect_dims(&scrollbar->rect, &dims);
+	sblen = scrollbar->dir == ui_sbd_horiz ?
+	    dims.x : dims.y;
+
+	len = resource->textmode ? ui_scrollbar_btn_len_text :
+	    ui_scrollbar_btn_len;
+
+	return sblen - 2 * len;
+}
+
+/** Determine scrollbar move length.
+ *
+ * Return the maximum distance the thumb can move.
+ *
+ * @param scrollbar Scrollbar
+ * @return Scrollbar move length in pixels
+ */
+gfx_coord_t ui_scrollbar_move_length(ui_scrollbar_t *scrollbar)
+{
+	return ui_scrollbar_trough_length(scrollbar) -
+	    scrollbar->thumb_len;
+}
+
+/** Set scrollbar thumb length.
+ *
+ * @param scrollbar Scrollbar
+ * @param len Thumb length in pixels
+ */
+void ui_scrollbar_set_thumb_length(ui_scrollbar_t *scrollbar, gfx_coord_t len)
+{
+	ui_resource_t *resource;
+	gfx_coord_t min_len;
+	gfx_coord_t max_len;
+
+	resource = ui_window_get_res(scrollbar->window);
+
+	min_len = resource->textmode ?
+	    ui_scrollbar_min_thumb_len_text :
+	    ui_scrollbar_min_thumb_len;
+
+	max_len = ui_scrollbar_trough_length(scrollbar);
+	if (len < min_len)
+		len = min_len;
+	if (len > max_len)
+		len = max_len;
+
+	if (len != scrollbar->thumb_len) {
+		scrollbar->thumb_len = len;
+		(void) ui_scrollbar_paint(scrollbar);
+	}
+}
+
+/** Get scrollbar thumb position.
+ *
+ * @param scrollbar Scrollbar
+ * @return Scrollbar thumb position in pixels
+ */
+gfx_coord_t ui_scrollbar_get_pos(ui_scrollbar_t *scrollbar)
+{
+	return scrollbar->pos;
+}
+
+/** Set scrollbar thumb position.
+ *
+ * The position is clipped to the allowed range.
+ *
+ * @param scrollbar Scrollbar
+ * @parap pos       Scrollbar thumb position in pixels
+ */
+void ui_scrollbar_set_pos(ui_scrollbar_t *scrollbar, gfx_coord_t pos)
+{
+	gfx_coord_t length;
+
+	length = ui_scrollbar_move_length(scrollbar);
+	if (pos < 0)
+		pos = 0;
+	if (pos > length)
+		pos = length;
+
+	if (pos != scrollbar->pos) {
+		scrollbar->pos = pos;
+		(void) ui_scrollbar_paint(scrollbar);
+		ui_scrollbar_troughs_update(scrollbar,
+		    &scrollbar->last_curs_pos);
+	}
+}
+
+/** Paint scrollbar in graphics mode.
+ *
+ * @param scrollbar Scrollbar
+ * @return EOK on success or an error code
+ */
+errno_t ui_scrollbar_paint_gfx(ui_scrollbar_t *scrollbar)
+{
+	ui_resource_t *resource;
+	ui_scrollbar_geom_t geom;
+	gfx_rect_t brect;
+	gfx_rect_t irect;
+	errno_t rc;
+
+	resource = ui_window_get_res(scrollbar->window);
+
+	ui_scrollbar_get_geom(scrollbar, &geom);
+
+	/* Paint scrollbar frame */
+
+	rc = ui_paint_inset_frame(resource, &scrollbar->rect, NULL);
+	if (rc != EOK)
+		goto error;
+
+	/* Paint scrollbar upper trough */
+	rc = gfx_set_color(resource->gc,
+	    scrollbar->upper_trough_held && scrollbar->upper_trough_inside ?
+	    resource->sbar_act_trough_color :
+	    resource->sbar_trough_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_fill_rect(resource->gc, &geom.upper_trough_rect);
+	if (rc != EOK)
+		goto error;
+
+	/* Paint scrollbar lower trough */
+
+	rc = gfx_set_color(resource->gc,
+	    scrollbar->lower_trough_held && scrollbar->lower_trough_inside ?
+	    resource->sbar_act_trough_color :
+	    resource->sbar_trough_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_fill_rect(resource->gc, &geom.lower_trough_rect);
+	if (rc != EOK)
+		goto error;
+
+	/* Paint scrollbar thumb */
+	rc = ui_scrollbar_paint_thumb_frame(resource, &geom.thumb_rect,
+	    ui_scrollbar_thumb_frame_thickness, &brect);
+	if (rc != EOK)
+		goto error;
+
+	rc = ui_scrollbar_paint_outset(scrollbar, &brect, &irect);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_set_color(resource->gc, resource->btn_face_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_fill_rect(resource->gc, &irect);
+	if (rc != EOK)
+		goto error;
+
+	rc = ui_pbutton_paint(scrollbar->up_btn);
+	if (rc != EOK)
+		goto error;
+
+	rc = ui_pbutton_paint(scrollbar->down_btn);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_update(resource->gc);
+	if (rc != EOK)
+		goto error;
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Paint scrollbar in text mode.
+ *
+ * @param scrollbar Scrollbar
+ * @return EOK on success or an error code
+ */
+errno_t ui_scrollbar_paint_text(ui_scrollbar_t *scrollbar)
+{
+	ui_resource_t *resource;
+	ui_scrollbar_geom_t geom;
+	errno_t rc;
+
+	resource = ui_window_get_res(scrollbar->window);
+	ui_scrollbar_get_geom(scrollbar, &geom);
+
+	/* Paint scrollbar trough */
+
+	rc = ui_paint_text_rect(resource, &geom.trough_rect,
+	    resource->sbar_trough_color, "\u2592");
+	if (rc != EOK)
+		goto error;
+
+	/* Paint scrollbar thumb */
+
+	rc = ui_paint_text_rect(resource, &geom.thumb_rect,
+	    resource->sbar_trough_color, "\u25a0");
+	if (rc != EOK)
+		goto error;
+
+	/* Paint buttons */
+
+	rc = ui_pbutton_paint(scrollbar->up_btn);
+	if (rc != EOK)
+		goto error;
+
+	rc = ui_pbutton_paint(scrollbar->down_btn);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_update(resource->gc);
+	if (rc != EOK)
+		goto error;
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Paint scrollbar.
+ *
+ * @param scrollbar Scrollbar
+ * @return EOK on success or an error code
+ */
+errno_t ui_scrollbar_paint(ui_scrollbar_t *scrollbar)
+{
+	ui_resource_t *resource = ui_window_get_res(scrollbar->window);
+
+	if (resource->textmode) {
+		return ui_scrollbar_paint_text(scrollbar);
+	} else {
+		return ui_scrollbar_paint_gfx(scrollbar);
+	}
+}
+
+/** Get scrollbar geometry.
+ *
+ * @param scrollbar Scrollbar
+ * @param geom Structure to fill in with computed geometry
+ */
+void ui_scrollbar_get_geom(ui_scrollbar_t *scrollbar, ui_scrollbar_geom_t *geom)
+{
+	ui_resource_t *resource;
+	gfx_coord_t btn_len;
+	gfx_rect_t orect;
+	gfx_rect_t irect;
+
+	resource = ui_window_get_res(scrollbar->window);
+
+	if (resource->textmode) {
+		btn_len = ui_scrollbar_btn_len_text;
+	} else {
+		btn_len = ui_scrollbar_btn_len;
+	}
+
+	if (resource->textmode) {
+		irect = scrollbar->rect;
+		orect = scrollbar->rect;
+	} else {
+		ui_paint_get_inset_frame_inside(resource,
+		    &scrollbar->rect, &irect);
+		ui_paint_get_bevel_inside(resource->gc,
+		    &scrollbar->rect, 1, &orect);
+	}
+
+	if (scrollbar->dir == ui_sbd_horiz) {
+		/* Up button */
+		geom->up_btn_rect.p0.x = orect.p0.x;
+		geom->up_btn_rect.p0.y = orect.p0.y;
+		geom->up_btn_rect.p1.x = orect.p0.x + btn_len;
+		geom->up_btn_rect.p1.y = orect.p1.y;
+
+		/* Trough */
+		geom->trough_rect.p0.x = geom->up_btn_rect.p1.x;
+		geom->trough_rect.p0.y = irect.p0.y;
+		geom->trough_rect.p1.x = orect.p1.x - btn_len;
+		geom->trough_rect.p1.y = irect.p1.y;
+
+		/* Thumb */
+		geom->thumb_rect.p0.x = geom->up_btn_rect.p1.x +
+		    scrollbar->pos;
+		geom->thumb_rect.p0.y = orect.p0.y;
+		geom->thumb_rect.p1.x = geom->thumb_rect.p0.x +
+		    scrollbar->thumb_len;
+		geom->thumb_rect.p1.y = orect.p1.y;
+
+		/* Upper trough */
+		geom->upper_trough_rect.p0 = geom->trough_rect.p0;
+		geom->upper_trough_rect.p1.x = geom->thumb_rect.p0.x;
+		geom->upper_trough_rect.p1.y = geom->trough_rect.p1.y;
+
+		/* Lower trough */
+		geom->lower_trough_rect.p0.x = geom->thumb_rect.p1.x;
+		geom->lower_trough_rect.p0.y = geom->trough_rect.p0.y;
+		geom->lower_trough_rect.p1 = geom->trough_rect.p1;
+
+		/* Down button */
+		geom->down_btn_rect.p0.x = geom->trough_rect.p1.x;
+		geom->down_btn_rect.p0.y = orect.p0.y;
+		geom->down_btn_rect.p1.x = orect.p1.x;
+		geom->down_btn_rect.p1.y = orect.p1.y;
+	} else {
+		/* Up button */
+		geom->up_btn_rect.p0.x = orect.p0.x;
+		geom->up_btn_rect.p0.y = orect.p0.y;
+		geom->up_btn_rect.p1.x = orect.p1.x;
+		geom->up_btn_rect.p1.y = orect.p0.y + btn_len;
+
+		/* Trough */
+		geom->trough_rect.p0.x = irect.p0.x;
+		geom->trough_rect.p0.y = geom->up_btn_rect.p1.y;
+		geom->trough_rect.p1.x = irect.p1.x;
+		geom->trough_rect.p1.y = orect.p1.y - btn_len;
+
+		/* Thumb */
+		geom->thumb_rect.p0.x = orect.p0.x;
+		geom->thumb_rect.p0.y = geom->up_btn_rect.p1.y +
+		    scrollbar->pos;
+		geom->thumb_rect.p1.x = orect.p1.x;
+		geom->thumb_rect.p1.y = geom->thumb_rect.p0.y +
+		    scrollbar->thumb_len;
+
+		/* Upper trough */
+		geom->upper_trough_rect.p0 = geom->trough_rect.p0;
+		geom->upper_trough_rect.p1.x = geom->trough_rect.p1.x;
+		geom->upper_trough_rect.p1.y = geom->thumb_rect.p0.y;
+
+		/* Lower trough */
+		geom->lower_trough_rect.p0.x = geom->trough_rect.p0.x;
+		geom->lower_trough_rect.p0.y = geom->thumb_rect.p1.y;
+		geom->lower_trough_rect.p1 = geom->trough_rect.p1;
+
+		/* Down button */
+		geom->down_btn_rect.p0.x = orect.p0.x;
+		geom->down_btn_rect.p0.y = geom->trough_rect.p1.y;
+		geom->down_btn_rect.p1.x = orect.p1.x;
+		geom->down_btn_rect.p1.y = orect.p1.y;
+	}
+}
+
+/** Press down scrollbar thumb.
+ *
+ * @param scrollbar Scrollbar
+ * @param pos Pointer position
+ */
+void ui_scrollbar_thumb_press(ui_scrollbar_t *scrollbar, gfx_coord2_t *pos)
+{
+	if (scrollbar->thumb_held)
+		return;
+
+	scrollbar->thumb_held = true;
+	scrollbar->press_pos = *pos;
+	scrollbar->last_pos = scrollbar->pos;
+
+	(void) ui_scrollbar_paint(scrollbar);
+}
+
+/** Press down scrollbar upper trough.
+ *
+ * @param scrollbar Scrollbar
+ */
+void ui_scrollbar_upper_trough_press(ui_scrollbar_t *scrollbar)
+{
+	ui_clickmatic_t *clickmatic = ui_get_clickmatic(scrollbar->ui);
+
+	scrollbar->upper_trough_held = true;
+	scrollbar->upper_trough_inside = true;
+
+	ui_clickmatic_set_cb(clickmatic, &ui_scrollbar_clickmatic_page_up_cb,
+	    (void *) scrollbar);
+	ui_clickmatic_press(clickmatic);
+}
+
+/** Press down scrollbar lower trough.
+ *
+ * @param scrollbar Scrollbar
+ */
+void ui_scrollbar_lower_trough_press(ui_scrollbar_t *scrollbar)
+{
+	ui_clickmatic_t *clickmatic = ui_get_clickmatic(scrollbar->ui);
+
+	scrollbar->lower_trough_held = true;
+	scrollbar->lower_trough_inside = true;
+
+	ui_clickmatic_set_cb(clickmatic, &ui_scrollbar_clickmatic_page_down_cb,
+	    (void *) scrollbar);
+	ui_clickmatic_press(clickmatic);
+}
+
+/** Release scrollbar.
+ *
+ * @param scrollbar Scrollbar
+ * @param pos Pointer position
+ */
+void ui_scrollbar_release(ui_scrollbar_t *scrollbar, gfx_coord2_t *pos)
+{
+	ui_clickmatic_t *clickmatic;
+
+	if (scrollbar->thumb_held) {
+		ui_scrollbar_update(scrollbar, pos);
+		scrollbar->thumb_held = false;
+	}
+
+	if (scrollbar->upper_trough_held || scrollbar->lower_trough_held) {
+		clickmatic = ui_get_clickmatic(scrollbar->ui);
+		ui_clickmatic_release(clickmatic);
+		ui_clickmatic_set_cb(clickmatic, NULL, NULL);
+
+		scrollbar->upper_trough_held = false;
+		scrollbar->lower_trough_held = false;
+		(void) ui_scrollbar_paint(scrollbar);
+	}
+}
+
+/** Update state of scrollbar throuhgs.
+ *
+ * Update state of scrollbar troughs after mouse cursor or thumb has moved.
+ *
+ * @param scrollbar Scrollbar
+ * @param pos Mouse cursor position
+ */
+void ui_scrollbar_troughs_update(ui_scrollbar_t *scrollbar, gfx_coord2_t *pos)
+{
+	ui_scrollbar_geom_t geom;
+	bool inside_up;
+	bool inside_down;
+
+	ui_scrollbar_get_geom(scrollbar, &geom);
+
+	inside_up = gfx_pix_inside_rect(pos, &geom.upper_trough_rect);
+	inside_down = gfx_pix_inside_rect(pos, &geom.lower_trough_rect);
+
+	if (inside_up && !scrollbar->upper_trough_inside) {
+		scrollbar->upper_trough_inside = true;
+		(void) ui_scrollbar_paint(scrollbar);
+	} else if (!inside_up && scrollbar->upper_trough_inside) {
+		scrollbar->upper_trough_inside = false;
+		(void) ui_scrollbar_paint(scrollbar);
+	}
+
+	if (inside_down && !scrollbar->lower_trough_inside) {
+		scrollbar->lower_trough_inside = true;
+		(void) ui_scrollbar_paint(scrollbar);
+	} else if (!inside_down && scrollbar->lower_trough_inside) {
+		scrollbar->lower_trough_inside = false;
+		(void) ui_scrollbar_paint(scrollbar);
+	}
+}
+
+/** Scrollbar handler for pointer movement.
+ *
+ * @param scrollbar Scrollbar
+ * @param pos New pointer position
+ */
+void ui_scrollbar_update(ui_scrollbar_t *scrollbar, gfx_coord2_t *pos)
+{
+	gfx_coord_t spos;
+
+	if (scrollbar->thumb_held) {
+		if (scrollbar->dir == ui_sbd_horiz)
+			spos = scrollbar->last_pos + pos->x - scrollbar->press_pos.x;
+		else
+			spos = scrollbar->last_pos + pos->y - scrollbar->press_pos.y;
+
+		ui_scrollbar_set_pos(scrollbar, spos);
+		ui_scrollbar_moved(scrollbar, scrollbar->pos);
+	}
+
+	ui_scrollbar_troughs_update(scrollbar, pos);
+}
+
+/** Scrollbar up button was pressed.
+ *
+ * @param scrollbar Scrollbar
+ */
+void ui_scrollbar_up(ui_scrollbar_t *scrollbar)
+{
+	if (scrollbar->cb != NULL && scrollbar->cb->up != NULL)
+		scrollbar->cb->up(scrollbar, scrollbar->arg);
+}
+
+/** Scrollbar down button was pressed.
+ *
+ * @param scrollbar Scrollbar
+ */
+void ui_scrollbar_down(ui_scrollbar_t *scrollbar)
+{
+	if (scrollbar->cb != NULL && scrollbar->cb->down != NULL)
+		scrollbar->cb->down(scrollbar, scrollbar->arg);
+}
+
+/** Scrollbar upper trough was pressed.
+ *
+ * @param scrollbar Scrollbar
+ */
+void ui_scrollbar_page_up(ui_scrollbar_t *scrollbar)
+{
+	if (scrollbar->cb != NULL && scrollbar->cb->page_up != NULL)
+		scrollbar->cb->page_up(scrollbar, scrollbar->arg);
+}
+
+/** Scrollbar lower trough was pressed.
+ *
+ * @param scrollbar Scrollbar
+ */
+void ui_scrollbar_page_down(ui_scrollbar_t *scrollbar)
+{
+	if (scrollbar->cb != NULL && scrollbar->cb->page_down != NULL)
+		scrollbar->cb->page_down(scrollbar, scrollbar->arg);
+}
+
+/** Scrollbar was moved.
+ *
+ * @param scrollbar Scrollbar
+ */
+void ui_scrollbar_moved(ui_scrollbar_t *scrollbar, gfx_coord_t pos)
+{
+	if (scrollbar->cb != NULL && scrollbar->cb->moved != NULL)
+		scrollbar->cb->moved(scrollbar, scrollbar->arg, pos);
+}
+
+/** Handle scrollbar position event.
+ *
+ * @param scrollbar Scrollbar
+ * @param pos_event Position event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t ui_scrollbar_pos_event(ui_scrollbar_t *scrollbar, pos_event_t *event)
+{
+	gfx_coord2_t pos;
+	ui_evclaim_t claimed;
+	ui_scrollbar_geom_t geom;
+
+	ui_scrollbar_get_geom(scrollbar, &geom);
+
+	pos.x = event->hpos;
+	pos.y = event->vpos;
+
+	claimed = ui_pbutton_pos_event(scrollbar->up_btn, event);
+	if (claimed == ui_claimed)
+		return ui_claimed;
+
+	claimed = ui_pbutton_pos_event(scrollbar->down_btn, event);
+	if (claimed == ui_claimed)
+		return ui_claimed;
+
+	switch (event->type) {
+	case POS_PRESS:
+		if (gfx_pix_inside_rect(&pos, &geom.thumb_rect)) {
+			ui_scrollbar_thumb_press(scrollbar, &pos);
+			return ui_claimed;
+		}
+		if (gfx_pix_inside_rect(&pos, &geom.upper_trough_rect)) {
+			ui_scrollbar_upper_trough_press(scrollbar);
+			return ui_claimed;
+		}
+		if (gfx_pix_inside_rect(&pos, &geom.lower_trough_rect)) {
+			ui_scrollbar_lower_trough_press(scrollbar);
+			return ui_claimed;
+		}
+		break;
+	case POS_RELEASE:
+		if (scrollbar->thumb_held || scrollbar->upper_trough_held ||
+		    scrollbar->lower_trough_held) {
+			ui_scrollbar_release(scrollbar, &pos);
+			return ui_claimed;
+		}
+		break;
+	case POS_UPDATE:
+		ui_scrollbar_update(scrollbar, &pos);
+		scrollbar->last_curs_pos = pos;
+		break;
+	case POS_DCLICK:
+		break;
+	}
+
+	return ui_unclaimed;
+}
+
+/** Scrollbar up button pressed.
+ *
+ * @param pbutton Up button
+ * @param arg Argument (ui_scrollbar_t *)
+ */
+static void ui_scrollbar_up_btn_down(ui_pbutton_t *pbutton, void *arg)
+{
+	ui_scrollbar_t *scrollbar = (ui_scrollbar_t *)arg;
+	ui_clickmatic_t *clickmatic = ui_get_clickmatic(scrollbar->ui);
+
+	ui_clickmatic_set_cb(clickmatic, &ui_scrollbar_clickmatic_up_cb,
+	    (void *) scrollbar);
+	ui_clickmatic_press(clickmatic);
+}
+
+/** Scrollbar up button released.
+ *
+ * @param pbutton Up button
+ * @param arg Argument (ui_scrollbar_t *)
+ */
+static void ui_scrollbar_up_btn_up(ui_pbutton_t *pbutton, void *arg)
+{
+	ui_scrollbar_t *scrollbar = (ui_scrollbar_t *)arg;
+	ui_clickmatic_t *clickmatic = ui_get_clickmatic(scrollbar->ui);
+
+	ui_clickmatic_release(clickmatic);
+	ui_clickmatic_set_cb(clickmatic, NULL, NULL);
+}
+
+/** Paint up button decoration.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (ui_scrollbar_t *)
+ * @param pos Center position
+ */
+static errno_t ui_scrollbar_up_btn_decor_paint(ui_pbutton_t *pbutton,
+    void *arg, gfx_coord2_t *pos)
+{
+	ui_scrollbar_t *scrollbar = (ui_scrollbar_t *)arg;
+	errno_t rc;
+
+	rc = gfx_set_color(pbutton->res->gc, pbutton->res->btn_text_color);
+	if (rc != EOK)
+		return rc;
+
+	if (scrollbar->dir == ui_sbd_horiz)
+		return ui_paint_left_triangle(pbutton->res->gc, pos, 5);
+	else
+		return ui_paint_up_triangle(pbutton->res->gc, pos, 5);
+}
+
+/** Paint down button decoration.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (ui_scrollbar_t *)
+ * @param pos Center position
+ */
+static errno_t ui_scrollbar_down_btn_decor_paint(ui_pbutton_t *pbutton,
+    void *arg, gfx_coord2_t *pos)
+{
+	ui_scrollbar_t *scrollbar = (ui_scrollbar_t *)arg;
+	errno_t rc;
+
+	rc = gfx_set_color(pbutton->res->gc, pbutton->res->btn_text_color);
+	if (rc != EOK)
+		return rc;
+
+	if (scrollbar->dir == ui_sbd_horiz)
+		return ui_paint_right_triangle(pbutton->res->gc, pos, 5);
+	else
+		return ui_paint_down_triangle(pbutton->res->gc, pos, 5);
+}
+
+/** Scrollbar down button pressed.
+ *
+ * @param pbutton Down button
+ * @param arg Argument (ui_scrollbar_t *)
+ */
+static void ui_scrollbar_down_btn_down(ui_pbutton_t *pbutton, void *arg)
+{
+	ui_scrollbar_t *scrollbar = (ui_scrollbar_t *)arg;
+	ui_clickmatic_t *clickmatic = ui_get_clickmatic(scrollbar->ui);
+
+	ui_clickmatic_set_cb(clickmatic, &ui_scrollbar_clickmatic_down_cb,
+	    (void *) scrollbar);
+	ui_clickmatic_press(clickmatic);
+}
+
+/** Scrollbar down button released.
+ *
+ * @param pbutton Down button
+ * @param arg Argument (ui_scrollbar_t *)
+ */
+static void ui_scrollbar_down_btn_up(ui_pbutton_t *pbutton, void *arg)
+{
+	ui_scrollbar_t *scrollbar = (ui_scrollbar_t *)arg;
+	ui_clickmatic_t *clickmatic = ui_get_clickmatic(scrollbar->ui);
+
+	ui_clickmatic_release(clickmatic);
+	ui_clickmatic_set_cb(clickmatic, NULL, NULL);
+}
+
+/** Destroy scrollbar control.
+ *
+ * @param arg Argument (ui_scrollbar_t *)
+ */
+void ui_scrollbar_ctl_destroy(void *arg)
+{
+	ui_scrollbar_t *scrollbar = (ui_scrollbar_t *) arg;
+
+	ui_scrollbar_destroy(scrollbar);
+}
+
+/** Paint scrollbar control.
+ *
+ * @param arg Argument (ui_scrollbar_t *)
+ * @return EOK on success or an error code
+ */
+errno_t ui_scrollbar_ctl_paint(void *arg)
+{
+	ui_scrollbar_t *scrollbar = (ui_scrollbar_t *) arg;
+
+	return ui_scrollbar_paint(scrollbar);
+}
+
+/** Handle scrollbar control position event.
+ *
+ * @param arg Argument (ui_scrollbar_t *)
+ * @param pos_event Position event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t ui_scrollbar_ctl_pos_event(void *arg, pos_event_t *event)
+{
+	ui_scrollbar_t *scrollbar = (ui_scrollbar_t *)arg;
+
+	return ui_scrollbar_pos_event(scrollbar, event);
+}
+
+/** Scrollbar clickmatic up button click event.
+ *
+ * @param clickmatic Clickmatic
+ * @param arg Argument (ui_scrollbar_t *)
+ */
+static void ui_scrollbar_cm_up(ui_clickmatic_t *clickmatic, void *arg)
+{
+	ui_scrollbar_t *scrollbar = (ui_scrollbar_t *)arg;
+
+	if (scrollbar->up_btn->inside)
+		ui_scrollbar_up(scrollbar);
+}
+
+/** Scrollbar clickmatic down button click event.
+ *
+ * @param clickmatic Clickmatic
+ * @param arg Argument (ui_scrollbar_t *)
+ */
+static void ui_scrollbar_cm_down(ui_clickmatic_t *clickmatic, void *arg)
+{
+	ui_scrollbar_t *scrollbar = (ui_scrollbar_t *)arg;
+
+	if (scrollbar->down_btn->inside)
+		ui_scrollbar_down(scrollbar);
+}
+
+/** Scrollbar clickmatic upper trough click event.
+ *
+ * @param clickmatic Clickmatic
+ * @param arg Argument (ui_scrollbar_t *)
+ */
+static void ui_scrollbar_cm_page_up(ui_clickmatic_t *clickmatic, void *arg)
+{
+	ui_scrollbar_t *scrollbar = (ui_scrollbar_t *)arg;
+
+	if (scrollbar->upper_trough_inside)
+		ui_scrollbar_page_up(scrollbar);
+}
+
+/** Scrollbar clickmatic lower trough click event.
+ *
+ * @param clickmatic Clickmatic
+ * @param arg Argument (ui_scrollbar_t *)
+ */
+static void ui_scrollbar_cm_page_down(ui_clickmatic_t *clickmatic, void *arg)
+{
+	ui_scrollbar_t *scrollbar = (ui_scrollbar_t *)arg;
+
+	if (scrollbar->lower_trough_inside)
+		ui_scrollbar_page_down(scrollbar);
+}
+
+/** @}
+ */
Index: uspace/lib/ui/src/selectdialog.c
===================================================================
--- uspace/lib/ui/src/selectdialog.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/src/selectdialog.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,442 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Select dialog
+ */
+
+#include <errno.h>
+#include <mem.h>
+#include <stdlib.h>
+#include <ui/fixed.h>
+#include <ui/label.h>
+#include <ui/list.h>
+#include <ui/selectdialog.h>
+#include <ui/pbutton.h>
+#include <ui/resource.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include "../private/selectdialog.h"
+
+static void ui_select_dialog_wnd_close(ui_window_t *, void *);
+static void ui_select_dialog_wnd_kbd(ui_window_t *, void *, kbd_event_t *);
+
+ui_window_cb_t ui_select_dialog_wnd_cb = {
+	.close = ui_select_dialog_wnd_close,
+	.kbd = ui_select_dialog_wnd_kbd
+};
+
+static void ui_select_dialog_bok_clicked(ui_pbutton_t *, void *);
+static void ui_select_dialog_bcancel_clicked(ui_pbutton_t *, void *);
+
+ui_pbutton_cb_t ui_select_dialog_bok_cb = {
+	.clicked = ui_select_dialog_bok_clicked
+};
+
+ui_pbutton_cb_t ui_select_dialog_bcancel_cb = {
+	.clicked = ui_select_dialog_bcancel_clicked
+};
+
+static void ui_select_dialog_list_selected(ui_list_entry_t *, void *);
+
+ui_list_cb_t ui_select_dialog_list_cb = {
+	.selected = ui_select_dialog_list_selected
+};
+
+/** Initialize select dialog parameters structure.
+ *
+ * Select dialog parameters structure must always be initialized using
+ * this function first.
+ *
+ * @param params Select dialog parameters structure
+ */
+void ui_select_dialog_params_init(ui_select_dialog_params_t *params)
+{
+	memset(params, 0, sizeof(ui_select_dialog_params_t));
+}
+
+/** Create new select dialog.
+ *
+ * @param ui User interface
+ * @param params Select dialog parameters
+ * @param rdialog Place to store pointer to new dialog
+ * @return EOK on success or an error code
+ */
+errno_t ui_select_dialog_create(ui_t *ui, ui_select_dialog_params_t *params,
+    ui_select_dialog_t **rdialog)
+{
+	errno_t rc;
+	ui_select_dialog_t *dialog;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t wparams;
+	ui_fixed_t *fixed = NULL;
+	ui_label_t *label = NULL;
+	ui_list_t *list = NULL;
+	ui_pbutton_t *bok = NULL;
+	ui_pbutton_t *bcancel = NULL;
+	gfx_rect_t rect;
+	ui_resource_t *ui_res;
+
+	dialog = calloc(1, sizeof(ui_select_dialog_t));
+	if (dialog == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ui_wnd_params_init(&wparams);
+	wparams.caption = params->caption;
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		wparams.rect.p0.x = 0;
+		wparams.rect.p0.y = 0;
+		wparams.rect.p1.x = 55;
+		wparams.rect.p1.y = 19;
+	} else {
+		wparams.rect.p0.x = 0;
+		wparams.rect.p0.y = 0;
+		wparams.rect.p1.x = 450;
+		wparams.rect.p1.y = 235;
+	}
+
+	rc = ui_window_create(ui, &wparams, &window);
+	if (rc != EOK)
+		goto error;
+
+	ui_window_set_cb(window, &ui_select_dialog_wnd_cb, dialog);
+
+	ui_res = ui_window_get_res(window);
+
+	rc = ui_fixed_create(&fixed);
+	if (rc != EOK)
+		goto error;
+
+	rc = ui_label_create(ui_res, params->prompt, &label);
+	if (rc != EOK)
+		goto error;
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 3;
+		rect.p0.y = 2;
+		rect.p1.x = 17;
+		rect.p1.y = 3;
+	} else {
+		rect.p0.x = 10;
+		rect.p0.y = 35;
+		rect.p1.x = 190;
+		rect.p1.y = 50;
+	}
+
+	ui_label_set_rect(label, &rect);
+
+	rc = ui_fixed_add(fixed, ui_label_ctl(label));
+	if (rc != EOK)
+		goto error;
+
+	label = NULL;
+
+	rc = ui_list_create(window, true, &list);
+	if (rc != EOK)
+		goto error;
+
+	ui_list_set_cb(list, &ui_select_dialog_list_cb, dialog);
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 3;
+		rect.p0.y = 4;
+		rect.p1.x = 52;
+		rect.p1.y = 15;
+	} else {
+		rect.p0.x = 10;
+		rect.p0.y = 55;
+		rect.p1.x = 440;
+		rect.p1.y = 180;
+	}
+
+	ui_list_set_rect(list, &rect);
+
+	rc = ui_fixed_add(fixed, ui_list_ctl(list));
+	if (rc != EOK)
+		goto error;
+
+	dialog->list = list;
+	list = NULL;
+
+	rc = ui_pbutton_create(ui_res, "OK", &bok);
+	if (rc != EOK)
+		goto error;
+
+	ui_pbutton_set_cb(bok, &ui_select_dialog_bok_cb, dialog);
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 16;
+		rect.p0.y = 16;
+		rect.p1.x = 26;
+		rect.p1.y = 17;
+	} else {
+		rect.p0.x = 130;
+		rect.p0.y = 190;
+		rect.p1.x = 220;
+		rect.p1.y = 218;
+	}
+
+	ui_pbutton_set_rect(bok, &rect);
+
+	ui_pbutton_set_default(bok, true);
+
+	rc = ui_fixed_add(fixed, ui_pbutton_ctl(bok));
+	if (rc != EOK)
+		goto error;
+
+	dialog->bok = bok;
+	bok = NULL;
+
+	rc = ui_pbutton_create(ui_res, "Cancel", &bcancel);
+	if (rc != EOK)
+		goto error;
+
+	ui_pbutton_set_cb(bcancel, &ui_select_dialog_bcancel_cb, dialog);
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 28;
+		rect.p0.y = 16;
+		rect.p1.x = 38;
+		rect.p1.y = 17;
+	} else {
+		rect.p0.x = 230;
+		rect.p0.y = 190;
+		rect.p1.x = 320;
+		rect.p1.y = 218;
+	}
+
+	ui_pbutton_set_rect(bcancel, &rect);
+
+	rc = ui_fixed_add(fixed, ui_pbutton_ctl(bcancel));
+	if (rc != EOK)
+		goto error;
+
+	dialog->bcancel = bcancel;
+	bcancel = NULL;
+
+	ui_window_add(window, ui_fixed_ctl(fixed));
+	fixed = NULL;
+
+	rc = ui_window_paint(window);
+	if (rc != EOK)
+		goto error;
+
+	dialog->window = window;
+	*rdialog = dialog;
+	return EOK;
+error:
+	if (list != NULL)
+		ui_list_destroy(list);
+	if (bok != NULL)
+		ui_pbutton_destroy(bok);
+	if (bcancel != NULL)
+		ui_pbutton_destroy(bcancel);
+	if (label != NULL)
+		ui_label_destroy(label);
+	if (fixed != NULL)
+		ui_fixed_destroy(fixed);
+	if (window != NULL)
+		ui_window_destroy(window);
+	if (dialog != NULL)
+		free(dialog);
+	return rc;
+}
+
+/** Destroy select dialog.
+ *
+ * @param dialog Select dialog or @c NULL
+ */
+void ui_select_dialog_destroy(ui_select_dialog_t *dialog)
+{
+	if (dialog == NULL)
+		return;
+
+	ui_window_destroy(dialog->window);
+	free(dialog);
+}
+
+/** Set mesage dialog callback.
+ *
+ * @param dialog Select dialog
+ * @param cb Select dialog callbacks
+ * @param arg Callback argument
+ */
+void ui_select_dialog_set_cb(ui_select_dialog_t *dialog, ui_select_dialog_cb_t *cb,
+    void *arg)
+{
+	dialog->cb = cb;
+	dialog->arg = arg;
+}
+
+/** Append new entry to select dialog.
+ *
+ * @param dialog Select dialog
+ * @param attr List entry attributes
+ * @return EOK on success or an error code
+ */
+errno_t ui_select_dialog_append(ui_select_dialog_t *dialog,
+    ui_list_entry_attr_t *attr)
+{
+	return ui_list_entry_append(dialog->list, attr, NULL);
+}
+
+/** Paint select dialog.
+ *
+ * This needs to be called after appending entries.
+ *
+ * @param dialog Select dialog
+ * @return EOK on success or an error code
+ */
+errno_t ui_select_dialog_paint(ui_select_dialog_t *dialog)
+{
+	return ui_window_paint(dialog->window);
+}
+
+/** Get the entry list from select dialog.
+ *
+ * @param dialog Select dialog
+ * @return UI list
+ */
+ui_list_t *ui_select_dialog_list(ui_select_dialog_t *dialog)
+{
+	return dialog->list;
+}
+
+/** Select dialog window close handler.
+ *
+ * @param window Window
+ * @param arg Argument (ui_select_dialog_t *)
+ */
+static void ui_select_dialog_wnd_close(ui_window_t *window, void *arg)
+{
+	ui_select_dialog_t *dialog = (ui_select_dialog_t *) arg;
+
+	if (dialog->cb != NULL && dialog->cb->close != NULL)
+		dialog->cb->close(dialog, dialog->arg);
+}
+
+/** Select dialog window keyboard event handler.
+ *
+ * @param window Window
+ * @param arg Argument (ui_select_dialog_t *)
+ * @param event Keyboard event
+ */
+static void ui_select_dialog_wnd_kbd(ui_window_t *window, void *arg,
+    kbd_event_t *event)
+{
+	ui_select_dialog_t *dialog = (ui_select_dialog_t *) arg;
+	ui_list_entry_t *entry;
+	void *earg;
+
+	if (event->type == KEY_PRESS &&
+	    (event->mods & (KM_CTRL | KM_SHIFT | KM_ALT)) == 0) {
+		if (event->key == KC_ENTER) {
+			/* Confirm */
+			if (dialog->cb != NULL && dialog->cb->bok != NULL) {
+				entry = ui_list_get_cursor(dialog->list);
+				earg = ui_list_entry_get_arg(entry);
+				dialog->cb->bok(dialog, dialog->arg, earg);
+				return;
+			}
+		} else if (event->key == KC_ESCAPE) {
+			/* Cancel */
+			if (dialog->cb != NULL && dialog->cb->bcancel != NULL) {
+				dialog->cb->bcancel(dialog, dialog->arg);
+				return;
+			}
+		}
+	}
+
+	ui_window_def_kbd(window, event);
+}
+
+/** Select dialog OK button click handler.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (ui_select_dialog_t *)
+ */
+static void ui_select_dialog_bok_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	ui_select_dialog_t *dialog = (ui_select_dialog_t *) arg;
+	ui_list_entry_t *entry;
+	void *earg;
+
+	if (dialog->cb != NULL && dialog->cb->bok != NULL) {
+		entry = ui_list_get_cursor(dialog->list);
+		if (entry != NULL)
+			earg = ui_list_entry_get_arg(entry);
+		else
+			earg = NULL;
+
+		dialog->cb->bok(dialog, dialog->arg, earg);
+	}
+}
+
+/** Select dialog cancel button click handler.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (ui_select_dialog_t *)
+ */
+static void ui_select_dialog_bcancel_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	ui_select_dialog_t *dialog = (ui_select_dialog_t *) arg;
+
+	if (dialog->cb != NULL && dialog->cb->bcancel != NULL)
+		dialog->cb->bcancel(dialog, dialog->arg);
+}
+
+/** Select dialog list entry selection handler.
+ *
+ * @param entry UI list entry
+ * @param arg Entry argument
+ */
+static void ui_select_dialog_list_selected(ui_list_entry_t *entry, void *arg)
+{
+	ui_list_t *list;
+	ui_select_dialog_t *dialog;
+
+	list = ui_list_entry_get_list(entry);
+	dialog = (ui_select_dialog_t *)ui_list_get_cb_arg(list);
+
+	if (dialog->cb != NULL && dialog->cb->bok != NULL)
+		dialog->cb->bok(dialog, dialog->arg, arg);
+}
+
+/** @}
+ */
Index: uspace/lib/ui/src/slider.c
===================================================================
--- uspace/lib/ui/src/slider.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/slider.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -60,4 +60,6 @@
 	/** Slider button width in text mode */
 	ui_slider_btn_w_text = 3,
+	/** Slider button height in text mode */
+	ui_slider_btn_h_text = 1,
 	/** Slider button frame thickness */
 	ui_slider_btn_frame_thickness = 1,
@@ -86,10 +88,8 @@
  *
  * @param resource UI resource
- * @param caption Caption
  * @param rslider Place to store pointer to new slider
  * @return EOK on success, ENOMEM if out of memory
  */
-errno_t ui_slider_create(ui_resource_t *resource, const char *caption,
-    ui_slider_t **rslider)
+errno_t ui_slider_create(ui_resource_t *resource, ui_slider_t **rslider)
 {
 	ui_slider_t *slider;
@@ -242,6 +242,12 @@
 	rect->p0.x = pos.x;
 	rect->p0.y = pos.y;
-	rect->p1.x = pos.x + ui_slider_btn_w;
-	rect->p1.y = pos.y + ui_slider_btn_h;
+
+	if (slider->res->textmode) {
+		rect->p1.x = pos.x + ui_slider_btn_w_text;
+		rect->p1.y = pos.y + ui_slider_btn_h_text;
+	} else {
+		rect->p1.x = pos.x + ui_slider_btn_w;
+		rect->p1.y = pos.y + ui_slider_btn_h;
+	}
 }
 
@@ -341,4 +347,5 @@
 
 	gfx_text_fmt_init(&fmt);
+	fmt.font = slider->res->font;
 	fmt.color = slider->res->wnd_text_color;
 	fmt.halign = gfx_halign_left;
@@ -357,5 +364,5 @@
 	buf[w * gcharsz] = '\0';
 
-	rc = gfx_puttext(slider->res->font, &pos, &fmt, buf);
+	rc = gfx_puttext(&pos, &fmt, buf);
 	free(buf);
 	if (rc != EOK)
@@ -366,6 +373,5 @@
 	pos.x += slider->pos;
 
-	rc = gfx_puttext(slider->res->font, &pos, &fmt,
-	    "[O]");
+	rc = gfx_puttext(&pos, &fmt, "[O]");
 	if (rc != EOK)
 		goto error;
@@ -522,4 +528,6 @@
 		ui_slider_update(slider, &pos);
 		break;
+	case POS_DCLICK:
+		break;
 	}
 
Index: uspace/lib/ui/src/tab.c
===================================================================
--- uspace/lib/ui/src/tab.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/src/tab.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,671 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Tab
+ */
+
+#include <adt/list.h>
+#include <errno.h>
+#include <gfx/color.h>
+#include <gfx/context.h>
+#include <gfx/render.h>
+#include <gfx/text.h>
+#include <io/pos_event.h>
+#include <stdlib.h>
+#include <str.h>
+#include <uchar.h>
+#include <ui/control.h>
+#include <ui/paint.h>
+#include <ui/popup.h>
+#include <ui/tab.h>
+#include <ui/tabset.h>
+#include <ui/resource.h>
+#include <ui/window.h>
+#include "../private/control.h"
+#include "../private/tab.h"
+#include "../private/tabset.h"
+#include "../private/resource.h"
+
+enum {
+	/** Horizontal margin before first tab handle */
+	tab_start_hmargin = 6,
+	/** Horizontal margin before first tab handle in text mode */
+	tab_start_hmargin_text = 1,
+	/** Tab handle horizontal internal padding */
+	tab_handle_hpad = 6,
+	/** Tab handle top internal padding */
+	tab_handle_top_pad = 5,
+	/** Tab handle bottom internal padding */
+	tab_handle_bottom_pad = 5,
+	/** Tab handle horizontal internal padding in text mode */
+	tab_handle_hpad_text = 1,
+	/** Tab handle top internal padding in text mode */
+	tab_handle_top_pad_text = 0,
+	/** Tab handle bototm internal padding in text mode */
+	tab_handle_bottom_pad_text = 1,
+	/** Tab handle chamfer */
+	tab_handle_chamfer = 3,
+	/** Number of pixels to pull active handle up by */
+	tab_handle_pullup = 2,
+	/** Tab frame horizontal thickness */
+	tab_frame_w = 2,
+	/** Tab frame vertical thickness */
+	tab_frame_h = 2,
+	/** Tab frame horizontal thickness in text mode */
+	tab_frame_w_text = 1,
+	/** Tab frame horizontal thickness in text mode */
+	tab_frame_h_text = 1
+};
+
+/** Selected tab handle box characters */
+static ui_box_chars_t sel_tab_box_chars = {
+	{
+		{ "\u250c", "\u2500", "\u2510" },
+		{ "\u2502", " ",      "\u2502" },
+		{ "\u2518", " ",      "\u2514" }
+	}
+};
+
+/** Not selected tab handle box characters */
+static ui_box_chars_t unsel_tab_box_chars = {
+	{
+		{ "\u250c", "\u2500", "\u2510" },
+		{ "\u2502", " ",      "\u2502" },
+		{ "\u2534", "\u2500", "\u2534" }
+	}
+};
+
+/** Create new tab.
+ *
+ * @param tabset Tab set
+ * @param caption Caption
+ * @param rtab Place to store pointer to new tab
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ui_tab_create(ui_tab_set_t *tabset, const char *caption,
+    ui_tab_t **rtab)
+{
+	ui_tab_t *tab;
+	ui_tab_t *prev;
+
+	tab = calloc(1, sizeof(ui_tab_t));
+	if (tab == NULL)
+		return ENOMEM;
+
+	tab->caption = str_dup(caption);
+	if (tab->caption == NULL) {
+		free(tab);
+		return ENOMEM;
+	}
+
+	prev = ui_tab_last(tabset);
+	if (prev != NULL)
+		tab->xoff = prev->xoff + ui_tab_handle_width(prev);
+	else
+		tab->xoff = tabset->res->textmode ?
+		    tab_start_hmargin_text : tab_start_hmargin;
+
+	tab->tabset = tabset;
+	list_append(&tab->ltabs, &tabset->tabs);
+
+	/* This is the first tab. Select it. */
+	if (tabset->selected == NULL)
+		tabset->selected = tab;
+
+	*rtab = tab;
+	return EOK;
+}
+
+/** Destroy tab.
+ *
+ * @param tab Tab or @c NULL
+ */
+void ui_tab_destroy(ui_tab_t *tab)
+{
+	if (tab == NULL)
+		return;
+
+	/* Destroy content */
+	ui_control_destroy(tab->content);
+
+	list_remove(&tab->ltabs);
+	free(tab->caption);
+	free(tab);
+}
+
+/** Get first tab in tab bar.
+ *
+ * @param tabset Tab set
+ * @return First tab or @c NULL if there is none
+ */
+ui_tab_t *ui_tab_first(ui_tab_set_t *tabset)
+{
+	link_t *link;
+
+	link = list_first(&tabset->tabs);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_tab_t, ltabs);
+}
+
+/** Get next tab in tab bar.
+ *
+ * @param cur Current tab
+ * @return Next tab or @c NULL if @a cur is the last one
+ */
+ui_tab_t *ui_tab_next(ui_tab_t *cur)
+{
+	link_t *link;
+
+	link = list_next(&cur->ltabs, &cur->tabset->tabs);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_tab_t, ltabs);
+}
+
+/** Get last tab in tab bar.
+ *
+ * @param tabset Tab set
+ * @return Last tab or @c NULL if there is none
+ */
+ui_tab_t *ui_tab_last(ui_tab_set_t *tabset)
+{
+	link_t *link;
+
+	link = list_last(&tabset->tabs);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_tab_t, ltabs);
+}
+
+/** Get previous tab in tab bar.
+ *
+ * @param cur Current tab
+ * @return Previous tab or @c NULL if @a cur is the fist one
+ */
+ui_tab_t *ui_tab_prev(ui_tab_t *cur)
+{
+	link_t *link;
+
+	link = list_prev(&cur->ltabs, &cur->tabset->tabs);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_tab_t, ltabs);
+}
+
+/** Determine if tab is selected.
+ *
+ * @param tab Tab
+ * @return @c true iff tab is selected
+ */
+bool ui_tab_is_selected(ui_tab_t *tab)
+{
+	return tab->tabset->selected == tab;
+}
+
+/** Add control to tab.
+ *
+ * Only one control can be added to a window. If more than one control
+ * is added, the results are undefined.
+ *
+ * @param tab Tab
+ * @param control Control
+ */
+void ui_tab_add(ui_tab_t *tab, ui_control_t *control)
+{
+	assert(tab->content == NULL);
+
+	tab->content = control;
+	control->elemp = (void *) tab;
+}
+
+/** Remove control from tab.
+ *
+ * @param tab Tab
+ * @param control Control
+ */
+void ui_tab_remove(ui_tab_t *tab, ui_control_t *control)
+{
+	assert(tab->content == control);
+	assert((ui_tab_t *) control->elemp == tab);
+
+	tab->content = NULL;
+	control->elemp = NULL;
+}
+
+/** Get tab handle width.
+ *
+ * @param tab Tab
+ * @return Handle width in pixels
+ */
+gfx_coord_t ui_tab_handle_width(ui_tab_t *tab)
+{
+	ui_resource_t *res;
+	gfx_coord_t frame_w;
+	gfx_coord_t handle_hpad;
+	gfx_coord_t text_w;
+
+	res = tab->tabset->res;
+	if (!res->textmode) {
+		frame_w = tab_frame_w;
+		handle_hpad = tab_handle_hpad;
+	} else {
+		frame_w = tab_frame_w_text;
+		handle_hpad = tab_handle_hpad_text;
+	}
+
+	text_w = ui_text_width(tab->tabset->res->font, tab->caption);
+	return 2 * frame_w + 2 * handle_hpad + text_w;
+}
+
+/** Get tab handle height.
+ *
+ * @param tab Tab
+ * @return Handle height in pixels
+ */
+gfx_coord_t ui_tab_handle_height(ui_tab_t *tab)
+{
+	gfx_coord_t frame_h;
+	gfx_coord_t handle_top_pad;
+	gfx_coord_t handle_bottom_pad;
+	gfx_font_metrics_t metrics;
+	ui_resource_t *res;
+
+	res = tab->tabset->res;
+	gfx_font_get_metrics(tab->tabset->res->font, &metrics);
+
+	if (!res->textmode) {
+		frame_h = tab_frame_h;
+		handle_top_pad = tab_handle_top_pad;
+		handle_bottom_pad = tab_handle_bottom_pad;
+	} else {
+		frame_h = tab_frame_h_text;
+		handle_top_pad = tab_handle_top_pad_text;
+		handle_bottom_pad = tab_handle_bottom_pad_text;
+	}
+
+	return frame_h + handle_top_pad + metrics.ascent +
+	    metrics.descent + 1 + handle_bottom_pad;
+}
+
+/** Get tab geometry.
+ *
+ * @param tab Tab
+ * @param geom Structure to fill in with computed geometry
+ */
+void ui_tab_get_geom(ui_tab_t *tab, ui_tab_geom_t *geom)
+{
+	gfx_coord_t handle_w;
+	gfx_coord_t handle_h;
+	gfx_coord_t pullup;
+	gfx_coord_t frame_w;
+	gfx_coord_t frame_h;
+	gfx_coord_t handle_hpad;
+	gfx_coord_t handle_top_pad;
+	ui_resource_t *res;
+
+	res = tab->tabset->res;
+
+	handle_w = ui_tab_handle_width(tab);
+	handle_h = ui_tab_handle_height(tab);
+	pullup = res->textmode ? 0 : tab_handle_pullup;
+
+	if (!res->textmode) {
+		frame_w = tab_frame_w;
+		frame_h = tab_frame_h;
+		handle_hpad = tab_handle_hpad;
+		handle_top_pad = tab_handle_top_pad;
+	} else {
+		frame_w = tab_frame_w_text;
+		frame_h = tab_frame_h_text;
+		handle_hpad = tab_handle_hpad_text;
+		handle_top_pad = tab_handle_top_pad_text;
+	}
+
+	/* Entire handle area */
+	geom->handle_area.p0.x = tab->tabset->rect.p0.x + tab->xoff;
+	geom->handle_area.p0.y = tab->tabset->rect.p0.y;
+	geom->handle_area.p1.x = geom->handle_area.p0.x + handle_w;
+	geom->handle_area.p1.y = geom->handle_area.p0.y + handle_h + pullup;
+
+	geom->handle = geom->handle_area;
+
+	/* If handle is selected */
+	if (!ui_tab_is_selected(tab)) {
+		/* Push top of handle down a bit */
+		geom->handle.p0.y += pullup;
+		/* Do not paint background over tab body frame */
+		geom->handle_area.p1.y -= pullup;
+	}
+
+	/* Caption text position */
+	geom->text_pos.x = geom->handle.p0.x + frame_w + handle_hpad;
+	geom->text_pos.y = geom->handle.p0.y + frame_h + handle_top_pad;
+
+	/* Tab body */
+	geom->body.p0.x = tab->tabset->rect.p0.x;
+	geom->body.p0.y = tab->tabset->rect.p0.y + handle_h - frame_h +
+	    pullup;
+	geom->body.p1 = tab->tabset->rect.p1;
+}
+
+/** Get UI resource from tab.
+ *
+ * @param tab Tab
+ * @return UI resource
+ */
+ui_resource_t *ui_tab_get_res(ui_tab_t *tab)
+{
+	return tab->tabset->res;
+}
+
+/** Paint tab handle frame.
+ *
+ * @param gc Graphic context
+ * @param rect Rectangle
+ * @param chamfer Chamfer
+ * @param hi_color Highlight color
+ * @param sh_color Shadow color
+ * @param selected Tab is selected
+ * @param irect Place to store interior rectangle
+ * @return EOK on success or an error code
+ */
+errno_t ui_tab_paint_handle_frame(gfx_context_t *gc, gfx_rect_t *rect,
+    gfx_coord_t chamfer, gfx_color_t *hi_color, gfx_color_t *sh_color,
+    bool selected, gfx_rect_t *irect)
+{
+	gfx_rect_t r;
+	gfx_coord_t i;
+	errno_t rc;
+
+	rc = gfx_set_color(gc, hi_color);
+	if (rc != EOK)
+		goto error;
+
+	/* Left side */
+	r.p0.x = rect->p0.x;
+	r.p0.y = rect->p0.y + chamfer;
+	r.p1.x = rect->p0.x + 1;
+	r.p1.y = rect->p1.y - 2;
+	rc = gfx_fill_rect(gc, &r);
+	if (rc != EOK)
+		goto error;
+
+	/* Top-left chamfer */
+	for (i = 1; i < chamfer; i++) {
+		r.p0.x = rect->p0.x + i;
+		r.p0.y = rect->p0.y + chamfer - i;
+		r.p1.x = r.p0.x + 1;
+		r.p1.y = r.p0.y + 1;
+		rc = gfx_fill_rect(gc, &r);
+		if (rc != EOK)
+			goto error;
+	}
+
+	/* Top side */
+	r.p0.x = rect->p0.x + chamfer;
+	r.p0.y = rect->p0.y;
+	r.p1.x = rect->p1.x - chamfer;
+	r.p1.y = rect->p0.y + 1;
+	rc = gfx_fill_rect(gc, &r);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_set_color(gc, sh_color);
+	if (rc != EOK)
+		goto error;
+
+	/* Top-right chamfer */
+	for (i = 1; i < chamfer; i++) {
+		r.p0.x = rect->p1.x - 1 - i;
+		r.p0.y = rect->p0.y + chamfer - i;
+		r.p1.x = r.p0.x + 1;
+		r.p1.y = r.p0.y + 1;
+		rc = gfx_fill_rect(gc, &r);
+		if (rc != EOK)
+			goto error;
+	}
+
+	/* Right side */
+	r.p0.x = rect->p1.x - 1;
+	r.p0.y = rect->p0.y + chamfer;
+	r.p1.x = rect->p1.x;
+	r.p1.y = rect->p1.y - 2;
+	rc = gfx_fill_rect(gc, &r);
+	if (rc != EOK)
+		goto error;
+
+	irect->p0.x = rect->p0.x + 1;
+	irect->p0.y = rect->p0.y + 1;
+	irect->p1.x = rect->p1.x - 1;
+	irect->p1.y = rect->p1.y;
+	return EOK;
+error:
+	return rc;
+}
+
+/** Paint tab body frame.
+ *
+ * @param tab Tab
+ * @return EOK on success or an error code
+ */
+errno_t ui_tab_paint_body_frame(ui_tab_t *tab)
+{
+	gfx_rect_t bg_rect;
+	ui_tab_geom_t geom;
+	ui_resource_t *res;
+	errno_t rc;
+
+	res = ui_tab_get_res(tab);
+	ui_tab_get_geom(tab, &geom);
+
+	if (!res->textmode) {
+		rc = ui_paint_outset_frame(res, &geom.body, &bg_rect);
+		if (rc != EOK)
+			goto error;
+	} else {
+		rc = ui_paint_text_box(res, &geom.body, ui_box_single,
+		    res->wnd_face_color);
+		if (rc != EOK)
+			goto error;
+
+		bg_rect.p0.x = geom.body.p0.x + 1;
+		bg_rect.p0.y = geom.body.p0.y + 1;
+		bg_rect.p1.x = geom.body.p1.x - 1;
+		bg_rect.p1.y = geom.body.p1.y - 1;
+	}
+
+	rc = gfx_set_color(res->gc, res->wnd_face_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_fill_rect(res->gc, &bg_rect);
+	if (rc != EOK)
+		goto error;
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Paint tab frame.
+ *
+ * @param tab Tab
+ * @return EOK on success or an error code
+ */
+errno_t ui_tab_paint_frame(ui_tab_t *tab)
+{
+	gfx_rect_t r0;
+	ui_tab_geom_t geom;
+	ui_resource_t *res;
+	errno_t rc;
+
+	res = ui_tab_get_res(tab);
+	ui_tab_get_geom(tab, &geom);
+
+	/* Paint handle background */
+
+	rc = gfx_set_color(res->gc, res->wnd_face_color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_fill_rect(res->gc, &geom.handle_area);
+	if (rc != EOK)
+		goto error;
+
+	/* Paint handle frame */
+	if (!res->textmode) {
+		rc = ui_tab_paint_handle_frame(res->gc, &geom.handle,
+		    tab_handle_chamfer, res->wnd_frame_hi_color, res->wnd_frame_sh_color,
+		    ui_tab_is_selected(tab), &r0);
+		if (rc != EOK)
+			goto error;
+
+		rc = ui_tab_paint_handle_frame(res->gc, &r0, tab_handle_chamfer - 1,
+		    res->wnd_highlight_color, res->wnd_shadow_color,
+		    ui_tab_is_selected(tab), &r0);
+		if (rc != EOK)
+			goto error;
+	} else {
+		rc = ui_paint_text_box_custom(res, &geom.handle,
+		    ui_tab_is_selected(tab) ? &sel_tab_box_chars :
+		    &unsel_tab_box_chars, res->wnd_face_color);
+		if (rc != EOK)
+			goto error;
+	}
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Paint tab.
+ *
+ * @param tab Tab
+ * @return EOK on success or an error code
+ */
+errno_t ui_tab_paint(ui_tab_t *tab)
+{
+	gfx_text_fmt_t fmt;
+	ui_tab_geom_t geom;
+	ui_resource_t *res;
+	errno_t rc;
+
+	res = ui_tab_get_res(tab);
+	ui_tab_get_geom(tab, &geom);
+
+	rc = ui_tab_paint_frame(tab);
+	if (rc != EOK)
+		goto error;
+
+	/* Paint caption */
+
+	gfx_text_fmt_init(&fmt);
+	fmt.font = res->font;
+	fmt.halign = gfx_halign_left;
+	fmt.valign = gfx_valign_top;
+	fmt.color = res->wnd_text_color;
+
+	rc = gfx_puttext(&geom.text_pos, &fmt, tab->caption);
+	if (rc != EOK)
+		goto error;
+
+	if (tab->content != NULL && ui_tab_is_selected(tab)) {
+		/* Paint content */
+		rc = ui_control_paint(tab->content);
+		if (rc != EOK)
+			goto error;
+	}
+
+	rc = gfx_update(res->gc);
+	if (rc != EOK)
+		goto error;
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Handle position event in tab.
+ *
+ * @param tab Tab
+ * @param event Position event
+ * @return ui_claimed iff the event was claimed
+ */
+ui_evclaim_t ui_tab_pos_event(ui_tab_t *tab, pos_event_t *event)
+{
+	ui_tab_geom_t geom;
+	gfx_coord2_t epos;
+
+	ui_tab_get_geom(tab, &geom);
+	epos.x = event->hpos;
+	epos.y = event->vpos;
+
+	/* Event inside tab handle? */
+	if (gfx_pix_inside_rect(&epos, &geom.handle)) {
+		/* Select tab? */
+		if (event->type == POS_PRESS && event->btn_num == 1 &&
+		    !ui_tab_is_selected(tab))
+			ui_tab_set_select(tab->tabset, tab);
+
+		/* Claim event */
+		return ui_claimed;
+	}
+
+	/* Deliver event to content control, if any */
+	if (ui_tab_is_selected(tab) && tab->content != NULL)
+		return ui_control_pos_event(tab->content, event);
+
+	return ui_unclaimed;
+}
+
+/** Handle keyboard event in tab.
+ *
+ * @param tab Tab
+ * @param event Keyboard event
+ * @return ui_claimed iff the event was claimed
+ */
+ui_evclaim_t ui_tab_kbd_event(ui_tab_t *tab, kbd_event_t *event)
+{
+	/* Deliver event to content control, if any */
+	if (ui_tab_is_selected(tab) && tab->content != NULL)
+		return ui_control_kbd_event(tab->content, event);
+
+	return ui_unclaimed;
+}
+
+/** @}
+ */
Index: uspace/lib/ui/src/tabset.c
===================================================================
--- uspace/lib/ui/src/tabset.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/src/tabset.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,278 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Tab set
+ */
+
+#include <adt/list.h>
+#include <errno.h>
+#include <gfx/render.h>
+#include <io/pos_event.h>
+#include <stdlib.h>
+#include <ui/control.h>
+#include <ui/tab.h>
+#include <ui/tabset.h>
+#include <ui/window.h>
+#include "../private/tab.h"
+#include "../private/tabset.h"
+#include "../private/resource.h"
+
+static void ui_tab_set_ctl_destroy(void *);
+static errno_t ui_tab_set_ctl_paint(void *);
+static ui_evclaim_t ui_tab_set_ctl_kbd_event(void *, kbd_event_t *);
+static ui_evclaim_t ui_tab_set_ctl_pos_event(void *, pos_event_t *);
+
+/** Tab set control ops */
+ui_control_ops_t ui_tab_set_ops = {
+	.destroy = ui_tab_set_ctl_destroy,
+	.paint = ui_tab_set_ctl_paint,
+	.kbd_event = ui_tab_set_ctl_kbd_event,
+	.pos_event = ui_tab_set_ctl_pos_event
+};
+
+/** Create new tab set.
+ *
+ * @param res UI resource
+ * @param rtabset Place to store pointer to new tab set
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ui_tab_set_create(ui_resource_t *res, ui_tab_set_t **rtabset)
+{
+	ui_tab_set_t *tabset;
+	errno_t rc;
+
+	tabset = calloc(1, sizeof(ui_tab_set_t));
+	if (tabset == NULL)
+		return ENOMEM;
+
+	rc = ui_control_new(&ui_tab_set_ops, (void *) tabset, &tabset->control);
+	if (rc != EOK) {
+		free(tabset);
+		return rc;
+	}
+
+	tabset->res = res;
+	list_initialize(&tabset->tabs);
+	*rtabset = tabset;
+	return EOK;
+}
+
+/** Destroy tab set
+ *
+ * @param tabset Tab set or @c NULL
+ */
+void ui_tab_set_destroy(ui_tab_set_t *tabset)
+{
+	ui_tab_t *tab;
+
+	if (tabset == NULL)
+		return;
+
+	/* Destroy tabs */
+	tab = ui_tab_first(tabset);
+	while (tab != NULL) {
+		ui_tab_destroy(tab);
+		tab = ui_tab_first(tabset);
+	}
+
+	ui_control_delete(tabset->control);
+	free(tabset);
+}
+
+/** Get base control from tab set.
+ *
+ * @param tabset Tab set
+ * @return Control
+ */
+ui_control_t *ui_tab_set_ctl(ui_tab_set_t *tabset)
+{
+	return tabset->control;
+}
+
+/** Set tab set rectangle.
+ *
+ * @param tabset Tab set
+ * @param rect New tab set rectangle
+ */
+void ui_tab_set_set_rect(ui_tab_set_t *tabset, gfx_rect_t *rect)
+{
+	tabset->rect = *rect;
+}
+
+/** Paint tab set.
+ *
+ * @param tabset Tab set
+ * @return EOK on success or an error code
+ */
+errno_t ui_tab_set_paint(ui_tab_set_t *tabset)
+{
+	ui_resource_t *res;
+	ui_tab_t *tab;
+	errno_t rc;
+
+	res = tabset->res;
+
+	if (tabset->selected != NULL) {
+		rc = ui_tab_paint_body_frame(tabset->selected);
+		if (rc != EOK)
+			goto error;
+	}
+
+	tab = ui_tab_first(tabset);
+	while (tab != NULL) {
+		rc = ui_tab_paint(tab);
+		if (rc != EOK)
+			return rc;
+
+		tab = ui_tab_next(tab);
+	}
+
+	rc = gfx_update(res->gc);
+	if (rc != EOK)
+		goto error;
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Select or deselect tab from tab set.
+ *
+ * Select @a tab. If @a tab is @c NULL or it is already selected,
+ * then select none.
+ *
+ * @param tabset Tab set
+ * @param tab Tab to select (or deselect if selected) or @c NULL
+ */
+void ui_tab_set_select(ui_tab_set_t *tabset, ui_tab_t *tab)
+{
+	tabset->selected = tab;
+	(void) ui_tab_set_paint(tabset);
+}
+
+/** Handle tab set keyboard event.
+ *
+ * @param tabset Tab set
+ * @param kbd_event Keyboard event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t ui_tab_set_kbd_event(ui_tab_set_t *tabset, kbd_event_t *event)
+{
+	ui_tab_t *tab;
+	ui_evclaim_t claim;
+
+	tab = ui_tab_first(tabset);
+	while (tab != NULL) {
+		claim = ui_tab_kbd_event(tab, event);
+		if (claim == ui_claimed)
+			return ui_claimed;
+
+		tab = ui_tab_next(tab);
+	}
+
+	return ui_unclaimed;
+}
+
+/** Handle tab set position event.
+ *
+ * @param tabset Tab set
+ * @param pos_event Position event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t ui_tab_set_pos_event(ui_tab_set_t *tabset, pos_event_t *event)
+{
+	ui_tab_t *tab;
+	ui_evclaim_t claim;
+
+	tab = ui_tab_first(tabset);
+	while (tab != NULL) {
+		claim = ui_tab_pos_event(tab, event);
+		if (claim == ui_claimed)
+			return ui_claimed;
+
+		tab = ui_tab_next(tab);
+	}
+
+	return ui_unclaimed;
+}
+
+/** Destroy tab set control.
+ *
+ * @param arg Argument (ui_tab_set_t *)
+ */
+static void ui_tab_set_ctl_destroy(void *arg)
+{
+	ui_tab_set_t *tabset = (ui_tab_set_t *) arg;
+
+	ui_tab_set_destroy(tabset);
+}
+
+/** Paint tab set control.
+ *
+ * @param arg Argument (ui_tab_set_t *)
+ * @return EOK on success or an error code
+ */
+static errno_t ui_tab_set_ctl_paint(void *arg)
+{
+	ui_tab_set_t *tabset = (ui_tab_set_t *) arg;
+
+	return ui_tab_set_paint(tabset);
+}
+
+/** Handle tab set control keyboard event.
+ *
+ * @param arg Argument (ui_tab_set_t *)
+ * @param pos_event Position event
+ * @return @c ui_claimed iff the event is claimed
+ */
+static ui_evclaim_t ui_tab_set_ctl_kbd_event(void *arg, kbd_event_t *event)
+{
+	ui_tab_set_t *tabset = (ui_tab_set_t *) arg;
+
+	return ui_tab_set_kbd_event(tabset, event);
+}
+
+/** Handle tab set control position event.
+ *
+ * @param arg Argument (ui_tab_set_t *)
+ * @param pos_event Position event
+ * @return @c ui_claimed iff the event is claimed
+ */
+static ui_evclaim_t ui_tab_set_ctl_pos_event(void *arg, pos_event_t *event)
+{
+	ui_tab_set_t *tabset = (ui_tab_set_t *) arg;
+
+	return ui_tab_set_pos_event(tabset, event);
+}
+
+/** @}
+ */
Index: uspace/lib/ui/src/testctl.c
===================================================================
--- uspace/lib/ui/src/testctl.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/src/testctl.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,159 @@
+/*
+ * 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 libui
+ * @{
+ */
+/**
+ * @file Test control
+ *
+ * Test control allows to read the arguments of and inject the responses
+ * to all control methods.
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <str.h>
+#include <ui/control.h>
+#include <ui/testctl.h>
+#include "../private/testctl.h"
+
+static void test_ctl_destroy(void *);
+static errno_t test_ctl_paint(void *);
+static ui_evclaim_t test_ctl_kbd_event(void *, kbd_event_t *);
+static ui_evclaim_t test_ctl_pos_event(void *, pos_event_t *);
+static void test_ctl_unfocus(void *, unsigned);
+
+ui_control_ops_t ui_test_ctl_ops = {
+	.destroy = test_ctl_destroy,
+	.paint = test_ctl_paint,
+	.kbd_event = test_ctl_kbd_event,
+	.pos_event = test_ctl_pos_event,
+	.unfocus = test_ctl_unfocus
+};
+
+static void test_ctl_destroy(void *arg)
+{
+	ui_test_ctl_t *testctl = (ui_test_ctl_t *)arg;
+	ui_tc_resp_t *resp = testctl->resp;
+
+	resp->destroy = true;
+	ui_test_ctl_destroy(testctl);
+}
+
+static errno_t test_ctl_paint(void *arg)
+{
+	ui_test_ctl_t *testctl = (ui_test_ctl_t *)arg;
+	ui_tc_resp_t *resp = testctl->resp;
+
+	resp->paint = true;
+	return resp->rc;
+}
+
+static ui_evclaim_t test_ctl_kbd_event(void *arg, kbd_event_t *event)
+{
+	ui_test_ctl_t *testctl = (ui_test_ctl_t *)arg;
+	ui_tc_resp_t *resp = testctl->resp;
+
+	resp->kbd = true;
+	resp->kevent = *event;
+
+	return resp->claim;
+}
+
+static ui_evclaim_t test_ctl_pos_event(void *arg, pos_event_t *event)
+{
+	ui_test_ctl_t *testctl = (ui_test_ctl_t *)arg;
+	ui_tc_resp_t *resp = testctl->resp;
+
+	resp->pos = true;
+	resp->pevent = *event;
+
+	return resp->claim;
+}
+
+static void test_ctl_unfocus(void *arg, unsigned nfocus)
+{
+	ui_test_ctl_t *testctl = (ui_test_ctl_t *)arg;
+	ui_tc_resp_t *resp = testctl->resp;
+
+	resp->unfocus = true;
+	resp->unfocus_nfocus = nfocus;
+}
+
+/** Create new test control.
+ *
+ * @param resp Response structure
+ * @param rtest Place to store pointer to new test control
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ui_test_ctl_create(ui_tc_resp_t *resp, ui_test_ctl_t **rtest)
+{
+	ui_test_ctl_t *test;
+	errno_t rc;
+
+	test = calloc(1, sizeof(ui_test_ctl_t));
+	if (test == NULL)
+		return ENOMEM;
+
+	rc = ui_control_new(&ui_test_ctl_ops, (void *)test, &test->control);
+	if (rc != EOK) {
+		free(test);
+		return rc;
+	}
+
+	test->resp = resp;
+	*rtest = test;
+	return EOK;
+}
+
+/** Destroy test control.
+ *
+ * @param test Test control or @c NULL
+ */
+void ui_test_ctl_destroy(ui_test_ctl_t *test)
+{
+	if (test == NULL)
+		return;
+
+	ui_control_delete(test->control);
+	free(test);
+}
+
+/** Get base control from test control.
+ *
+ * @param test Test control
+ * @return Control
+ */
+ui_control_t *ui_test_ctl_ctl(ui_test_ctl_t *test)
+{
+	return test->control;
+}
+
+/** @}
+ */
Index: uspace/lib/ui/src/ui.c
===================================================================
--- uspace/lib/ui/src/ui.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/ui.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,5 +39,7 @@
 #include <errno.h>
 #include <fibril.h>
+#include <fibril_synch.h>
 #include <gfx/color.h>
+#include <gfx/cursor.h>
 #include <gfx/render.h>
 #include <io/console.h>
@@ -46,7 +48,10 @@
 #include <str.h>
 #include <task.h>
+#include <types/common.h>
+#include <ui/clickmatic.h>
 #include <ui/ui.h>
 #include <ui/wdecor.h>
 #include <ui/window.h>
+#include "../private/wdecor.h"
 #include "../private/window.h"
 #include "../private/ui.h"
@@ -61,15 +66,18 @@
  * @param ws Place to store window system type (protocol)
  * @param osvc Place to store pointer to output service name
- */
-static void ui_ospec_parse(const char *ospec, ui_winsys_t *ws,
-    const char **osvc)
+ * @param ridev_id Place to store input device ID
+ * @return EOK on success, EINVAL if syntax is invalid, ENOMEM if out of
+ *         memory
+ */
+static errno_t ui_ospec_parse(const char *ospec, ui_winsys_t *ws,
+    char **osvc, sysarg_t *ridev_id)
 {
 	const char *cp;
-
-	if (ospec == UI_DISPLAY_DEFAULT) {
-		*ws = ui_ws_display;
-		*osvc = DISPLAY_DEFAULT;
-		return;
-	}
+	const char *qm;
+	const char *endptr;
+	uint64_t idev_id;
+	errno_t rc;
+
+	*ridev_id = 0;
 
 	cp = ospec;
@@ -77,4 +85,5 @@
 		++cp;
 
+	/* Window system / protocol */
 	if (*cp == '@') {
 		if (str_lcmp(ospec, "disp@", str_length("disp@")) == 0) {
@@ -84,16 +93,54 @@
 		} else if (str_lcmp(ospec, "null@", str_length("null@")) == 0) {
 			*ws = ui_ws_null;
+		} else if (str_lcmp(ospec, "@", str_length("@")) == 0) {
+			*ws = ui_ws_any;
 		} else {
 			*ws = ui_ws_unknown;
 		}
 
-		if (cp[1] != '\0')
-			*osvc = cp + 1;
-		else
-			*osvc = NULL;
+		++cp;
 	} else {
 		*ws = ui_ws_display;
-		*osvc = ospec;
-	}
+	}
+
+	/* Output service is the part before question mark */
+	qm = str_chr(cp, '?');
+	if (qm != NULL) {
+		*osvc = str_ndup(cp, qm - cp);
+	} else {
+		/* No question mark */
+		*osvc = str_dup(cp);
+	}
+
+	if (*osvc == NULL)
+		return ENOMEM;
+
+	if (qm != NULL) {
+		/* The part after the question mark */
+		cp = qm + 1;
+
+		/* Input device ID parameter */
+		if (str_lcmp(cp, "idev=", str_length("idev=")) == 0) {
+			cp += str_length("idev=");
+
+			rc = str_uint64_t(cp, &endptr, 10, false, &idev_id);
+			if (rc != EOK)
+				goto error;
+
+			*ridev_id = idev_id;
+			cp = endptr;
+		}
+	}
+
+	if (*cp != '\0') {
+		rc = EINVAL;
+		goto error;
+	}
+
+	return EOK;
+error:
+	free(*osvc);
+	*osvc = NULL;
+	return rc;
 }
 
@@ -114,30 +161,43 @@
 	console_gc_t *cgc;
 	ui_winsys_t ws;
-	const char *osvc;
+	char *osvc;
 	sysarg_t cols;
 	sysarg_t rows;
+	sysarg_t idev_id;
 	ui_t *ui;
 
-	ui_ospec_parse(ospec, &ws, &osvc);
-
-	if (ws == ui_ws_display) {
-		rc = display_open(osvc, &display);
+	rc = ui_ospec_parse(ospec, &ws, &osvc, &idev_id);
+	if (rc != EOK)
+		return rc;
+
+	if (ws == ui_ws_display || ws == ui_ws_any) {
+		rc = display_open((str_cmp(osvc, "") != 0) ? osvc :
+		    DISPLAY_DEFAULT, &display);
 		if (rc != EOK)
-			return rc;
+			goto disp_fail;
 
 		rc = ui_create_disp(display, &ui);
 		if (rc != EOK) {
 			display_close(display);
-			return rc;
-		}
-	} else if (ws == ui_ws_console) {
+			goto disp_fail;
+		}
+
+		free(osvc);
+		ui->myoutput = true;
+		ui->idev_id = idev_id;
+		*rui = ui;
+		return EOK;
+	}
+
+disp_fail:
+	if (ws == ui_ws_console || ws == ui_ws_any) {
 		console = console_init(stdin, stdout);
 		if (console == NULL)
-			return EIO;
+			goto cons_fail;
 
 		rc = console_get_size(console, &cols, &rows);
 		if (rc != EOK) {
 			console_done(console);
-			return rc;
+			goto cons_fail;
 		}
 
@@ -148,5 +208,5 @@
 		if (rc != EOK) {
 			console_done(console);
-			return rc;
+			goto cons_fail;
 		}
 
@@ -155,6 +215,8 @@
 			ui_destroy(ui);
 			console_done(console);
-			return rc;
-		}
+			goto cons_fail;
+		}
+
+		free(osvc);
 
 		ui->cgc = cgc;
@@ -165,15 +227,23 @@
 
 		(void) ui_paint(ui);
-	} else if (ws == ui_ws_null) {
+		ui->myoutput = true;
+		*rui = ui;
+		return EOK;
+	}
+
+cons_fail:
+	if (ws == ui_ws_null) {
+		free(osvc);
 		rc = ui_create_disp(NULL, &ui);
 		if (rc != EOK)
 			return rc;
-	} else {
-		return EINVAL;
-	}
-
-	ui->myoutput = true;
-	*rui = ui;
-	return EOK;
+
+		ui->myoutput = true;
+		*rui = ui;
+		return EOK;
+	}
+
+	free(osvc);
+	return EINVAL;
 }
 
@@ -186,4 +256,5 @@
 {
 	ui_t *ui;
+	errno_t rc;
 
 	ui = calloc(1, sizeof(ui_t));
@@ -191,6 +262,13 @@
 		return ENOMEM;
 
+	rc = ui_clickmatic_create(ui, &ui->clickmatic);
+	if (rc != EOK) {
+		free(ui);
+		return rc;
+	}
+
 	ui->console = console;
 	list_initialize(&ui->windows);
+	fibril_mutex_initialize(&ui->lock);
 	*rui = ui;
 	return EOK;
@@ -206,4 +284,5 @@
 {
 	ui_t *ui;
+	errno_t rc;
 
 	ui = calloc(1, sizeof(ui_t));
@@ -211,6 +290,13 @@
 		return ENOMEM;
 
+	rc = ui_clickmatic_create(ui, &ui->clickmatic);
+	if (rc != EOK) {
+		free(ui);
+		return rc;
+	}
+
 	ui->display = disp;
 	list_initialize(&ui->windows);
+	fibril_mutex_initialize(&ui->lock);
 	*rui = ui;
 	return EOK;
@@ -252,5 +338,7 @@
 	switch (event->type) {
 	case CEV_KEY:
+		ui_lock(ui);
 		ui_window_send_kbd(awnd, &event->ev.key);
+		ui_unlock(ui);
 		break;
 	case CEV_POS:
@@ -262,6 +350,9 @@
 		claim = ui_wdecor_pos_event(awnd->wdecor, &pos);
 		/* Note: If event is claimed, awnd might not be valid anymore */
-		if (claim == ui_unclaimed)
+		if (claim == ui_unclaimed) {
+			ui_lock(ui);
 			ui_window_send_pos(awnd, &pos);
+			ui_unlock(ui);
+		}
 
 		break;
@@ -355,4 +446,137 @@
 }
 
+/** Free up console for other users.
+ *
+ * Release console resources for another application (that the current
+ * task is starting). After the other application finishes, resume
+ * operation with ui_resume(). No calls to UI must happen inbetween
+ * and no events must be processed (i.e. the calling function must not
+ * return control to UI.
+ *
+ * @param ui UI
+ * @return EOK on success or an error code
+ */
+errno_t ui_suspend(ui_t *ui)
+{
+	errno_t rc;
+
+	assert(!ui->suspended);
+
+	if (ui->cgc == NULL) {
+		ui->suspended = true;
+		return EOK;
+	}
+
+	(void) console_set_caption(ui->console, "");
+	rc = console_gc_suspend(ui->cgc);
+	if (rc != EOK)
+		return rc;
+
+	ui->suspended = true;
+	return EOK;
+}
+
+/** Resume suspended UI.
+ *
+ * Reclaim console resources (after child application has finished running)
+ * and restore UI operation previously suspended by calling ui_suspend().
+ *
+ * @param ui UI
+ * @return EOK on success or an error code
+ */
+errno_t ui_resume(ui_t *ui)
+{
+	errno_t rc;
+	ui_window_t *awnd;
+	sysarg_t col;
+	sysarg_t row;
+	cons_event_t ev;
+
+	assert(ui->suspended);
+
+	if (ui->cgc == NULL) {
+		ui->suspended = false;
+		return EOK;
+	}
+
+	rc = console_get_pos(ui->console, &col, &row);
+	if (rc != EOK)
+		return rc;
+
+	/*
+	 * Here's a little heuristic to help determine if we need
+	 * to pause before returning to the UI. If we are in the
+	 * top-left corner, chances are the screen is empty and
+	 * there is no need to pause.
+	 */
+	if (col != 0 || row != 0) {
+		printf("Press any key or button to continue...\n");
+
+		while (true) {
+			rc = console_get_event(ui->console, &ev);
+			if (rc != EOK)
+				return EIO;
+
+			if (ev.type == CEV_KEY && ev.ev.key.type == KEY_PRESS)
+				break;
+
+			if (ev.type == CEV_POS && ev.ev.pos.type == POS_PRESS)
+				break;
+		}
+	}
+
+	rc = console_gc_resume(ui->cgc);
+	if (rc != EOK)
+		return rc;
+
+	ui->suspended = false;
+
+	awnd = ui_window_get_active(ui);
+	if (awnd != NULL)
+		(void) console_set_caption(ui->console, awnd->wdecor->caption);
+
+	rc = gfx_cursor_set_visible(console_gc_get_ctx(ui->cgc), false);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Determine if UI is suspended.
+ *
+ * @param ui UI
+ * @return @c true iff UI is suspended
+ */
+bool ui_is_suspended(ui_t *ui)
+{
+	return ui->suspended;
+}
+
+/** Lock UI.
+ *
+ * Block UI from calling window callbacks. @c ui_lock() and @c ui_unlock()
+ * must be used when accessing UI resources from a fibril (as opposed to
+ * from a window callback).
+ *
+ * @param ui UI
+ */
+void ui_lock(ui_t *ui)
+{
+	fibril_mutex_lock(&ui->lock);
+}
+
+/** Unlock UI.
+ *
+ * Allow UI to call window callbacks. @c ui_lock() and @c ui_unlock()
+ * must be used when accessing window resources from a fibril (as opposed to
+ * from a window callback).
+ *
+ * @param ui UI
+ */
+void ui_unlock(ui_t *ui)
+{
+	fibril_mutex_unlock(&ui->lock);
+}
+
 /** Terminate user interface.
  *
@@ -392,4 +616,47 @@
 }
 
+/** Get UI screen rectangle.
+ *
+ * @param ui User interface
+ * @param rect Place to store bounding rectangle
+ */
+errno_t ui_get_rect(ui_t *ui, gfx_rect_t *rect)
+{
+	display_info_t info;
+	sysarg_t cols, rows;
+	errno_t rc;
+
+	if (ui->display != NULL) {
+		rc = display_get_info(ui->display, &info);
+		if (rc != EOK)
+			return rc;
+
+		*rect = info.rect;
+	} else if (ui->console != NULL) {
+		rc = console_get_size(ui->console, &cols, &rows);
+		if (rc != EOK)
+			return rc;
+
+		rect->p0.x = 0;
+		rect->p0.y = 0;
+		rect->p1.x = cols;
+		rect->p1.y = rows;
+	} else {
+		return ENOTSUP;
+	}
+
+	return EOK;
+}
+
+/** Get clickmatic from UI.
+ *
+ * @pararm ui UI
+ * @return Clickmatic
+ */
+ui_clickmatic_t *ui_get_clickmatic(ui_t *ui)
+{
+	return ui->clickmatic;
+}
+
 /** @}
  */
Index: uspace/lib/ui/src/wdecor.c
===================================================================
--- uspace/lib/ui/src/wdecor.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/wdecor.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -45,22 +45,98 @@
 #include <ui/paint.h>
 #include <ui/pbutton.h>
+#include <ui/ui.h>
 #include <ui/wdecor.h>
 #include "../private/resource.h"
 #include "../private/wdecor.h"
 
-static void ui_wdecor_btn_clicked(ui_pbutton_t *, void *);
+static void ui_wdecor_btn_min_clicked(ui_pbutton_t *, void *);
+static errno_t ui_wdecor_btn_min_paint(ui_pbutton_t *, void *,
+    gfx_coord2_t *);
+
+static void ui_wdecor_btn_max_clicked(ui_pbutton_t *, void *);
+static errno_t ui_wdecor_btn_max_paint(ui_pbutton_t *, void *,
+    gfx_coord2_t *);
+
+static void ui_wdecor_btn_close_clicked(ui_pbutton_t *, void *);
+static errno_t ui_wdecor_btn_close_paint(ui_pbutton_t *, void *,
+    gfx_coord2_t *);
+
+static ui_pbutton_cb_t ui_wdecor_btn_min_cb = {
+	.clicked = ui_wdecor_btn_min_clicked
+};
+
+static ui_pbutton_decor_ops_t ui_wdecor_btn_min_decor_ops = {
+	.paint = ui_wdecor_btn_min_paint
+};
+
+static ui_pbutton_cb_t ui_wdecor_btn_max_cb = {
+	.clicked = ui_wdecor_btn_max_clicked
+};
+
+static ui_pbutton_decor_ops_t ui_wdecor_btn_max_decor_ops = {
+	.paint = ui_wdecor_btn_max_paint
+};
 
 static ui_pbutton_cb_t ui_wdecor_btn_close_cb = {
-	.clicked = ui_wdecor_btn_clicked
+	.clicked = ui_wdecor_btn_close_clicked
 };
 
+static ui_pbutton_decor_ops_t ui_wdecor_btn_close_decor_ops = {
+	.paint = ui_wdecor_btn_close_paint
+};
+
 enum {
+	/** Width of corner drag area */
 	wdecor_corner_w = 24,
+	/** Height of corner drag area */
 	wdecor_corner_h = 24,
+	/** Window resizing edge witdth */
 	wdecor_edge_w = 4,
+	/** 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,
+	/** Window width */
 	wdecor_frame_w = 4,
-	wdecor_frame_w_text = 1
+	/** Window frame width in text mode */
+	wdecor_frame_w_text = 1,
+	/** Window caption horizontal margin */
+	wdecor_cap_hmargin = 4,
+	/** Window caption horizontal margin in text mode */
+	wdecor_cap_hmargin_text = 1,
+	/** System menu handle width */
+	wdecor_sysmenu_hdl_w = 20,
+	/** System menu handle height */
+	wdecor_sysmenu_hdl_h = 20,
+	/** System menu handle width in text mode */
+	wdecor_sysmenu_hdl_w_text = 3,
+	/** System menu handle height in text mode */
+	wdecor_sysmenu_hdl_h_text = 1,
+	/** Close button cross leg length */
+	wdecor_close_cross_n = 5,
+	/** Close button cross pen width */
+	wdecor_close_cross_w = 2,
+	/** Close button cross pen height */
+	wdecor_close_cross_h = 1,
+	/** Minimize icon width */
+	wdecor_min_w = 10,
+	/** Minimize icon height */
+	wdecor_min_h = 10,
+	/** Maximize icon width */
+	wdecor_max_w = 10,
+	/** Maximize icon height */
+	wdecor_max_h = 10,
+	/** Unmaximize icon window width */
+	wdecor_unmax_w = 8,
+	/** Unmaximize icon window height */
+	wdecor_unmax_h = 8,
+	/** Unmaximize icon window horizontal distance */
+	wdecor_unmax_dw = 4,
+	/** Unmaximize icon window vertical distance */
+	wdecor_unmax_dh = 4
 };
 
@@ -89,13 +165,45 @@
 	}
 
-	rc = ui_pbutton_create(resource, "X", &wdecor->btn_close);
-	if (rc != EOK) {
-		free(wdecor->caption);
-		free(wdecor);
-		return rc;
-	}
-
-	ui_pbutton_set_cb(wdecor->btn_close, &ui_wdecor_btn_close_cb,
-	    (void *)wdecor);
+	if ((style & ui_wds_minimize_btn) != 0) {
+		rc = ui_pbutton_create(resource, "_", &wdecor->btn_min);
+		if (rc != EOK) {
+			ui_wdecor_destroy(wdecor);
+			return rc;
+		}
+
+		ui_pbutton_set_cb(wdecor->btn_min, &ui_wdecor_btn_min_cb,
+		    (void *)wdecor);
+
+		ui_pbutton_set_decor_ops(wdecor->btn_min,
+		    &ui_wdecor_btn_min_decor_ops, (void *)wdecor);
+	}
+
+	if ((style & ui_wds_maximize_btn) != 0) {
+		rc = ui_pbutton_create(resource, "^", &wdecor->btn_max);
+		if (rc != EOK) {
+			ui_wdecor_destroy(wdecor);
+			return rc;
+		}
+
+		ui_pbutton_set_cb(wdecor->btn_max, &ui_wdecor_btn_max_cb,
+		    (void *)wdecor);
+
+		ui_pbutton_set_decor_ops(wdecor->btn_max,
+		    &ui_wdecor_btn_max_decor_ops, (void *)wdecor);
+	}
+
+	if ((style & ui_wds_close_btn) != 0) {
+		rc = ui_pbutton_create(resource, "X", &wdecor->btn_close);
+		if (rc != EOK) {
+			ui_wdecor_destroy(wdecor);
+			return rc;
+		}
+
+		ui_pbutton_set_cb(wdecor->btn_close, &ui_wdecor_btn_close_cb,
+		    (void *)wdecor);
+
+		ui_pbutton_set_decor_ops(wdecor->btn_close,
+		    &ui_wdecor_btn_close_decor_ops, (void *)wdecor);
+	}
 
 	wdecor->res = resource;
@@ -115,4 +223,6 @@
 		return;
 
+	ui_pbutton_destroy(wdecor->btn_min);
+	ui_pbutton_destroy(wdecor->btn_max);
 	ui_pbutton_destroy(wdecor->btn_close);
 	free(wdecor->caption);
@@ -144,5 +254,11 @@
 
 	ui_wdecor_get_geom(wdecor, &geom);
-	ui_pbutton_set_rect(wdecor->btn_close, &geom.btn_close_rect);
+
+	if (wdecor->btn_min != NULL)
+		ui_pbutton_set_rect(wdecor->btn_min, &geom.btn_min_rect);
+	if (wdecor->btn_max != NULL)
+		ui_pbutton_set_rect(wdecor->btn_max, &geom.btn_max_rect);
+	if (wdecor->btn_close != NULL)
+		ui_pbutton_set_rect(wdecor->btn_close, &geom.btn_close_rect);
 }
 
@@ -157,4 +273,148 @@
 {
 	wdecor->active = active;
+}
+
+/** Set maximized flag.
+ *
+ * Active window is the one receiving keyboard events.
+ *
+ * @param wdecor Window decoration
+ * @param maximized @c true iff window is maximized
+ */
+void ui_wdecor_set_maximized(ui_wdecor_t *wdecor, bool maximized)
+{
+	wdecor->maximized = maximized;
+}
+
+/** Change caption.
+ *
+ * @param wdecor Window decoration
+ * @param caption New caption
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ui_wdecor_set_caption(ui_wdecor_t *wdecor, const char *caption)
+{
+	char *cdup;
+
+	cdup = str_dup(caption);
+	if (cdup == NULL)
+		return ENOMEM;
+
+	free(wdecor->caption);
+	wdecor->caption = cdup;
+
+	ui_wdecor_paint(wdecor);
+	return EOK;
+}
+
+/** Paint system menu handle in graphics mode.
+ *
+ * @param wdecor Window decoration
+ * @param rect System menu handle rectangle
+ * @return EOK on success or an error code
+ */
+errno_t ui_wdecor_sysmenu_hdl_paint_gfx(ui_wdecor_t *wdecor, gfx_rect_t *rect)
+{
+	errno_t rc;
+	gfx_rect_t mrect;
+	gfx_rect_t inside;
+	gfx_coord2_t center;
+
+	rc = gfx_set_color(wdecor->res->gc, wdecor->sysmenu_hdl_active ?
+	    wdecor->res->btn_shadow_color : wdecor->res->btn_face_color);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_fill_rect(wdecor->res->gc, rect);
+	if (rc != EOK)
+		return rc;
+
+	center.x = (rect->p0.x + rect->p1.x) / 2;
+	center.y = (rect->p0.y + rect->p1.y) / 2;
+	mrect.p0.x = center.x - 7;
+	mrect.p0.y = center.y - 1;
+	mrect.p1.x = center.x + 7;
+	mrect.p1.y = center.y + 1 + 1;
+
+	/* XXX Not really a bevel, just a frame */
+	rc = ui_paint_bevel(wdecor->res->gc, &mrect,
+	    wdecor->res->btn_text_color, wdecor->res->btn_text_color, 1,
+	    &inside);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_set_color(wdecor->res->gc, wdecor->res->btn_highlight_color);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_fill_rect(wdecor->res->gc, &inside);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Paint system menu handle in text mode.
+ *
+ * @param wdecor Window decoration
+ * @param rect System menu handle rectangle
+ * @return EOK on success or an error code
+ */
+errno_t ui_wdecor_sysmenu_hdl_paint_text(ui_wdecor_t *wdecor, gfx_rect_t *rect)
+{
+	errno_t rc;
+	gfx_text_fmt_t fmt;
+
+	rc = gfx_set_color(wdecor->res->gc, wdecor->sysmenu_hdl_active ?
+	    wdecor->res->btn_shadow_color : wdecor->res->btn_face_color);
+
+	gfx_text_fmt_init(&fmt);
+	fmt.font = wdecor->res->font;
+	fmt.color = wdecor->sysmenu_hdl_active ?
+	    wdecor->res->wnd_sel_text_color :
+	    wdecor->res->tbar_act_text_color;
+	fmt.halign = gfx_halign_left;
+	fmt.valign = gfx_valign_top;
+
+	rc = gfx_puttext(&rect->p0, &fmt, "[\u2261]");
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Paint system menu handle.
+ *
+ * @param wdecor Window decoration
+ * @param rect System menu handle rectangle
+ * @return EOK on success or an error code
+ */
+errno_t ui_wdecor_sysmenu_hdl_paint(ui_wdecor_t *wdecor, gfx_rect_t *rect)
+{
+	errno_t rc;
+
+	if (wdecor->res->textmode)
+		rc = ui_wdecor_sysmenu_hdl_paint_text(wdecor, rect);
+	else
+		rc = ui_wdecor_sysmenu_hdl_paint_gfx(wdecor, rect);
+
+	return rc;
+}
+
+/** Set system menu handle active flag.
+ *
+ * @param wdecor Window decoration
+ * @param active @c true iff handle should be active
+ */
+void ui_wdecor_sysmenu_hdl_set_active(ui_wdecor_t *wdecor, bool active)
+{
+	ui_wdecor_geom_t geom;
+
+	wdecor->sysmenu_hdl_active = active;
+
+	ui_wdecor_get_geom(wdecor, &geom);
+	(void) ui_wdecor_sysmenu_hdl_paint(wdecor, &geom.sysmenu_hdl_rect);
+	(void) gfx_update(wdecor->res->gc);
 }
 
@@ -220,4 +480,5 @@
 
 		gfx_text_fmt_init(&fmt);
+		fmt.font = wdecor->res->font;
 		fmt.color = wdecor->active ?
 		    wdecor->res->tbar_act_text_color :
@@ -225,12 +486,14 @@
 		fmt.halign = gfx_halign_center;
 		fmt.valign = gfx_valign_center;
-
-		pos.x = (trect.p0.x + trect.p1.x) / 2;
-		pos.y = (trect.p0.y + trect.p1.y) / 2;
+		fmt.abbreviate = true;
+		fmt.width = geom.caption_rect.p1.x -
+		    geom.caption_rect.p0.x;
+
+		pos.x = (geom.caption_rect.p0.x + geom.caption_rect.p1.x) / 2;
+		pos.y = (geom.caption_rect.p0.y + geom.caption_rect.p1.y) / 2;
 
 		if (wdecor->res->textmode) {
 			/* Make space around caption text */
-			gfx_text_rect(wdecor->res->font, &pos, &fmt,
-			    wdecor->caption, &text_rect);
+			gfx_text_rect(&pos, &fmt, wdecor->caption, &text_rect);
 
 			/* Only make space if caption is non-empty */
@@ -251,9 +514,28 @@
 		}
 
-		rc = gfx_puttext(wdecor->res->font, &pos, &fmt, wdecor->caption);
+		rc = gfx_puttext(&pos, &fmt, wdecor->caption);
 		if (rc != EOK)
 			return rc;
 
-		if ((wdecor->style & ui_wds_close_btn) != 0) {
+		if ((wdecor->style & ui_wds_sysmenu_hdl) != 0) {
+			rc = ui_wdecor_sysmenu_hdl_paint(wdecor,
+			    &geom.sysmenu_hdl_rect);
+			if (rc != EOK)
+				return rc;
+		}
+
+		if (wdecor->btn_min != NULL) {
+			rc = ui_pbutton_paint(wdecor->btn_min);
+			if (rc != EOK)
+				return rc;
+		}
+
+		if (wdecor->btn_max != NULL) {
+			rc = ui_pbutton_paint(wdecor->btn_max);
+			if (rc != EOK)
+				return rc;
+		}
+
+		if (wdecor->btn_close != NULL) {
 			rc = ui_pbutton_paint(wdecor->btn_close);
 			if (rc != EOK)
@@ -269,4 +551,79 @@
 }
 
+/** Send decoration sysmenu open event.
+ *
+ * @param wdecor Window decoration
+ * @param idev_id Input device ID
+ */
+void ui_wdecor_sysmenu_open(ui_wdecor_t *wdecor, sysarg_t idev_id)
+{
+	if (wdecor->cb != NULL && wdecor->cb->sysmenu_open != NULL)
+		wdecor->cb->sysmenu_open(wdecor, wdecor->arg, idev_id);
+}
+
+/** Send decoration sysmenu left event.
+ *
+ * @param wdecor Window decoration
+ * @param idev_id Input device ID
+ */
+void ui_wdecor_sysmenu_left(ui_wdecor_t *wdecor, sysarg_t idev_id)
+{
+	if (wdecor->cb != NULL && wdecor->cb->sysmenu_left != NULL)
+		wdecor->cb->sysmenu_left(wdecor, wdecor->arg, idev_id);
+}
+
+/** Send decoration sysmenu right event.
+ *
+ * @param wdecor Window decoration
+ * @param idev_id Input device ID
+ */
+void ui_wdecor_sysmenu_right(ui_wdecor_t *wdecor, sysarg_t idev_id)
+{
+	if (wdecor->cb != NULL && wdecor->cb->sysmenu_right != NULL)
+		wdecor->cb->sysmenu_right(wdecor, wdecor->arg, idev_id);
+}
+
+/** Send decoration sysmenu accelerator event.
+ *
+ * @param wdecor Window decoration
+ * @param c Accelerator character
+ * @param idev_id Input device ID
+ */
+void ui_wdecor_sysmenu_accel(ui_wdecor_t *wdecor, char32_t c, sysarg_t idev_id)
+{
+	if (wdecor->cb != NULL && wdecor->cb->sysmenu_right != NULL)
+		wdecor->cb->sysmenu_accel(wdecor, wdecor->arg, c, idev_id);
+}
+
+/** Send decoration minimize event.
+ *
+ * @param wdecor Window decoration
+ */
+void ui_wdecor_minimize(ui_wdecor_t *wdecor)
+{
+	if (wdecor->cb != NULL && wdecor->cb->minimize != NULL)
+		wdecor->cb->minimize(wdecor, wdecor->arg);
+}
+
+/** Send decoration maximize event.
+ *
+ * @param wdecor Window decoration
+ */
+void ui_wdecor_maximize(ui_wdecor_t *wdecor)
+{
+	if (wdecor->cb != NULL && wdecor->cb->maximize != NULL)
+		wdecor->cb->maximize(wdecor, wdecor->arg);
+}
+
+/** Send decoration unmaximize event.
+ *
+ * @param wdecor Window decoration
+ */
+void ui_wdecor_unmaximize(ui_wdecor_t *wdecor)
+{
+	if (wdecor->cb != NULL && wdecor->cb->unmaximize != NULL)
+		wdecor->cb->unmaximize(wdecor, wdecor->arg);
+}
+
 /** Send decoration close event.
  *
@@ -283,9 +640,10 @@
  * @param wdecor Window decoration
  * @param pos Position where the title bar was pressed
- */
-void ui_wdecor_move(ui_wdecor_t *wdecor, gfx_coord2_t *pos)
+ * @param pos_id Positioning device ID
+ */
+void ui_wdecor_move(ui_wdecor_t *wdecor, gfx_coord2_t *pos, sysarg_t pos_id)
 {
 	if (wdecor->cb != NULL && wdecor->cb->move != NULL)
-		wdecor->cb->move(wdecor, wdecor->arg, pos);
+		wdecor->cb->move(wdecor, wdecor->arg, pos, pos_id);
 }
 
@@ -295,10 +653,11 @@
  * @param rsztype Resize type
  * @param pos Position where the button was pressed
+ * @param pos_id Positioning device ID
  */
 void ui_wdecor_resize(ui_wdecor_t *wdecor, ui_wdecor_rsztype_t rsztype,
-    gfx_coord2_t *pos)
+    gfx_coord2_t *pos, sysarg_t pos_id)
 {
 	if (wdecor->cb != NULL && wdecor->cb->resize != NULL)
-		wdecor->cb->resize(wdecor, wdecor->arg, rsztype, pos);
+		wdecor->cb->resize(wdecor, wdecor->arg, rsztype, pos, pos_id);
 }
 
@@ -322,4 +681,12 @@
 {
 	gfx_coord_t frame_w;
+	gfx_coord_t btn_x;
+	gfx_coord_t btn_y;
+	gfx_coord_t cap_hmargin;
+	gfx_coord_t cap_x;
+	gfx_coord_t hdl_x_off;
+	gfx_coord_t hdl_y_off;
+	gfx_coord_t hdl_w;
+	gfx_coord_t hdl_h;
 
 	/* Does window have a frame? */
@@ -342,4 +709,7 @@
 			geom->title_bar_rect.p1.x = wdecor->rect.p1.x;
 			geom->title_bar_rect.p1.y = wdecor->rect.p0.y + 1;
+
+			btn_x = geom->title_bar_rect.p1.x - 1;
+			btn_y = geom->title_bar_rect.p0.y;
 		} else {
 			geom->title_bar_rect.p0 = geom->interior_rect.p0;
@@ -347,4 +717,7 @@
 			geom->title_bar_rect.p1.y = geom->interior_rect.p0.y +
 			    wdecor_tbar_h;
+
+			btn_x = geom->title_bar_rect.p1.x - 1;
+			btn_y = geom->title_bar_rect.p0.y + 1;
 		}
 
@@ -352,4 +725,5 @@
 		geom->app_area_rect.p0.y = geom->title_bar_rect.p1.y;
 		geom->app_area_rect.p1 = geom->interior_rect.p1;
+
 	} else {
 		geom->title_bar_rect.p0.x = 0;
@@ -359,4 +733,37 @@
 
 		geom->app_area_rect = geom->interior_rect;
+		btn_x = 0;
+		btn_y = 0;
+	}
+
+	/* Does window have a sysmenu handle? */
+	if ((wdecor->style & ui_wds_sysmenu_hdl) != 0) {
+		if (wdecor->res->textmode) {
+			hdl_x_off = 2;
+			hdl_y_off = 0;
+			hdl_w = wdecor_sysmenu_hdl_w_text;
+			hdl_h = wdecor_sysmenu_hdl_h_text;
+		} else {
+			hdl_x_off = 1;
+			hdl_y_off = 1;
+			hdl_w = wdecor_sysmenu_hdl_w;
+			hdl_h = wdecor_sysmenu_hdl_h;
+		}
+
+		geom->sysmenu_hdl_rect.p0.x = geom->title_bar_rect.p0.x +
+		    hdl_x_off;
+		geom->sysmenu_hdl_rect.p0.y = geom->title_bar_rect.p0.y +
+		    hdl_y_off;
+		geom->sysmenu_hdl_rect.p1.x = geom->sysmenu_hdl_rect.p0.x +
+		    hdl_w;
+		geom->sysmenu_hdl_rect.p1.y = geom->sysmenu_hdl_rect.p0.y +
+		    hdl_h;
+		cap_x = hdl_w;
+	} else {
+		geom->sysmenu_hdl_rect.p0.x = 0;
+		geom->sysmenu_hdl_rect.p0.y = 0;
+		geom->sysmenu_hdl_rect.p1.x = 0;
+		geom->sysmenu_hdl_rect.p1.y = 0;
+		cap_x = 0;
 	}
 
@@ -364,21 +771,17 @@
 	if ((wdecor->style & ui_wds_close_btn) != 0) {
 		if (wdecor->res->textmode == false) {
-			geom->btn_close_rect.p0.x =
-			    geom->title_bar_rect.p1.x - 1 - 20;
-			geom->btn_close_rect.p0.y =
-			    geom->title_bar_rect.p0.y + 1;
-			geom->btn_close_rect.p1.x =
-			    geom->title_bar_rect.p1.x - 1;
-			geom->btn_close_rect.p1.y =
-			    geom->title_bar_rect.p0.y + 1 + 20;
+			geom->btn_close_rect.p0.x = btn_x - 20;
+			geom->btn_close_rect.p0.y = btn_y;
+			geom->btn_close_rect.p1.x = btn_x;
+			geom->btn_close_rect.p1.y = btn_y + 20;
+
+			btn_x -= 20;
 		} else {
-			geom->btn_close_rect.p0.x =
-			    geom->title_bar_rect.p1.x - 1 - 3;
-			geom->btn_close_rect.p0.y =
-			    geom->title_bar_rect.p0.y;
-			geom->btn_close_rect.p1.x =
-			    geom->title_bar_rect.p1.x - 1;
-			geom->btn_close_rect.p1.y =
-			    geom->title_bar_rect.p0.y + 1;
+			geom->btn_close_rect.p0.x = btn_x - 3;
+			geom->btn_close_rect.p0.y = btn_y;
+			geom->btn_close_rect.p1.x = btn_x;
+			geom->btn_close_rect.p1.y = btn_y + 1;
+
+			btn_x -= 3;
 		}
 	} else {
@@ -388,4 +791,70 @@
 		geom->btn_close_rect.p1.y = 0;
 	}
+
+	/* Does window have a (un)maximize button? */
+	if ((wdecor->style & ui_wds_maximize_btn) != 0) {
+		if (wdecor->res->textmode == false) {
+			geom->btn_max_rect.p0.x = btn_x - 20;
+			geom->btn_max_rect.p0.y = btn_y;
+			geom->btn_max_rect.p1.x = btn_x;
+			geom->btn_max_rect.p1.y = btn_y + 20;
+
+			btn_x -= 20;
+		} else {
+			geom->btn_max_rect.p0.x = btn_x - 3;
+			geom->btn_max_rect.p0.y = btn_y;
+			geom->btn_max_rect.p1.x = btn_x;
+			geom->btn_max_rect.p1.y = btn_y + 1;
+
+			btn_x -= 3;
+		}
+	} else {
+		geom->btn_max_rect.p0.x = 0;
+		geom->btn_max_rect.p0.y = 0;
+		geom->btn_max_rect.p1.x = 0;
+		geom->btn_max_rect.p1.y = 0;
+	}
+
+	/* Does window have a minimize button? */
+	if ((wdecor->style & ui_wds_minimize_btn) != 0) {
+		if (wdecor->res->textmode == false) {
+			geom->btn_min_rect.p0.x = btn_x - 20;
+			geom->btn_min_rect.p0.y = btn_y;
+			geom->btn_min_rect.p1.x = btn_x;
+			geom->btn_min_rect.p1.y = btn_y + 20;
+
+			btn_x -= 20;
+		} else {
+			geom->btn_min_rect.p0.x = btn_x - 3;
+			geom->btn_min_rect.p0.y = btn_y;
+			geom->btn_min_rect.p1.x = btn_x;
+			geom->btn_min_rect.p1.y = btn_y + 1;
+
+			btn_x -= 3;
+		}
+	} else {
+		geom->btn_min_rect.p0.x = 0;
+		geom->btn_min_rect.p0.y = 0;
+		geom->btn_min_rect.p1.x = 0;
+		geom->btn_min_rect.p1.y = 0;
+	}
+
+	if ((wdecor->style & ui_wds_titlebar) != 0) {
+		if (wdecor->res->textmode == false)
+			cap_hmargin = wdecor_cap_hmargin;
+		else
+			cap_hmargin = wdecor_cap_hmargin_text;
+
+		geom->caption_rect.p0.x = geom->title_bar_rect.p0.x +
+		    cap_hmargin + cap_x;
+		geom->caption_rect.p0.y = geom->title_bar_rect.p0.y;
+		geom->caption_rect.p1.x = btn_x - cap_hmargin;
+		geom->caption_rect.p1.y = geom->title_bar_rect.p1.y;
+	} else {
+		geom->caption_rect.p0.x = 0;
+		geom->caption_rect.p0.y = 0;
+		geom->caption_rect.p1.x = 0;
+		geom->caption_rect.p1.y = 0;
+	}
 }
 
@@ -396,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;
 }
 
@@ -461,4 +942,8 @@
 		return ui_wr_none;
 
+	/* Window is maximized? */
+	if (wdecor->maximized)
+		return ui_wr_none;
+
 	/* Position not inside window? */
 	if (!gfx_pix_inside_rect(pos, &wdecor->rect))
@@ -550,4 +1035,51 @@
 }
 
+/** Handle window decoration keyboard event.
+ *
+ * @param wdecor Window decoration
+ * @param kbd_event Keyboard event
+ * @return @c ui_claimed iff event was claimed
+ */
+ui_evclaim_t ui_wdecor_kbd_event(ui_wdecor_t *wdecor, kbd_event_t *event)
+{
+	if (event->type == KEY_PRESS && (event->mods & (KM_CTRL | KM_ALT |
+	    KM_SHIFT)) == 0) {
+		if (event->key == KC_F10) {
+			ui_wdecor_sysmenu_hdl_set_active(wdecor, true);
+			ui_wdecor_sysmenu_open(wdecor, event->kbd_id);
+			return ui_claimed;
+		}
+	}
+
+	/* System menu handle events (if active) */
+	if (event->type == KEY_PRESS && (event->mods & (KM_CTRL | KM_ALT |
+	    KM_SHIFT)) == 0 && wdecor->sysmenu_hdl_active) {
+		switch (event->key) {
+		case KC_ESCAPE:
+			ui_wdecor_sysmenu_hdl_set_active(wdecor, false);
+			return ui_claimed;
+		case KC_LEFT:
+			ui_wdecor_sysmenu_left(wdecor, event->kbd_id);
+			return ui_claimed;
+		case KC_RIGHT:
+			ui_wdecor_sysmenu_right(wdecor, event->kbd_id);
+			return ui_claimed;
+		case KC_DOWN:
+			ui_wdecor_sysmenu_open(wdecor, event->kbd_id);
+			return ui_claimed;
+		default:
+			break;
+		}
+
+		if (event->c != '\0') {
+			/* Could be an accelerator key */
+			ui_wdecor_sysmenu_accel(wdecor, event->c,
+			    event->kbd_id);
+		}
+	}
+
+	return ui_unclaimed;
+}
+
 /** Handle window frame position event.
  *
@@ -558,4 +1090,5 @@
 {
 	gfx_coord2_t pos;
+	sysarg_t pos_id;
 	ui_wdecor_rsztype_t rsztype;
 	ui_stock_cursor_t cursor;
@@ -563,4 +1096,5 @@
 	pos.x = event->hpos;
 	pos.y = event->vpos;
+	pos_id = event->pos_id;
 
 	/* Set appropriate resizing cursor, or set arrow cursor */
@@ -573,5 +1107,5 @@
 	/* Press on window border? */
 	if (rsztype != ui_wr_none && event->type == POS_PRESS)
-		ui_wdecor_resize(wdecor, rsztype, &pos);
+		ui_wdecor_resize(wdecor, rsztype, &pos, pos_id);
 }
 
@@ -585,4 +1119,5 @@
 {
 	gfx_coord2_t pos;
+	sysarg_t pos_id;
 	ui_wdecor_geom_t geom;
 	ui_evclaim_t claim;
@@ -590,8 +1125,31 @@
 	pos.x = event->hpos;
 	pos.y = event->vpos;
+	pos_id = event->pos_id;
 
 	ui_wdecor_get_geom(wdecor, &geom);
 
-	if ((wdecor->style & ui_wds_close_btn) != 0) {
+	if ((wdecor->style & ui_wds_titlebar) != 0 &&
+	    (wdecor->style & ui_wds_sysmenu_hdl) != 0) {
+		if (event->type == POS_PRESS &&
+		    gfx_pix_inside_rect(&pos, &geom.sysmenu_hdl_rect)) {
+			ui_wdecor_sysmenu_hdl_set_active(wdecor, true);
+			ui_wdecor_sysmenu_open(wdecor, event->pos_id);
+			return ui_claimed;
+		}
+	}
+
+	if (wdecor->btn_min != NULL) {
+		claim = ui_pbutton_pos_event(wdecor->btn_min, event);
+		if (claim == ui_claimed)
+			return ui_claimed;
+	}
+
+	if (wdecor->btn_max != NULL) {
+		claim = ui_pbutton_pos_event(wdecor->btn_max, event);
+		if (claim == ui_claimed)
+			return ui_claimed;
+	}
+
+	if (wdecor->btn_close != NULL) {
 		claim = ui_pbutton_pos_event(wdecor->btn_close, event);
 		if (claim == ui_claimed)
@@ -601,8 +1159,8 @@
 	ui_wdecor_frame_pos_event(wdecor, event);
 
-	if ((wdecor->style & ui_wds_titlebar) != 0) {
+	if ((wdecor->style & ui_wds_titlebar) != 0 && !wdecor->maximized) {
 		if (event->type == POS_PRESS &&
 		    gfx_pix_inside_rect(&pos, &geom.title_bar_rect)) {
-			ui_wdecor_move(wdecor, &pos);
+			ui_wdecor_move(wdecor, &pos, pos_id);
 			return ui_claimed;
 		}
@@ -610,4 +1168,76 @@
 
 	return ui_unclaimed;
+}
+
+/** Window decoration minimize button was clicked.
+ *
+ * @param pbutton Minimize button
+ * @param arg Argument (ui_wdecor_t)
+ */
+static void ui_wdecor_btn_min_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	ui_wdecor_t *wdecor = (ui_wdecor_t *) arg;
+
+	(void) pbutton;
+
+	ui_wdecor_minimize(wdecor);
+}
+
+/** Window decoration (un)maximize button was clicked.
+ *
+ * @param pbutton (Un)maximize button
+ * @param arg Argument (ui_wdecor_t)
+ */
+static void ui_wdecor_btn_max_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	ui_wdecor_t *wdecor = (ui_wdecor_t *) arg;
+
+	(void) pbutton;
+
+	if (wdecor->maximized)
+		ui_wdecor_unmaximize(wdecor);
+	else
+		ui_wdecor_maximize(wdecor);
+}
+
+/** Paint minimize button decoration.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (ui_wdecor_t *)
+ * @param pos Center position
+ */
+static errno_t ui_wdecor_btn_min_paint(ui_pbutton_t *pbutton,
+    void *arg, gfx_coord2_t *pos)
+{
+	ui_wdecor_t *wdecor = (ui_wdecor_t *)arg;
+	errno_t rc;
+
+	rc = ui_paint_minicon(wdecor->res, pos, wdecor_min_w,
+	    wdecor_min_h);
+
+	return rc;
+}
+
+/** Paint (un)maximize button decoration.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (ui_wdecor_t *)
+ * @param pos Center position
+ */
+static errno_t ui_wdecor_btn_max_paint(ui_pbutton_t *pbutton,
+    void *arg, gfx_coord2_t *pos)
+{
+	ui_wdecor_t *wdecor = (ui_wdecor_t *)arg;
+	errno_t rc;
+
+	if (wdecor->maximized) {
+		rc = ui_paint_unmaxicon(wdecor->res, pos, wdecor_unmax_w,
+		    wdecor_unmax_h, wdecor_unmax_dw, wdecor_unmax_dh);
+	} else {
+		rc = ui_paint_maxicon(wdecor->res, pos, wdecor_max_w,
+		    wdecor_max_h);
+	}
+
+	return rc;
 }
 
@@ -617,5 +1247,5 @@
  * @param arg Argument (ui_wdecor_t)
  */
-static void ui_wdecor_btn_clicked(ui_pbutton_t *pbutton, void *arg)
+static void ui_wdecor_btn_close_clicked(ui_pbutton_t *pbutton, void *arg)
 {
 	ui_wdecor_t *wdecor = (ui_wdecor_t *) arg;
@@ -625,4 +1255,27 @@
 }
 
+/** Paint close button decoration.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (ui_wdecor_t *)
+ * @param pos Center position
+ */
+static errno_t ui_wdecor_btn_close_paint(ui_pbutton_t *pbutton,
+    void *arg, gfx_coord2_t *pos)
+{
+	ui_wdecor_t *wdecor = (ui_wdecor_t *)arg;
+	gfx_coord2_t p;
+	errno_t rc;
+
+	rc = gfx_set_color(wdecor->res->gc, wdecor->res->btn_text_color);
+	if (rc != EOK)
+		return rc;
+
+	p.x = pos->x - 1;
+	p.y = pos->y - 1;
+	return ui_paint_cross(wdecor->res->gc, &p, wdecor_close_cross_n,
+	    wdecor_close_cross_w, wdecor_close_cross_h);
+}
+
 /** @}
  */
Index: uspace/lib/ui/src/window.c
===================================================================
--- uspace/lib/ui/src/window.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/src/window.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -47,4 +47,7 @@
 #include <stdlib.h>
 #include <ui/control.h>
+#include <ui/menubar.h>
+#include <ui/menu.h>
+#include <ui/menuentry.h>
 #include <ui/resource.h>
 #include <ui/ui.h>
@@ -59,9 +62,9 @@
 
 static void dwnd_close_event(void *);
-static void dwnd_focus_event(void *);
+static void dwnd_focus_event(void *, unsigned);
 static void dwnd_kbd_event(void *, kbd_event_t *);
 static void dwnd_pos_event(void *, pos_event_t *);
 static void dwnd_resize_event(void *, gfx_rect_t *);
-static void dwnd_unfocus_event(void *);
+static void dwnd_unfocus_event(void *, unsigned);
 
 static display_wnd_cb_t dwnd_cb = {
@@ -74,11 +77,25 @@
 };
 
+static void wd_sysmenu_open(ui_wdecor_t *, void *, sysarg_t);
+static void wd_sysmenu_left(ui_wdecor_t *, void *, sysarg_t);
+static void wd_sysmenu_right(ui_wdecor_t *, void *, sysarg_t);
+static void wd_sysmenu_accel(ui_wdecor_t *, void *, char32_t, sysarg_t);
+static void wd_minimize(ui_wdecor_t *, void *);
+static void wd_maximize(ui_wdecor_t *, void *);
+static void wd_unmaximize(ui_wdecor_t *, void *);
 static void wd_close(ui_wdecor_t *, void *);
-static void wd_move(ui_wdecor_t *, void *, gfx_coord2_t *);
+static void wd_move(ui_wdecor_t *, void *, gfx_coord2_t *, sysarg_t);
 static void wd_resize(ui_wdecor_t *, void *, ui_wdecor_rsztype_t,
-    gfx_coord2_t *);
+    gfx_coord2_t *, sysarg_t);
 static void wd_set_cursor(ui_wdecor_t *, void *, ui_stock_cursor_t);
 
 static ui_wdecor_cb_t wdecor_cb = {
+	.sysmenu_open = wd_sysmenu_open,
+	.sysmenu_left = wd_sysmenu_left,
+	.sysmenu_right = wd_sysmenu_right,
+	.sysmenu_accel = wd_sysmenu_accel,
+	.minimize = wd_minimize,
+	.maximize = wd_maximize,
+	.unmaximize = wd_unmaximize,
 	.close = wd_close,
 	.move = wd_move,
@@ -86,4 +103,21 @@
 	.set_cursor = wd_set_cursor
 };
+
+static void wnd_sysmenu_left(ui_menu_t *, void *, sysarg_t);
+static void wnd_sysmenu_right(ui_menu_t *, void *, sysarg_t);
+static void wnd_sysmenu_close_req(ui_menu_t *, void *);
+static void wnd_sysmenu_press_accel(ui_menu_t *, void *, char32_t, sysarg_t);
+
+static ui_menu_cb_t wnd_sysmenu_cb = {
+	.left = wnd_sysmenu_left,
+	.right = wnd_sysmenu_right,
+	.close_req = wnd_sysmenu_close_req,
+	.press_accel = wnd_sysmenu_press_accel
+};
+
+static void wnd_sysmenu_erestore(ui_menu_entry_t *, void *);
+static void wnd_sysmenu_eminimize(ui_menu_entry_t *, void *);
+static void wnd_sysmenu_emaximize(ui_menu_entry_t *, void *);
+static void wnd_sysmenu_eclose(ui_menu_entry_t *, void *);
 
 static void ui_window_invalidate(void *, gfx_rect_t *);
@@ -144,4 +178,6 @@
 {
 	gfx_coord2_t dims;
+	gfx_coord2_t below_pos;
+	gfx_rect_t below_rect;
 
 	assert(params->placement != ui_wnd_place_default ||
@@ -177,9 +213,96 @@
 		break;
 	case ui_wnd_place_popup:
-		/* Place popup window below parent rectangle */
-		pos->x = params->prect.p0.x;
-		pos->y = params->prect.p1.y;
+		/* Compute rectangle when placed below */
+		below_pos.x = params->prect.p0.x;
+		below_pos.y = params->prect.p1.y;
+		gfx_rect_translate(&below_pos, &params->rect, &below_rect);
+
+		/* Does below_rect fit within the display? */
+		if (gfx_rect_is_inside(&below_rect, drect)) {
+			/* Place popup window below parent rectangle */
+			pos->x = params->prect.p0.x - params->rect.p0.x;
+			pos->y = params->prect.p1.y - params->rect.p0.y;
+		} else {
+			/* Place popup window above parent rectangle */
+			pos->x = params->prect.p0.x;
+			pos->y = params->prect.p0.y -
+			    (params->rect.p1.y - params->rect.p0.y);
+		}
 		break;
 	}
+}
+
+/** Create window's system menu.
+ *
+ * @param window Window
+ * @return EOK on success or an error code
+ */
+static errno_t ui_window_sysmenu_create(ui_window_t *window)
+{
+	errno_t rc;
+	ui_menu_entry_t *mrestore;
+	ui_menu_entry_t *mmin;
+	ui_menu_entry_t *mmax;
+	ui_menu_entry_t *msep;
+	ui_menu_entry_t *mclose;
+
+	rc = ui_menu_create(window, &window->sysmenu);
+	if (rc != EOK)
+		goto error;
+
+	ui_menu_set_cb(window->sysmenu, &wnd_sysmenu_cb, (void *)window);
+
+	rc = ui_menu_entry_create(window->sysmenu, "~R~estore",
+	    "", &mrestore);
+	if (rc != EOK)
+		goto error;
+
+	if (!window->wdecor->maximized)
+		ui_menu_entry_set_disabled(mrestore, true);
+
+	ui_menu_entry_set_cb(mrestore, wnd_sysmenu_erestore, (void *)window);
+
+	rc = ui_menu_entry_create(window->sysmenu, "Mi~n~imize",
+	    "", &mmin);
+	if (rc != EOK)
+		goto error;
+
+	if ((window->wdecor->style & ui_wds_minimize_btn) == 0)
+		ui_menu_entry_set_disabled(mmin, true);
+
+	ui_menu_entry_set_cb(mmin, wnd_sysmenu_eminimize, (void *)window);
+
+	rc = ui_menu_entry_create(window->sysmenu, "Ma~x~imize",
+	    "", &mmax);
+	if (rc != EOK)
+		goto error;
+
+	if ((window->wdecor->style & ui_wds_maximize_btn) == 0 ||
+	    window->wdecor->maximized)
+		ui_menu_entry_set_disabled(mmax, true);
+
+	ui_menu_entry_set_cb(mmax, wnd_sysmenu_emaximize, (void *)window);
+
+	rc = ui_menu_entry_sep_create(window->sysmenu, &msep);
+	if (rc != EOK)
+		goto error;
+
+	rc = ui_menu_entry_create(window->sysmenu, "~C~lose", "Alt-F4",
+	    &mclose);
+	if (rc != EOK)
+		goto error;
+
+	if ((window->wdecor->style & ui_wds_close_btn) == 0)
+		ui_menu_entry_set_disabled(mclose, true);
+
+	ui_menu_entry_set_cb(mclose, wnd_sysmenu_eclose, (void *)window);
+
+	window->sysmenu_restore = mrestore;
+	window->sysmenu_minimize = mmin;
+	window->sysmenu_maximize = mmax;
+
+	return EOK;
+error:
+	return rc;
 }
 
@@ -195,4 +318,5 @@
 {
 	ui_window_t *window;
+	ui_window_t *pwindow = NULL;
 	display_info_t info;
 	gfx_coord2_t scr_dims;
@@ -218,9 +342,26 @@
 	display_wnd_params_init(&dparams);
 	dparams.rect = params->rect;
+	dparams.caption = params->caption;
 	/* Only allow making the window larger */
 	gfx_rect_dims(&params->rect, &dparams.min_size);
 
+	/*
+	 * If idev_id is not specified, use the UI default (probably
+	 * obtained from display specification. This creates the
+	 * main window in the seat specified on the command line.
+	 */
+	if (params->idev_id != 0)
+		dparams.idev_id = params->idev_id;
+	else
+		dparams.idev_id = ui->idev_id;
+
 	if ((params->flags & ui_wndf_popup) != 0)
 		dparams.flags |= wndf_popup;
+	if ((params->flags & ui_wndf_topmost) != 0)
+		dparams.flags |= wndf_topmost;
+	if ((params->flags & ui_wndf_system) != 0)
+		dparams.flags |= wndf_system;
+	if ((params->flags & ui_wndf_avoid) != 0)
+		dparams.flags |= wndf_avoid;
 
 	if (ui->display != NULL) {
@@ -262,4 +403,6 @@
 			gfx_coord2_add(&dparams.rect.p0, &scr_dims,
 			    &dparams.rect.p1);
+			(void) console_set_caption(ui->console,
+			    params->caption);
 		}
 	} else {
@@ -355,7 +498,23 @@
 	window->wdecor = wdecor;
 	window->cursor = ui_curs_arrow;
+	window->placement = params->placement;
+
+	rc = ui_window_sysmenu_create(window);
+	if (rc != EOK)
+		goto error;
+
 	*rwindow = window;
 
+	if (ui_is_fullscreen(ui))
+		pwindow = ui_window_get_active(ui);
+
 	list_append(&window->lwindows, &ui->windows);
+
+	if (ui_is_fullscreen(ui)) {
+		/* Send unfocus event to previously active window */
+		if (pwindow != NULL)
+			ui_window_send_unfocus(pwindow, 0);
+	}
+
 	return EOK;
 error:
@@ -383,4 +542,5 @@
 {
 	ui_t *ui;
+	ui_window_t *nwindow;
 
 	if (window == NULL)
@@ -391,9 +551,10 @@
 	list_remove(&window->lwindows);
 	ui_control_destroy(window->control);
+	ui_menu_destroy(window->sysmenu);
 	ui_wdecor_destroy(window->wdecor);
 	ui_resource_destroy(window->res);
-	if (0 && window->app_mgc != NULL)
+	if (window->app_mgc != NULL)
 		mem_gc_delete(window->app_mgc);
-	if (0 && window->app_bmp != NULL)
+	if (window->app_bmp != NULL)
 		gfx_bitmap_destroy(window->app_bmp);
 	if (window->mgc != NULL) {
@@ -406,10 +567,19 @@
 		display_window_destroy(window->dwindow);
 
-	free(window);
-
 	/* Need to repaint if windows are emulated */
 	if (ui_is_fullscreen(ui)) {
 		ui_paint(ui);
-	}
+		/* Send focus event to newly active window */
+		nwindow = ui_window_get_active(ui);
+		if (nwindow != NULL)
+			ui_window_send_focus(nwindow, 0);
+	}
+
+	if (ui->console != NULL &&
+	    window->placement == ui_wnd_place_full_screen) {
+		(void) console_set_caption(ui->console, "");
+	}
+
+	free(window);
 }
 
@@ -421,5 +591,4 @@
  * @param window Window
  * @param control Control
- * @return EOK on success, ENOMEM if out of memory
  */
 void ui_window_add(ui_window_t *window, ui_control_t *control)
@@ -461,15 +630,14 @@
 }
 
-/** Resize/move window.
- *
- * Resize window to the dimensions of @a rect. If @a rect.p0 is not 0,0,
- * the top-left corner of the window will move on the screen accordingly.
+/** Resize or (un)maximize window.
  *
  * @param window Window
  * @param rect Rectangle
+ * @param scop Size change operation
  *
  * @return EOK on success or an error code
  */
-errno_t ui_window_resize(ui_window_t *window, gfx_rect_t *rect)
+errno_t ui_window_size_change(ui_window_t *window, gfx_rect_t *rect,
+    ui_wnd_sc_op_t scop)
 {
 	gfx_coord2_t offs;
@@ -537,9 +705,24 @@
 	}
 
-	/* dwindow can be NULL in case of unit tests */
+	/* dwindow can be NULL in case of unit tests or fullscreen mode */
 	if (window->dwindow != NULL) {
-		rc = display_window_resize(window->dwindow, &offs, &nrect);
-		if (rc != EOK)
-			goto error;
+		switch (scop) {
+		case ui_wsc_resize:
+			rc = display_window_resize(window->dwindow, &offs,
+			    &nrect);
+			if (rc != EOK)
+				goto error;
+			break;
+		case ui_wsc_maximize:
+			rc = display_window_maximize(window->dwindow);
+			if (rc != EOK)
+				goto error;
+			break;
+		case ui_wsc_unmaximize:
+			rc = display_window_unmaximize(window->dwindow);
+			if (rc != EOK)
+				goto error;
+			break;
+		}
 	}
 
@@ -561,5 +744,4 @@
 			goto error;
 #endif
-
 		mem_gc_retarget(window->mgc, &win_params.rect, &win_alloc);
 
@@ -568,4 +750,5 @@
 	}
 
+	window->rect = nrect;
 	ui_wdecor_set_rect(window->wdecor, &nrect);
 	ui_wdecor_paint(window->wdecor);
@@ -589,4 +772,19 @@
 }
 
+/** Resize/move window.
+ *
+ * Resize window to the dimensions of @a rect. If @a rect.p0 is not 0,0,
+ * the top-left corner of the window will move on the screen accordingly.
+ *
+ * @param window Window
+ * @param rect Rectangle
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ui_window_resize(ui_window_t *window, gfx_rect_t *rect)
+{
+	return ui_window_size_change(window, rect, ui_wsc_resize);
+}
+
 /** Set window callbacks.
  *
@@ -599,4 +797,33 @@
 	window->cb = cb;
 	window->arg = arg;
+}
+
+/** Change window caption.
+ *
+ * @param window Window
+ * @param caption New caption
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ui_window_set_caption(ui_window_t *window, const char *caption)
+{
+	errno_t rc;
+
+	/* Set console caption if fullscreen window on console */
+	if (window->ui->console != NULL && window->placement ==
+	    ui_wnd_place_full_screen) {
+		rc = console_set_caption(window->ui->console, caption);
+		if (rc != EOK)
+			return rc;
+	}
+
+	/* Set display window caption if running on display service */
+	if (window->dwindow != NULL) {
+		rc = display_window_set_caption(window->dwindow, caption);
+		if (rc != EOK)
+			return rc;
+	}
+
+	return ui_wdecor_set_caption(window->wdecor, caption);
 }
 
@@ -746,12 +973,19 @@
 {
 	ui_window_t *window = (ui_window_t *) arg;
-
+	ui_t *ui = window->ui;
+
+	ui_lock(ui);
 	ui_window_send_close(window);
+	ui_unlock(ui);
 }
 
 /** Handle window focus event. */
-static void dwnd_focus_event(void *arg)
-{
-	ui_window_t *window = (ui_window_t *) arg;
+static void dwnd_focus_event(void *arg, unsigned nfocus)
+{
+	ui_window_t *window = (ui_window_t *) arg;
+	ui_t *ui = window->ui;
+
+	ui_lock(ui);
+	(void)nfocus;
 
 	if (window->wdecor != NULL) {
@@ -760,5 +994,6 @@
 	}
 
-	ui_window_send_focus(window);
+	ui_window_send_focus(window, nfocus);
+	ui_unlock(ui);
 }
 
@@ -767,7 +1002,9 @@
 {
 	ui_window_t *window = (ui_window_t *) arg;
-
-	(void) window;
+	ui_t *ui = window->ui;
+
+	ui_lock(ui);
 	ui_window_send_kbd(window, kbd_event);
+	ui_unlock(ui);
 }
 
@@ -776,4 +1013,6 @@
 {
 	ui_window_t *window = (ui_window_t *) arg;
+	ui_t *ui = window->ui;
+	ui_evclaim_t claim;
 
 	/* Make sure we don't process events until fully initialized */
@@ -781,6 +1020,14 @@
 		return;
 
-	ui_wdecor_pos_event(window->wdecor, event);
+	ui_lock(ui);
+
+	claim = ui_wdecor_pos_event(window->wdecor, event);
+	if (claim == ui_claimed) {
+		ui_unlock(ui);
+		return;
+	}
+
 	ui_window_send_pos(window, event);
+	ui_unlock(ui);
 }
 
@@ -789,4 +1036,5 @@
 {
 	ui_window_t *window = (ui_window_t *) arg;
+	ui_t *ui = window->ui;
 
 	/* Make sure we don't process events until fully initialized */
@@ -797,19 +1045,127 @@
 		return;
 
+	ui_lock(ui);
 	(void) ui_window_resize(window, rect);
 	(void) ui_window_paint(window);
+	ui_unlock(ui);
 }
 
 /** Handle window unfocus event. */
-static void dwnd_unfocus_event(void *arg)
-{
-	ui_window_t *window = (ui_window_t *) arg;
-
-	if (window->wdecor != NULL) {
+static void dwnd_unfocus_event(void *arg, unsigned nfocus)
+{
+	ui_window_t *window = (ui_window_t *) arg;
+	ui_t *ui = window->ui;
+
+	ui_lock(ui);
+
+	if (window->wdecor != NULL && nfocus == 0) {
 		ui_wdecor_set_active(window->wdecor, false);
 		ui_wdecor_paint(window->wdecor);
 	}
 
-	ui_window_send_unfocus(window);
+	ui_window_send_unfocus(window, nfocus);
+	ui_unlock(ui);
+}
+
+/** Window decoration requested opening of system menu.
+ *
+ * @param wdecor Window decoration
+ * @param arg Argument (window)
+ * @param idev_id Input device ID
+ */
+static void wd_sysmenu_open(ui_wdecor_t *wdecor, void *arg, sysarg_t idev_id)
+{
+	ui_window_t *window = (ui_window_t *) arg;
+
+	ui_window_send_sysmenu(window, idev_id);
+}
+
+/** Window decoration requested moving left from system menu handle.
+ *
+ * @param wdecor Window decoration
+ * @param arg Argument (window)
+ * @param idev_id Input device ID
+ */
+static void wd_sysmenu_left(ui_wdecor_t *wdecor, void *arg, sysarg_t idev_id)
+{
+	ui_window_t *window = (ui_window_t *) arg;
+
+	if (window->mbar != NULL) {
+		ui_wdecor_sysmenu_hdl_set_active(window->wdecor, false);
+		ui_menu_close(window->sysmenu);
+		ui_menu_bar_select_last(window->mbar, false, idev_id);
+	}
+}
+
+/** Window decoration requested moving right from system menu handle.
+ *
+ * @param wdecor Window decoration
+ * @param arg Argument (window)
+ * @param idev_id Input device ID
+ */
+static void wd_sysmenu_right(ui_wdecor_t *wdecor, void *arg, sysarg_t idev_id)
+{
+	ui_window_t *window = (ui_window_t *) arg;
+
+	if (window->mbar != NULL) {
+		ui_wdecor_sysmenu_hdl_set_active(window->wdecor, false);
+		ui_menu_close(window->sysmenu);
+		ui_menu_bar_select_first(window->mbar, false, idev_id);
+	}
+}
+
+/** Window decoration detected accelerator press from system menu handle.
+ *
+ * @param wdecor Window decoration
+ * @param arg Argument (window)
+ * @param c Accelerator key
+ * @param idev_id Input device ID
+ */
+static void wd_sysmenu_accel(ui_wdecor_t *wdecor, void *arg, char32_t c,
+    sysarg_t idev_id)
+{
+	ui_window_t *window = (ui_window_t *) arg;
+
+	if (window->mbar != NULL) {
+		ui_wdecor_sysmenu_hdl_set_active(window->wdecor, false);
+		ui_menu_close(window->sysmenu);
+		ui_menu_bar_press_accel(window->mbar, c, idev_id);
+	}
+}
+
+/** Window decoration requested window minimization.
+ *
+ * @param wdecor Window decoration
+ * @param arg Argument (window)
+ */
+static void wd_minimize(ui_wdecor_t *wdecor, void *arg)
+{
+	ui_window_t *window = (ui_window_t *) arg;
+
+	ui_window_send_minimize(window);
+}
+
+/** Window decoration requested window maximization.
+ *
+ * @param wdecor Window decoration
+ * @param arg Argument (window)
+ */
+static void wd_maximize(ui_wdecor_t *wdecor, void *arg)
+{
+	ui_window_t *window = (ui_window_t *) arg;
+
+	ui_window_send_maximize(window);
+}
+
+/** Window decoration requested window unmaximization.
+ *
+ * @param wdecor Window decoration
+ * @param arg Argument (window)
+ */
+static void wd_unmaximize(ui_wdecor_t *wdecor, void *arg)
+{
+	ui_window_t *window = (ui_window_t *) arg;
+
+	ui_window_send_unmaximize(window);
 }
 
@@ -831,11 +1187,13 @@
  * @param arg Argument (window)
  * @param pos Position where the title bar was pressed
- */
-static void wd_move(ui_wdecor_t *wdecor, void *arg, gfx_coord2_t *pos)
+ * @param pos_id Positioning device ID
+ */
+static void wd_move(ui_wdecor_t *wdecor, void *arg, gfx_coord2_t *pos,
+    sysarg_t pos_id)
 {
 	ui_window_t *window = (ui_window_t *) arg;
 
 	if (window->dwindow != NULL)
-		(void) display_window_move_req(window->dwindow, pos);
+		(void) display_window_move_req(window->dwindow, pos, pos_id);
 }
 
@@ -846,12 +1204,16 @@
  * @param rsztype Resize type
  * @param pos Position where the button was pressed
+ * @param pos_id Positioning device ID
  */
 static void wd_resize(ui_wdecor_t *wdecor, void *arg,
-    ui_wdecor_rsztype_t rsztype, gfx_coord2_t *pos)
-{
-	ui_window_t *window = (ui_window_t *) arg;
-
-	if (window->dwindow != NULL)
-		(void) display_window_resize_req(window->dwindow, rsztype, pos);
+    ui_wdecor_rsztype_t rsztype, gfx_coord2_t *pos, sysarg_t pos_id)
+{
+	ui_window_t *window = (ui_window_t *) arg;
+
+	if (window->dwindow != NULL) {
+		(void) display_window_resize_req(window->dwindow,
+		    (display_wnd_rsztype_t) rsztype, // Same constants in the enums
+		    pos, pos_id);
+	}
 }
 
@@ -914,4 +1276,53 @@
 }
 
+/** Send window sysmenu event.
+ *
+ * @param window Window
+ * @parma idev_id Input device ID
+ */
+void ui_window_send_sysmenu(ui_window_t *window, sysarg_t idev_id)
+{
+	if (window->cb != NULL && window->cb->sysmenu != NULL)
+		window->cb->sysmenu(window, window->arg, idev_id);
+	else
+		ui_window_def_sysmenu(window, idev_id);
+}
+
+/** Send window minimize event.
+ *
+ * @param window Window
+ */
+void ui_window_send_minimize(ui_window_t *window)
+{
+	if (window->cb != NULL && window->cb->maximize != NULL)
+		window->cb->minimize(window, window->arg);
+	else
+		ui_window_def_minimize(window);
+}
+
+/** Send window maximize event.
+ *
+ * @param window Window
+ */
+void ui_window_send_maximize(ui_window_t *window)
+{
+	if (window->cb != NULL && window->cb->maximize != NULL)
+		window->cb->maximize(window, window->arg);
+	else
+		ui_window_def_maximize(window);
+}
+
+/** Send window unmaximize event.
+ *
+ * @param window Window
+ */
+void ui_window_send_unmaximize(ui_window_t *window)
+{
+	if (window->cb != NULL && window->cb->unmaximize != NULL)
+		window->cb->unmaximize(window, window->arg);
+	else
+		ui_window_def_unmaximize(window);
+}
+
 /** Send window close event.
  *
@@ -927,9 +1338,10 @@
  *
  * @param window Window
- */
-void ui_window_send_focus(ui_window_t *window)
+ * @param nfocus New number of foci
+ */
+void ui_window_send_focus(ui_window_t *window, unsigned nfocus)
 {
 	if (window->cb != NULL && window->cb->focus != NULL)
-		window->cb->focus(window, window->arg);
+		window->cb->focus(window, window->arg, nfocus);
 }
 
@@ -943,5 +1355,5 @@
 		window->cb->kbd(window, window->arg, kbd);
 	else
-		return ui_window_def_kbd(window, kbd);
+		ui_window_def_kbd(window, kbd);
 }
 
@@ -973,11 +1385,120 @@
  *
  * @param window Window
- */
-void ui_window_send_unfocus(ui_window_t *window)
+ * @param nfocus Number of remaining foci
+ */
+void ui_window_send_unfocus(ui_window_t *window, unsigned nfocus)
 {
 	if (window->cb != NULL && window->cb->unfocus != NULL)
-		window->cb->unfocus(window, window->arg);
+		window->cb->unfocus(window, window->arg, nfocus);
 	else
-		return ui_window_def_unfocus(window);
+		return ui_window_def_unfocus(window, nfocus);
+}
+
+/** Default window sysmenu routine.
+ *
+ * @param window Window
+ * @param idev_id Input device ID
+ * @return EOK on success or an error code
+ */
+errno_t ui_window_def_sysmenu(ui_window_t *window, sysarg_t idev_id)
+{
+	errno_t rc;
+	ui_wdecor_geom_t geom;
+
+	if (ui_menu_is_open(window->sysmenu)) {
+		ui_menu_close(window->sysmenu);
+	} else {
+		ui_wdecor_get_geom(window->wdecor, &geom);
+
+		rc = ui_menu_open(window->sysmenu, &geom.title_bar_rect,
+		    idev_id);
+		if (rc != EOK)
+			goto error;
+	}
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Default window minimize routine.
+ *
+ * @param window Window
+ * @return EOK on success or an error code
+ */
+errno_t ui_window_def_minimize(ui_window_t *window)
+{
+	errno_t rc;
+
+	if (window->dwindow != NULL) {
+		rc = display_window_minimize(window->dwindow);
+		if (rc != EOK)
+			goto error;
+	}
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Default window maximize routine.
+ *
+ * @param window Window
+ * @return EOK on success or an error code
+ */
+errno_t ui_window_def_maximize(ui_window_t *window)
+{
+	errno_t rc;
+	gfx_rect_t old_rect;
+	gfx_rect_t rect;
+
+	old_rect = window->rect;
+
+	if (window->dwindow != NULL) {
+		rc = display_window_get_max_rect(window->dwindow, &rect);
+		if (rc != EOK)
+			return rc;
+	} else {
+		rect = window->ui->rect;
+	}
+
+	ui_wdecor_set_maximized(window->wdecor, true);
+	ui_menu_entry_set_disabled(window->sysmenu_restore, false);
+	ui_menu_entry_set_disabled(window->sysmenu_maximize, true);
+
+	rc = ui_window_size_change(window, &rect, ui_wsc_maximize);
+	if (rc != EOK) {
+		ui_wdecor_set_maximized(window->wdecor, false);
+		return rc;
+	}
+
+	window->normal_rect = old_rect;
+	(void) ui_window_paint(window);
+	return EOK;
+}
+
+/** Default window unmaximize routine.
+ *
+ * @param window Window
+ * @return EOK on success or an error code
+ */
+errno_t ui_window_def_unmaximize(ui_window_t *window)
+{
+	errno_t rc;
+
+	ui_wdecor_set_maximized(window->wdecor, false);
+	ui_menu_entry_set_disabled(window->sysmenu_restore, true);
+	ui_menu_entry_set_disabled(window->sysmenu_maximize, false);
+
+	rc = ui_window_size_change(window, &window->normal_rect,
+	    ui_wsc_unmaximize);
+	if (rc != EOK) {
+		ui_wdecor_set_maximized(window->wdecor, true);
+		printf("ui_window_size_change->error\n");
+		return rc;
+	}
+
+	(void) ui_window_paint(window);
+	return EOK;
 }
 
@@ -985,9 +1506,19 @@
  *
  * @param window Window
- */
-void ui_window_def_kbd(ui_window_t *window, kbd_event_t *kbd)
-{
+ * @return ui_claimed iff event was claimed
+ */
+ui_evclaim_t ui_window_def_kbd(ui_window_t *window, kbd_event_t *kbd)
+{
+	ui_evclaim_t claim;
+
 	if (window->control != NULL)
-		ui_control_kbd_event(window->control, kbd);
+		claim = ui_control_kbd_event(window->control, kbd);
+	else
+		claim = ui_unclaimed;
+
+	if (claim == ui_unclaimed)
+		return ui_wdecor_kbd_event(window->wdecor, kbd);
+
+	return ui_unclaimed;
 }
 
@@ -1035,10 +1566,122 @@
  *
  * @param window Window
+ * @param nfocus Number of remaining foci
  * @return EOK on success or an error code
  */
-void ui_window_def_unfocus(ui_window_t *window)
+void ui_window_def_unfocus(ui_window_t *window, unsigned nfocus)
 {
 	if (window->control != NULL)
-		ui_control_unfocus(window->control);
+		ui_control_unfocus(window->control, nfocus);
+}
+
+/** Handle system menu left event.
+ *
+ * @param sysmenu System menu
+ * @param arg Argument (ui_window_t *)
+ * @param idev_id Input device ID
+ */
+static void wnd_sysmenu_left(ui_menu_t *sysmenu, void *arg, sysarg_t idev_id)
+{
+	ui_window_t *window = (ui_window_t *)arg;
+
+	if (window->mbar != NULL) {
+		ui_wdecor_sysmenu_hdl_set_active(window->wdecor, false);
+		ui_menu_close(sysmenu);
+		ui_menu_bar_select_last(window->mbar, true, idev_id);
+	}
+}
+
+/** Handle system menu right event.
+ *
+ * @param sysmenu System menu
+ * @param arg Argument (ui_window_t *)
+ * @param idev_id Input device ID
+ */
+static void wnd_sysmenu_right(ui_menu_t *sysmenu, void *arg, sysarg_t idev_id)
+{
+	ui_window_t *window = (ui_window_t *)arg;
+
+	if (window->mbar != NULL) {
+		ui_wdecor_sysmenu_hdl_set_active(window->wdecor, false);
+		ui_menu_close(sysmenu);
+		ui_menu_bar_select_first(window->mbar, true, idev_id);
+	}
+}
+
+/** Handle system menu close request event.
+ *
+ * @param sysmenu System menu
+ * @param arg Argument (ui_window_t *)
+ * @param idev_id Input device ID
+ */
+static void wnd_sysmenu_close_req(ui_menu_t *sysmenu, void *arg)
+{
+	ui_window_t *window = (ui_window_t *)arg;
+
+	ui_wdecor_sysmenu_hdl_set_active(window->wdecor, false);
+	ui_menu_close(sysmenu);
+}
+
+/** Handle system menu Restore entry activation.
+ *
+ * @param mentry Menu entry
+ * @param arg Argument (ui_window_t *)
+ */
+static void wnd_sysmenu_erestore(ui_menu_entry_t *mentry, void *arg)
+{
+	ui_window_t *window = (ui_window_t *)arg;
+
+	ui_window_send_unmaximize(window);
+}
+
+/** Handle system menu Minimize entry activation.
+ *
+ * @param mentry Menu entry
+ * @param arg Argument (ui_window_t *)
+ */
+static void wnd_sysmenu_eminimize(ui_menu_entry_t *mentry, void *arg)
+{
+	ui_window_t *window = (ui_window_t *)arg;
+
+	ui_window_send_minimize(window);
+}
+
+/** Handle system menu Maximize entry activation.
+ *
+ * @param mentry Menu entry
+ * @param arg Argument (ui_window_t *)
+ */
+static void wnd_sysmenu_emaximize(ui_menu_entry_t *mentry, void *arg)
+{
+	ui_window_t *window = (ui_window_t *)arg;
+
+	ui_window_send_maximize(window);
+}
+
+/** Handle system menu Close entry activation.
+ *
+ * @param mentry Menu entry
+ * @param arg Argument (ui_window_t *)
+ */
+static void wnd_sysmenu_eclose(ui_menu_entry_t *mentry, void *arg)
+{
+	ui_window_t *window = (ui_window_t *)arg;
+
+	ui_window_send_close(window);
+}
+
+/** Handle system menu press accelerator key event.
+ *
+ * @param sysmenu System menu
+ * @param arg Argument (ui_window_t *)
+ * @param idev_id Input device ID
+ */
+static void wnd_sysmenu_press_accel(ui_menu_t *sysmenu, void *arg,
+    char32_t c, sysarg_t idev_id)
+{
+	(void)sysmenu;
+	(void)arg;
+	(void)c;
+	(void)idev_id;
 }
 
Index: uspace/lib/ui/test/accel.c
===================================================================
--- uspace/lib/ui/test/accel.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/test/accel.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTvvhhzccgggrERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <pcut/pcut.h>
+#include <stdlib.h>
+#include <str.h>
+#include <uchar.h>
+#include <ui/accel.h>
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(accel);
+
+/** ui_accel_process() */
+PCUT_TEST(process)
+{
+	errno_t rc;
+	char *buf;
+	char *endptr;
+	char *bp1;
+	char *bp2;
+
+	/* Cases where one string is produced */
+
+	rc = ui_accel_process("", &buf, &endptr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_STR_EQUALS("", buf);
+	PCUT_ASSERT_EQUALS(buf + 1, endptr);
+	free(buf);
+
+	rc = ui_accel_process("Hello", &buf, &endptr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_STR_EQUALS("Hello", buf);
+	PCUT_ASSERT_EQUALS(buf + str_size("Hello") + 1, endptr);
+	free(buf);
+
+	rc = ui_accel_process("~~Hello~~", &buf, &endptr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_STR_EQUALS("~Hello~", buf);
+	PCUT_ASSERT_EQUALS(buf + str_size("~Hello~") + 1, endptr);
+	free(buf);
+
+	/* Three strings are produced (the first is empty) */
+
+	rc = ui_accel_process("~H~ello", &buf, &endptr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_STR_EQUALS("", buf);
+
+	bp1 = buf + str_size(buf) + 1;
+	PCUT_ASSERT_STR_EQUALS("H", bp1);
+
+	bp2 = bp1 + str_size(bp1) + 1;
+	PCUT_ASSERT_STR_EQUALS("ello", bp2);
+
+	PCUT_ASSERT_EQUALS(bp2 + str_size("ello") + 1, endptr);
+	free(buf);
+}
+
+/** ui_accel_get() */
+PCUT_TEST(get)
+{
+	char32_t a;
+
+	a = ui_accel_get("");
+	PCUT_ASSERT_EQUALS('\0', a);
+
+	a = ui_accel_get("Hello");
+	PCUT_ASSERT_EQUALS('\0', a);
+
+	a = ui_accel_get("~~");
+	PCUT_ASSERT_EQUALS('\0', a);
+
+	a = ui_accel_get("~~Hello~~");
+	PCUT_ASSERT_EQUALS('\0', a);
+
+	a = ui_accel_get("~H~ello");
+	PCUT_ASSERT_EQUALS('h', a);
+
+	a = ui_accel_get("H~e~llo");
+	PCUT_ASSERT_EQUALS('e', a);
+}
+
+PCUT_EXPORT(accel);
Index: uspace/lib/ui/test/checkbox.c
===================================================================
--- uspace/lib/ui/test/checkbox.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/test/checkbox.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -151,4 +151,38 @@
 }
 
+/** Get check box checked returns internal field */
+PCUT_TEST(get_checked)
+{
+	ui_checkbox_t *checkbox;
+	errno_t rc;
+
+	rc = ui_checkbox_create(NULL, "Hello", &checkbox);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	checkbox->checked = false;
+	PCUT_ASSERT_FALSE(ui_checkbox_get_checked(checkbox));
+	checkbox->checked = true;
+	PCUT_ASSERT_TRUE(ui_checkbox_get_checked(checkbox));
+
+	ui_checkbox_destroy(checkbox);
+}
+
+/** Set check box checked sets internal field */
+PCUT_TEST(set_checked)
+{
+	ui_checkbox_t *checkbox;
+	errno_t rc;
+
+	rc = ui_checkbox_create(NULL, "Hello", &checkbox);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_checkbox_set_checked(checkbox, true);
+	PCUT_ASSERT_TRUE(checkbox->checked);
+	ui_checkbox_set_checked(checkbox, false);
+	PCUT_ASSERT_FALSE(checkbox->checked);
+
+	ui_checkbox_destroy(checkbox);
+}
+
 /** Paint check box in graphics mode */
 PCUT_TEST(paint_gfx)
Index: uspace/lib/ui/test/clickmatic.c
===================================================================
--- uspace/lib/ui/test/clickmatic.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/test/clickmatic.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <pcut/pcut.h>
+#include <ui/clickmatic.h>
+#include <ui/ui.h>
+#include "../private/clickmatic.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(clickmatic);
+
+static void test_clicked(ui_clickmatic_t *, void *);
+
+static ui_clickmatic_cb_t test_cb = {
+	.clicked = test_clicked
+};
+
+typedef struct {
+	int clicked_cnt;
+} test_resp_t;
+
+/** Create and destroy clickmatic */
+PCUT_TEST(create_destroy)
+{
+	ui_t *ui = NULL;
+	ui_clickmatic_t *clickmatic = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_clickmatic_create(ui, &clickmatic);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(clickmatic);
+
+	ui_clickmatic_destroy(clickmatic);
+	ui_destroy(ui);
+}
+
+/** ui_clickmatic_destroy() can take NULL argument (no-op) */
+PCUT_TEST(destroy_null)
+{
+	ui_clickmatic_destroy(NULL);
+}
+
+/** ui_clickmatic_set_cb() sets internal fields */
+PCUT_TEST(set_cb)
+{
+	ui_t *ui = NULL;
+	ui_clickmatic_t *clickmatic = NULL;
+	test_resp_t test_resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_clickmatic_create(ui, &clickmatic);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(clickmatic);
+
+	ui_clickmatic_set_cb(clickmatic, &test_cb, (void *)&test_resp);
+	PCUT_ASSERT_EQUALS(&test_cb, clickmatic->cb);
+	PCUT_ASSERT_EQUALS((void *)&test_resp, clickmatic->arg);
+
+	ui_clickmatic_destroy(clickmatic);
+	ui_destroy(ui);
+}
+
+/** Pressing and releasing clickmatic generates one event */
+PCUT_TEST(press_release)
+{
+	ui_t *ui = NULL;
+	ui_clickmatic_t *clickmatic = NULL;
+	test_resp_t test_resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_clickmatic_create(ui, &clickmatic);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(clickmatic);
+
+	test_resp.clicked_cnt = 0;
+
+	ui_clickmatic_set_cb(clickmatic, &test_cb, (void *)&test_resp);
+	PCUT_ASSERT_EQUALS(&test_cb, clickmatic->cb);
+	PCUT_ASSERT_EQUALS((void *)&test_resp, clickmatic->arg);
+
+	PCUT_ASSERT_INT_EQUALS(0, test_resp.clicked_cnt);
+
+	ui_clickmatic_press(clickmatic);
+	ui_clickmatic_release(clickmatic);
+
+	PCUT_ASSERT_INT_EQUALS(1, test_resp.clicked_cnt);
+
+	ui_clickmatic_destroy(clickmatic);
+	ui_destroy(ui);
+}
+
+static void test_clicked(ui_clickmatic_t *clickmatic, void *arg)
+{
+	test_resp_t *test_resp = (test_resp_t *)arg;
+
+	++test_resp->clicked_cnt;
+}
+
+PCUT_EXPORT(clickmatic);
Index: uspace/lib/ui/test/control.c
===================================================================
--- uspace/lib/ui/test/control.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/test/control.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -33,51 +33,12 @@
 #include <pcut/pcut.h>
 #include <ui/control.h>
+#include <ui/testctl.h>
 #include <stdbool.h>
 #include <types/ui/event.h>
+#include "../private/testctl.h"
 
 PCUT_INIT;
 
 PCUT_TEST_SUITE(control);
-
-static void test_ctl_destroy(void *);
-static errno_t test_ctl_paint(void *);
-static ui_evclaim_t test_ctl_kbd_event(void *, kbd_event_t *);
-static ui_evclaim_t test_ctl_pos_event(void *, pos_event_t *);
-static void test_ctl_unfocus(void *);
-
-static ui_control_ops_t test_ctl_ops = {
-	.destroy = test_ctl_destroy,
-	.paint = test_ctl_paint,
-	.kbd_event = test_ctl_kbd_event,
-	.pos_event = test_ctl_pos_event,
-	.unfocus = test_ctl_unfocus
-};
-
-/** Test response */
-typedef struct {
-	/** Claim to return */
-	ui_evclaim_t claim;
-	/** Result code to return */
-	errno_t rc;
-
-	/** @c true iff destroy was called */
-	bool destroy;
-
-	/** @c true iff paint was called */
-	bool paint;
-
-	/** @c true iff kbd_event was called */
-	bool kbd;
-	/** Keyboard event that was sent */
-	kbd_event_t kevent;
-
-	/** @c true iff pos_event was called */
-	bool pos;
-	/** Position event that was sent */
-	pos_event_t pevent;
-
-	/** @c true iff unfocus was called */
-	bool unfocus;
-} test_resp_t;
 
 /** Allocate and deallocate control */
@@ -87,5 +48,5 @@
 	errno_t rc;
 
-	rc = ui_control_new(&test_ctl_ops, NULL, &control);
+	rc = ui_control_new(&ui_test_ctl_ops, NULL, &control);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(control);
@@ -103,16 +64,16 @@
 PCUT_TEST(destroy)
 {
-	ui_control_t *control = NULL;
-	test_resp_t resp;
+	ui_test_ctl_t *testctl = NULL;
+	ui_tc_resp_t resp;
 	errno_t rc;
 
-	rc = ui_control_new(&test_ctl_ops, &resp, &control);
+	rc = ui_test_ctl_create(&resp, &testctl);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(control);
+	PCUT_ASSERT_NOT_NULL(testctl);
 
 	resp.rc = EOK;
 	resp.destroy = false;
 
-	ui_control_destroy(control);
+	ui_control_destroy(ui_test_ctl_ctl(testctl));
 	PCUT_ASSERT_TRUE(resp.destroy);
 }
@@ -121,16 +82,16 @@
 PCUT_TEST(paint)
 {
-	ui_control_t *control = NULL;
-	test_resp_t resp;
+	ui_test_ctl_t *testctl = NULL;
+	ui_tc_resp_t resp;
 	errno_t rc;
 
-	rc = ui_control_new(&test_ctl_ops, &resp, &control);
+	rc = ui_test_ctl_create(&resp, &testctl);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(control);
+	PCUT_ASSERT_NOT_NULL(testctl);
 
 	resp.rc = EOK;
 	resp.paint = false;
 
-	rc = ui_control_paint(control);
+	rc = ui_control_paint(ui_test_ctl_ctl(testctl));
 	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
 	PCUT_ASSERT_TRUE(resp.paint);
@@ -139,9 +100,9 @@
 	resp.paint = false;
 
-	rc = ui_control_paint(control);
+	rc = ui_control_paint(ui_test_ctl_ctl(testctl));
 	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
 	PCUT_ASSERT_TRUE(resp.paint);
 
-	ui_control_delete(control);
+	ui_test_ctl_destroy(testctl);
 }
 
@@ -149,13 +110,13 @@
 PCUT_TEST(kbd_event)
 {
-	ui_control_t *control = NULL;
-	test_resp_t resp;
+	ui_test_ctl_t *testctl = NULL;
+	ui_tc_resp_t resp;
 	kbd_event_t event;
 	ui_evclaim_t claim;
 	errno_t rc;
 
-	rc = ui_control_new(&test_ctl_ops, &resp, &control);
+	rc = ui_test_ctl_create(&resp, &testctl);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(control);
+	PCUT_ASSERT_NOT_NULL(testctl);
 
 	resp.claim = ui_claimed;
@@ -166,5 +127,5 @@
 	event.c = '@';
 
-	claim = ui_control_kbd_event(control, &event);
+	claim = ui_control_kbd_event(ui_test_ctl_ctl(testctl), &event);
 	PCUT_ASSERT_EQUALS(resp.claim, claim);
 	PCUT_ASSERT_TRUE(resp.kbd);
@@ -174,5 +135,5 @@
 	PCUT_ASSERT_INT_EQUALS(resp.kevent.c, event.c);
 
-	ui_control_delete(control);
+	ui_test_ctl_destroy(testctl);
 }
 
@@ -180,13 +141,13 @@
 PCUT_TEST(pos_event)
 {
-	ui_control_t *control = NULL;
-	test_resp_t resp;
+	ui_test_ctl_t *testctl = NULL;
+	ui_tc_resp_t resp;
 	pos_event_t event;
 	ui_evclaim_t claim;
 	errno_t rc;
 
-	rc = ui_control_new(&test_ctl_ops, &resp, &control);
+	rc = ui_test_ctl_create(&resp, &testctl);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(control);
+	PCUT_ASSERT_NOT_NULL(testctl);
 
 	resp.claim = ui_claimed;
@@ -198,5 +159,5 @@
 	event.vpos = 4;
 
-	claim = ui_control_pos_event(control, &event);
+	claim = ui_control_pos_event(ui_test_ctl_ctl(testctl), &event);
 	PCUT_ASSERT_EQUALS(resp.claim, claim);
 	PCUT_ASSERT_TRUE(resp.pos);
@@ -207,5 +168,5 @@
 	PCUT_ASSERT_INT_EQUALS(resp.pevent.vpos, event.vpos);
 
-	ui_control_delete(control);
+	ui_test_ctl_destroy(testctl);
 }
 
@@ -213,60 +174,19 @@
 PCUT_TEST(unfocus)
 {
-	ui_control_t *control = NULL;
-	test_resp_t resp;
+	ui_test_ctl_t *testctl = NULL;
+	ui_tc_resp_t resp;
 	errno_t rc;
 
-	rc = ui_control_new(&test_ctl_ops, &resp, &control);
+	rc = ui_test_ctl_create(&resp, &testctl);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(control);
+	PCUT_ASSERT_NOT_NULL(testctl);
 
 	resp.unfocus = false;
 
-	ui_control_unfocus(control);
+	ui_control_unfocus(ui_test_ctl_ctl(testctl), 42);
 	PCUT_ASSERT_TRUE(resp.unfocus);
+	PCUT_ASSERT_INT_EQUALS(42, resp.unfocus_nfocus);
 
-	ui_control_delete(control);
-}
-
-static void test_ctl_destroy(void *arg)
-{
-	test_resp_t *resp = (test_resp_t *) arg;
-
-	resp->destroy = true;
-}
-
-static errno_t test_ctl_paint(void *arg)
-{
-	test_resp_t *resp = (test_resp_t *) arg;
-
-	resp->paint = true;
-	return resp->rc;
-}
-
-static ui_evclaim_t test_ctl_kbd_event(void *arg, kbd_event_t *event)
-{
-	test_resp_t *resp = (test_resp_t *) arg;
-
-	resp->kbd = true;
-	resp->kevent = *event;
-
-	return resp->claim;
-}
-
-static ui_evclaim_t test_ctl_pos_event(void *arg, pos_event_t *event)
-{
-	test_resp_t *resp = (test_resp_t *) arg;
-
-	resp->pos = true;
-	resp->pevent = *event;
-
-	return resp->claim;
-}
-
-static void test_ctl_unfocus(void *arg)
-{
-	test_resp_t *resp = (test_resp_t *) arg;
-
-	resp->unfocus = true;
+	ui_test_ctl_destroy(testctl);
 }
 
Index: uspace/lib/ui/test/entry.c
===================================================================
--- uspace/lib/ui/test/entry.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/test/entry.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -106,8 +106,21 @@
 PCUT_TEST(set_halign)
 {
-	ui_entry_t *entry;
-	errno_t rc;
-
-	rc = ui_entry_create(NULL, "Hello", &entry);
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_entry_t *entry;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_entry_create(window, "Hello", &entry);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -118,4 +131,6 @@
 
 	ui_entry_destroy(entry);
+	ui_window_destroy(window);
+	ui_destroy(ui);
 }
 
Index: uspace/lib/ui/test/filelist.c
===================================================================
--- uspace/lib/ui/test/filelist.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/test/filelist.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,1223 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <io/kbd_event.h>
+#include <io/pos_event.h>
+#include <pcut/pcut.h>
+#include <stdio.h>
+#include <ui/ui.h>
+#include <ui/filelist.h>
+#include <ui/scrollbar.h>
+#include <vfs/vfs.h>
+#include "../private/filelist.h"
+#include "../private/list.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(file_list);
+
+/** Test response */
+typedef struct {
+	bool activate_req;
+	ui_file_list_t *activate_req_file_list;
+
+	bool selected;
+	ui_file_list_t *selected_file_list;
+	const char *selected_fname;
+} test_resp_t;
+
+static void test_file_list_activate_req(ui_file_list_t *, void *);
+static void test_file_list_selected(ui_file_list_t *, void *, const char *);
+
+static ui_file_list_cb_t test_cb = {
+	.activate_req = test_file_list_activate_req,
+	.selected = test_file_list_selected
+};
+
+/** Create and destroy file list. */
+PCUT_TEST(create_destroy)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_set_cb() sets callback */
+PCUT_TEST(set_cb)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	errno_t rc;
+	test_resp_t resp;
+
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_file_list_set_cb(flist, &test_cb, &resp);
+	PCUT_ASSERT_EQUALS(&test_cb, flist->cb);
+	PCUT_ASSERT_EQUALS(&resp, flist->cb_arg);
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test ui_file_list_paint() */
+PCUT_TEST(paint)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_file_list_paint(flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_ctl() returns a valid UI control */
+PCUT_TEST(ctl)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	ui_control_t *control;
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	control = ui_file_list_ctl(flist);
+	PCUT_ASSERT_NOT_NULL(control);
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_set_rect() sets internal field */
+PCUT_TEST(set_rect)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	gfx_rect_t rect;
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 1;
+	rect.p0.y = 2;
+	rect.p1.x = 3;
+	rect.p1.y = 4;
+
+	ui_file_list_set_rect(flist, &rect);
+	PCUT_ASSERT_INT_EQUALS(rect.p0.x, flist->list->rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(rect.p0.y, flist->list->rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(rect.p1.x, flist->list->rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(rect.p1.y, flist->list->rect.p1.y);
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_is_active() returns file list activity state */
+PCUT_TEST(is_active)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_TRUE(ui_file_list_is_active(flist));
+	ui_file_list_destroy(flist);
+
+	rc = ui_file_list_create(window, false, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_FALSE(ui_file_list_is_active(flist));
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_activate() activates file list */
+PCUT_TEST(activate)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	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 = ui_file_list_create(window, false, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(ui_file_list_is_active(flist));
+	rc = ui_file_list_activate(flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_TRUE(ui_file_list_is_active(flist));
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_deactivate() deactivates file list */
+PCUT_TEST(deactivate)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_TRUE(ui_file_list_is_active(flist));
+	ui_file_list_deactivate(flist);
+	PCUT_ASSERT_FALSE(ui_file_list_is_active(flist));
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_entry_append() appends new entry */
+PCUT_TEST(entry_append)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	ui_file_list_entry_attr_t attr;
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_file_list_entry_attr_init(&attr);
+
+	attr.name = "a";
+	attr.size = 1;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(1, ui_list_entries_cnt(flist->list));
+
+	attr.name = "b";
+	attr.size = 2;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_entries_cnt(flist->list));
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_entry_destroy() destroys entry */
+PCUT_TEST(entry_destroy)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	ui_file_list_entry_t *entry;
+	ui_file_list_entry_attr_t attr;
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.name = "a";
+	attr.size = 1;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.name = "b";
+	attr.size = 2;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_entries_cnt(flist->list));
+
+	entry = ui_file_list_first(flist);
+	ui_file_list_entry_destroy(entry);
+
+	PCUT_ASSERT_INT_EQUALS(1, ui_list_entries_cnt(flist->list));
+
+	entry = ui_file_list_first(flist);
+	ui_file_list_entry_destroy(entry);
+
+	PCUT_ASSERT_INT_EQUALS(0, ui_list_entries_cnt(flist->list));
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_clear_entries() removes all entries from file list */
+PCUT_TEST(clear_entries)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	ui_file_list_entry_attr_t attr;
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_file_list_entry_attr_init(&attr);
+	attr.name = "a";
+	attr.size = 1;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.name = "a";
+	attr.size = 2;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_entries_cnt(flist->list));
+
+	ui_file_list_clear_entries(flist);
+	PCUT_ASSERT_INT_EQUALS(0, ui_list_entries_cnt(flist->list));
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_read_dir() reads the contents of a directory */
+PCUT_TEST(read_dir)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	ui_file_list_entry_t *entry;
+	char buf[L_tmpnam];
+	char *fname;
+	char *p;
+	errno_t rc;
+	FILE *f;
+	int rv;
+
+	/* Create name for temporary directory */
+	p = tmpnam(buf);
+	PCUT_ASSERT_NOT_NULL(p);
+
+	/* Create temporary directory */
+	rc = vfs_link_path(p, KIND_DIRECTORY, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rv = asprintf(&fname, "%s/%s", p, "a");
+	PCUT_ASSERT_TRUE(rv >= 0);
+
+	f = fopen(fname, "wb");
+	PCUT_ASSERT_NOT_NULL(f);
+
+	rv = fprintf(f, "X");
+	PCUT_ASSERT_TRUE(rv >= 0);
+
+	rv = fclose(f);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_file_list_read_dir(flist, p);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_entries_cnt(flist->list));
+
+	entry = ui_file_list_first(flist);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("..", entry->name);
+
+	entry = ui_file_list_next(entry);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("a", entry->name);
+	PCUT_ASSERT_INT_EQUALS(1, entry->size);
+
+	ui_file_list_destroy(flist);
+
+	rv = remove(fname);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	rv = remove(p);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	free(fname);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** When moving to parent directory from a subdir, we seek to the
+ * coresponding entry
+ */
+PCUT_TEST(read_dir_up)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	char buf[L_tmpnam];
+	char *subdir_a;
+	char *subdir_b;
+	char *subdir_c;
+	char *p;
+	errno_t rc;
+	int rv;
+
+	/* Create name for temporary directory */
+	p = tmpnam(buf);
+	PCUT_ASSERT_NOT_NULL(p);
+
+	/* Create temporary directory */
+	rc = vfs_link_path(p, KIND_DIRECTORY, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Create some subdirectories */
+
+	rv = asprintf(&subdir_a, "%s/%s", p, "a");
+	PCUT_ASSERT_TRUE(rv >= 0);
+	rc = vfs_link_path(subdir_a, KIND_DIRECTORY, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rv = asprintf(&subdir_b, "%s/%s", p, "b");
+	PCUT_ASSERT_TRUE(rv >= 0);
+	rc = vfs_link_path(subdir_b, KIND_DIRECTORY, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rv = asprintf(&subdir_c, "%s/%s", p, "c");
+	PCUT_ASSERT_TRUE(rv >= 0);
+	rc = vfs_link_path(subdir_c, KIND_DIRECTORY, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, 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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Start in subdirectory "b" */
+	rc = ui_file_list_read_dir(flist, subdir_b);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Now go up (into p) */
+
+	rc = ui_file_list_read_dir(flist, "..");
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_NOT_NULL(ui_file_list_get_cursor(flist));
+	PCUT_ASSERT_STR_EQUALS("b", ui_file_list_get_cursor(flist)->name);
+
+	ui_file_list_destroy(flist);
+
+	rv = remove(subdir_a);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	rv = remove(subdir_b);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	rv = remove(subdir_c);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	rv = remove(p);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	free(subdir_a);
+	free(subdir_b);
+	free(subdir_c);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_sort() sorts file list entries */
+PCUT_TEST(sort)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	ui_file_list_entry_t *entry;
+	ui_file_list_entry_attr_t attr;
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_file_list_entry_attr_init(&attr);
+
+	attr.name = "b";
+	attr.size = 1;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.name = "c";
+	attr.size = 3;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.name = "a";
+	attr.size = 2;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_file_list_sort(flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	entry = ui_file_list_first(flist);
+	PCUT_ASSERT_STR_EQUALS("a", entry->name);
+	PCUT_ASSERT_INT_EQUALS(2, entry->size);
+
+	entry = ui_file_list_next(entry);
+	PCUT_ASSERT_STR_EQUALS("b", entry->name);
+	PCUT_ASSERT_INT_EQUALS(1, entry->size);
+
+	entry = ui_file_list_next(entry);
+	PCUT_ASSERT_STR_EQUALS("c", entry->name);
+	PCUT_ASSERT_INT_EQUALS(3, entry->size);
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_list_compare compares two file list entries */
+PCUT_TEST(list_compare)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	ui_file_list_entry_t *a, *b;
+	ui_file_list_entry_attr_t attr;
+	int rel;
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_file_list_entry_attr_init(&attr);
+
+	attr.name = "a";
+	attr.size = 2;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.name = "b";
+	attr.size = 1;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	a = ui_file_list_first(flist);
+	PCUT_ASSERT_NOT_NULL(a);
+	b = ui_file_list_next(a);
+	PCUT_ASSERT_NOT_NULL(b);
+
+	/* a < b */
+	rel = ui_file_list_list_compare(a->entry, b->entry);
+	PCUT_ASSERT_TRUE(rel < 0);
+
+	/* b > a */
+	rel = ui_file_list_list_compare(b->entry, a->entry);
+	PCUT_ASSERT_TRUE(rel > 0);
+
+	/* a == a */
+	rel = ui_file_list_list_compare(a->entry, a->entry);
+	PCUT_ASSERT_INT_EQUALS(0, rel);
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_entry_attr_init() initializes file list attribute structure */
+PCUT_TEST(entry_attr_init)
+{
+	ui_file_list_entry_attr_t attr;
+
+	ui_file_list_entry_attr_init(&attr);
+	PCUT_ASSERT_NULL(attr.name);
+	PCUT_ASSERT_INT_EQUALS(0, attr.size);
+	PCUT_ASSERT_EQUALS(false, attr.isdir);
+	PCUT_ASSERT_INT_EQUALS(0, attr.svc);
+}
+
+/** ui_file_list_first() returns valid entry or @c NULL as appropriate */
+PCUT_TEST(first)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	ui_file_list_entry_t *entry;
+	ui_file_list_entry_attr_t attr;
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_file_list_entry_attr_init(&attr);
+
+	entry = ui_file_list_first(flist);
+	PCUT_ASSERT_NULL(entry);
+
+	/* Add one entry */
+	attr.name = "a";
+	attr.size = 1;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Now try getting it */
+	entry = ui_file_list_first(flist);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("a", entry->name);
+	PCUT_ASSERT_INT_EQUALS(1, entry->size);
+
+	/* Add another entry */
+	attr.name = "b";
+	attr.size = 2;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* We should still get the first entry */
+	entry = ui_file_list_first(flist);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("a", entry->name);
+	PCUT_ASSERT_INT_EQUALS(1, entry->size);
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_last() returns valid entry or @c NULL as appropriate */
+PCUT_TEST(last)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	ui_file_list_entry_t *entry;
+	ui_file_list_entry_attr_t attr;
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_file_list_entry_attr_init(&attr);
+
+	entry = ui_file_list_last(flist);
+	PCUT_ASSERT_NULL(entry);
+
+	/* Add one entry */
+	attr.name = "a";
+	attr.size = 1;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Now try getting it */
+	entry = ui_file_list_last(flist);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("a", entry->name);
+	PCUT_ASSERT_INT_EQUALS(1, entry->size);
+
+	/* Add another entry */
+	attr.name = "b";
+	attr.size = 2;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* We should get new entry now */
+	entry = ui_file_list_last(flist);
+	PCUT_ASSERT_NOT_NULL(entry);
+	attr.name = "b";
+	attr.size = 2;
+	PCUT_ASSERT_STR_EQUALS("b", entry->name);
+	PCUT_ASSERT_INT_EQUALS(2, entry->size);
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_next() returns the next entry or @c NULL as appropriate */
+PCUT_TEST(next)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	ui_file_list_entry_t *entry;
+	ui_file_list_entry_attr_t attr;
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_file_list_entry_attr_init(&attr);
+
+	/* Add one entry */
+	attr.name = "a";
+	attr.size = 1;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Now try getting its successor */
+	entry = ui_file_list_first(flist);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	entry = ui_file_list_next(entry);
+	PCUT_ASSERT_NULL(entry);
+
+	/* Add another entry */
+	attr.name = "b";
+	attr.size = 2;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Try getting the successor of first entry again */
+	entry = ui_file_list_first(flist);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	entry = ui_file_list_next(entry);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("b", entry->name);
+	PCUT_ASSERT_INT_EQUALS(2, entry->size);
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_prev() returns the previous entry or @c NULL as appropriate */
+PCUT_TEST(prev)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	ui_file_list_entry_t *entry;
+	ui_file_list_entry_attr_t attr;
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_file_list_entry_attr_init(&attr);
+
+	/* Add one entry */
+	attr.name = "a";
+	attr.size = 1;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Now try getting its predecessor */
+	entry = ui_file_list_last(flist);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	entry = ui_file_list_prev(entry);
+	PCUT_ASSERT_NULL(entry);
+
+	/* Add another entry */
+	attr.name = "b";
+	attr.size = 2;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Try getting the predecessor of the new entry */
+	entry = ui_file_list_last(flist);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	entry = ui_file_list_prev(entry);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("a", entry->name);
+	PCUT_ASSERT_INT_EQUALS(1, entry->size);
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_open_dir() opens a directory entry */
+PCUT_TEST(open_dir)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	ui_file_list_entry_t *entry;
+	char buf[L_tmpnam];
+	char *sdname;
+	char *p;
+	errno_t rc;
+	int rv;
+
+	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);
+
+	/* Create name for temporary directory */
+	p = tmpnam(buf);
+	PCUT_ASSERT_NOT_NULL(p);
+
+	/* Create temporary directory */
+	rc = vfs_link_path(p, KIND_DIRECTORY, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rv = asprintf(&sdname, "%s/%s", p, "a");
+	PCUT_ASSERT_TRUE(rv >= 0);
+
+	/* Create sub-directory */
+	rc = vfs_link_path(sdname, KIND_DIRECTORY, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_file_list_read_dir(flist, p);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_STR_EQUALS(p, flist->dir);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_entries_cnt(flist->list));
+
+	entry = ui_file_list_first(flist);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("..", entry->name);
+
+	entry = ui_file_list_next(entry);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("a", entry->name);
+	PCUT_ASSERT_TRUE(entry->isdir);
+
+	rc = ui_file_list_open_dir(flist, entry);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_STR_EQUALS(sdname, flist->dir);
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+
+	rv = remove(sdname);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	rv = remove(p);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	free(sdname);
+}
+
+/** ui_file_list_open_file() runs selected callback */
+PCUT_TEST(open_file)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	ui_file_list_entry_attr_t attr;
+	errno_t rc;
+	test_resp_t resp;
+
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_file_list_set_cb(flist, &test_cb, &resp);
+
+	attr.name = "hello.txt";
+	attr.size = 1;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	resp.selected = false;
+	resp.selected_file_list = NULL;
+	resp.selected_fname = NULL;
+
+	ui_file_list_open_file(flist, ui_file_list_first(flist));
+	PCUT_ASSERT_TRUE(resp.selected);
+	PCUT_ASSERT_EQUALS(flist, resp.selected_file_list);
+	PCUT_ASSERT_STR_EQUALS("hello.txt", resp.selected_fname);
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_activate_req() sends activation request */
+PCUT_TEST(activate_req)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	errno_t rc;
+	test_resp_t resp;
+
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_file_list_set_cb(flist, &test_cb, &resp);
+
+	resp.activate_req = false;
+	resp.activate_req_file_list = NULL;
+
+	ui_file_list_activate_req(flist);
+	PCUT_ASSERT_TRUE(resp.activate_req);
+	PCUT_ASSERT_EQUALS(flist, resp.activate_req_file_list);
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_selected() runs selected callback */
+PCUT_TEST(selected)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	errno_t rc;
+	test_resp_t resp;
+
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_file_list_set_cb(flist, &test_cb, &resp);
+
+	resp.selected = false;
+	resp.selected_file_list = NULL;
+	resp.selected_fname = NULL;
+
+	ui_file_list_selected(flist, "hello.txt");
+	PCUT_ASSERT_TRUE(resp.selected);
+	PCUT_ASSERT_EQUALS(flist, resp.selected_file_list);
+	PCUT_ASSERT_STR_EQUALS("hello.txt", resp.selected_fname);
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_get_cursor() returns the current cursor position */
+PCUT_TEST(get_cursor)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	ui_file_list_entry_attr_t attr;
+	ui_file_list_entry_t *entry;
+	ui_file_list_entry_t *cursor;
+	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 = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_file_list_entry_attr_init(&attr);
+
+	/* Append entry */
+	attr.name = "a";
+	attr.size = 1;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	entry = ui_file_list_first(flist);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	/* Cursor should be at the only entry */
+	cursor = ui_file_list_get_cursor(flist);
+	PCUT_ASSERT_EQUALS(entry, cursor);
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+static void test_file_list_activate_req(ui_file_list_t *flist, void *arg)
+{
+	test_resp_t *resp = (test_resp_t *)arg;
+
+	resp->activate_req = true;
+	resp->activate_req_file_list = flist;
+}
+
+static void test_file_list_selected(ui_file_list_t *flist, void *arg,
+    const char *fname)
+{
+	test_resp_t *resp = (test_resp_t *)arg;
+
+	resp->selected = true;
+	resp->selected_file_list = flist;
+	resp->selected_fname = fname;
+}
+
+PCUT_EXPORT(file_list);
Index: uspace/lib/ui/test/fixed.c
===================================================================
--- uspace/lib/ui/test/fixed.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/test/fixed.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -41,5 +41,5 @@
 static errno_t test_ctl_paint(void *);
 static ui_evclaim_t test_ctl_pos_event(void *, pos_event_t *);
-static void test_ctl_unfocus(void *);
+static void test_ctl_unfocus(void *, unsigned);
 
 static ui_control_ops_t test_ctl_ops = {
@@ -66,4 +66,6 @@
 	/** @c true iff unfocus was called */
 	bool unfocus;
+	/** Number of remaining foci */
+	unsigned unfocus_nfocus;
 } test_resp_t;
 
@@ -253,6 +255,7 @@
 	resp.unfocus = false;
 
-	ui_fixed_unfocus(fixed);
+	ui_fixed_unfocus(fixed, 42);
 	PCUT_ASSERT_TRUE(resp.unfocus);
+	PCUT_ASSERT_INT_EQUALS(42, resp.unfocus_nfocus);
 
 	ui_fixed_destroy(fixed);
@@ -284,9 +287,10 @@
 }
 
-static void test_ctl_unfocus(void *arg)
+static void test_ctl_unfocus(void *arg, unsigned nfocus)
 {
 	test_resp_t *resp = (test_resp_t *) arg;
 
 	resp->unfocus = true;
+	resp->unfocus_nfocus = nfocus;
 }
 
Index: uspace/lib/ui/test/list.c
===================================================================
--- uspace/lib/ui/test/list.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/test/list.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,3087 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <io/kbd_event.h>
+#include <io/pos_event.h>
+#include <pcut/pcut.h>
+#include <stdio.h>
+#include <str.h>
+#include <ui/ui.h>
+#include <ui/list.h>
+#include <ui/scrollbar.h>
+#include <vfs/vfs.h>
+#include "../private/list.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(list);
+
+/** Test response */
+typedef struct {
+	bool activate_req;
+	ui_list_t *activate_req_list;
+
+	bool selected;
+	ui_list_entry_t *selected_entry;
+} test_resp_t;
+
+static void test_list_activate_req(ui_list_t *, void *);
+static void test_list_selected(ui_list_entry_t *, void *);
+static int test_list_compare(ui_list_entry_t *, ui_list_entry_t *);
+
+static ui_list_cb_t test_cb = {
+	.activate_req = test_list_activate_req,
+	.selected = test_list_selected,
+	.compare = test_list_compare
+};
+
+/** Create and destroy file list. */
+PCUT_TEST(create_destroy)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_set_cb() sets callback */
+PCUT_TEST(set_cb)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	errno_t rc;
+	test_resp_t resp;
+
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_set_cb(list, &test_cb, &resp);
+	PCUT_ASSERT_EQUALS(&test_cb, list->cb);
+	PCUT_ASSERT_EQUALS(&resp, list->cb_arg);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_get_cb_arg() returns the callback argument */
+PCUT_TEST(get_cb_arg)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	errno_t rc;
+	test_resp_t resp;
+	void *arg;
+
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_set_cb(list, &test_cb, &resp);
+	arg = ui_list_get_cb_arg(list);
+	PCUT_ASSERT_EQUALS((void *)&resp, arg);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_entry_height() gives the correct height */
+PCUT_TEST(entry_height)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	errno_t rc;
+	gfx_coord_t height;
+
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Font height is 13, padding: 2 (top) + 2 (bottom) */
+	height = ui_list_entry_height(list);
+	PCUT_ASSERT_INT_EQUALS(17, height);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test ui_list_entry_paint() */
+PCUT_TEST(entry_paint)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+	attr.caption = "a";
+	attr.arg = (void *)1;
+
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_entry_paint(ui_list_first(list), 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test ui_list_paint() */
+PCUT_TEST(paint)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_paint(list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_ctl() returns a valid UI control */
+PCUT_TEST(ctl)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_control_t *control;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	control = ui_list_ctl(list);
+	PCUT_ASSERT_NOT_NULL(control);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test ui_list_kbd_event() */
+PCUT_TEST(kbd_event)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_evclaim_t claimed;
+	kbd_event_t event;
+	errno_t rc;
+
+	/* Active list should claim events */
+
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	event.type = KEY_PRESS;
+	event.key = KC_ESCAPE;
+	event.mods = 0;
+	event.c = '\0';
+
+	claimed = ui_list_kbd_event(list, &event);
+	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
+
+	ui_list_destroy(list);
+
+	/* Inactive list should not claim events */
+
+	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 = ui_list_create(window, false, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	event.type = KEY_PRESS;
+	event.key = KC_ESCAPE;
+	event.mods = 0;
+	event.c = '\0';
+
+	claimed = ui_list_kbd_event(list, &event);
+	PCUT_ASSERT_EQUALS(ui_unclaimed, claimed);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test ui_list_pos_event() */
+PCUT_TEST(pos_event)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_evclaim_t claimed;
+	pos_event_t event;
+	gfx_rect_t rect;
+	ui_list_entry_attr_t attr;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 50;
+	rect.p1.y = 220;
+
+	ui_list_set_rect(list, &rect);
+
+	ui_list_entry_attr_init(&attr);
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	list->cursor = ui_list_first(list);
+	list->cursor_idx = 0;
+	list->page = ui_list_first(list);
+	list->page_idx = 0;
+
+	event.pos_id = 0;
+	event.type = POS_PRESS;
+	event.btn_num = 1;
+
+	/* Clicking on the middle entry should select it */
+	event.hpos = 20;
+	event.vpos = 40;
+
+	claimed = ui_list_pos_event(list, &event);
+	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
+
+	PCUT_ASSERT_NOT_NULL(list->cursor);
+	PCUT_ASSERT_STR_EQUALS("b", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->cursor->arg);
+
+	/* Clicking on the top edge should do a page-up */
+	event.hpos = 20;
+	event.vpos = 20;
+	claimed = ui_list_pos_event(list, &event);
+	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
+
+	PCUT_ASSERT_NOT_NULL(list->cursor);
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_set_rect() sets internal field */
+PCUT_TEST(set_rect)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	gfx_rect_t rect;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 1;
+	rect.p0.y = 2;
+	rect.p1.x = 3;
+	rect.p1.y = 4;
+
+	ui_list_set_rect(list, &rect);
+	PCUT_ASSERT_INT_EQUALS(rect.p0.x, list->rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(rect.p0.y, list->rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(rect.p1.x, list->rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(rect.p1.y, list->rect.p1.y);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_page_size() returns correct size */
+PCUT_TEST(page_size)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	gfx_rect_t rect;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 50;
+	rect.p1.y = 220;
+
+	ui_list_set_rect(list, &rect);
+
+	/* NOTE If page size changes, we have problems elsewhere in the tests */
+	PCUT_ASSERT_INT_EQUALS(11, ui_list_page_size(list));
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_inside_rect() gives correct interior rectangle */
+PCUT_TEST(inside_rect)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	gfx_rect_t rect;
+	gfx_rect_t irect;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 50;
+	rect.p1.y = 220;
+
+	ui_list_set_rect(list, &rect);
+
+	ui_list_inside_rect(list, &irect);
+	PCUT_ASSERT_INT_EQUALS(10 + 2, irect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(20 + 2, irect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(50 - 2 - 23, irect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(220 - 2, irect.p1.y);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_scrollbar_rect() gives correct scrollbar rectangle */
+PCUT_TEST(scrollbar_rect)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	gfx_rect_t rect;
+	gfx_rect_t srect;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 50;
+	rect.p1.y = 220;
+
+	ui_list_set_rect(list, &rect);
+
+	ui_list_scrollbar_rect(list, &srect);
+	PCUT_ASSERT_INT_EQUALS(50 - 2 - 23, srect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(20 + 2, srect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(50 - 2, srect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(220 - 2, srect.p1.y);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_scrollbar_update() updates scrollbar position */
+PCUT_TEST(scrollbar_update)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	gfx_rect_t rect;
+	ui_list_entry_attr_t attr;
+	ui_list_entry_t *entry;
+	gfx_coord_t pos;
+	gfx_coord_t move_len;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 50;
+	rect.p1.y = 38;
+
+	ui_list_set_rect(list, &rect);
+
+	ui_list_entry_attr_init(&attr);
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	entry = ui_list_next(ui_list_first(list));
+
+	list->cursor = entry;
+	list->cursor_idx = 1;
+	list->page = entry;
+	list->page_idx = 1;
+
+	ui_list_scrollbar_update(list);
+
+	/* Now scrollbar thumb should be all the way down */
+	move_len = ui_scrollbar_move_length(list->scrollbar);
+	pos = ui_scrollbar_get_pos(list->scrollbar);
+	PCUT_ASSERT_INT_EQUALS(move_len, pos);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_is_active() returns list activity state */
+PCUT_TEST(is_active)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_TRUE(ui_list_is_active(list));
+	ui_list_destroy(list);
+
+	rc = ui_list_create(window, false, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_FALSE(ui_list_is_active(list));
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_activate() activates list */
+PCUT_TEST(activate)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	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 = ui_list_create(window, false, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(ui_list_is_active(list));
+	rc = ui_list_activate(list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_TRUE(ui_list_is_active(list));
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_deactivate() deactivates list */
+PCUT_TEST(deactivate)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_TRUE(ui_list_is_active(list));
+	ui_list_deactivate(list);
+	PCUT_ASSERT_FALSE(ui_list_is_active(list));
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_get_cursor() returns the current cursor position */
+PCUT_TEST(get_cursor)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	ui_list_entry_t *entry;
+	ui_list_entry_t *cursor;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Append entry and get pointer to it */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	entry = NULL;
+	rc = ui_list_entry_append(list, &attr, &entry);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	/* Cursor should be at the only entry */
+	cursor = ui_list_get_cursor(list);
+	PCUT_ASSERT_EQUALS(entry, cursor);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_set_cursor() sets list cursor position */
+PCUT_TEST(set_cursor)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	ui_list_entry_t *e1;
+	ui_list_entry_t *e2;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Append entry and get pointer to it */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, &e1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(e1);
+
+	/* Append entry and get pointer to it */
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, &e2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(e2);
+
+	/* Append entry */
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor should be at the first entry */
+	PCUT_ASSERT_EQUALS(e1, list->cursor);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+
+	/* Set cursor to the second entry */
+	ui_list_set_cursor(list, e2);
+	PCUT_ASSERT_EQUALS(e2, list->cursor);
+	PCUT_ASSERT_INT_EQUALS(1, list->cursor_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_entry_attr_init() initializes entry attribute structure */
+PCUT_TEST(entry_attr_init)
+{
+	ui_list_entry_attr_t attr;
+
+	ui_list_entry_attr_init(&attr);
+	PCUT_ASSERT_NULL(attr.caption);
+	PCUT_ASSERT_NULL(attr.arg);
+	PCUT_ASSERT_NULL(attr.color);
+	PCUT_ASSERT_NULL(attr.bgcolor);
+}
+
+/** ui_list_entry_append() appends new entry */
+PCUT_TEST(entry_append)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	ui_list_entry_t *entry;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Append entry without retrieving pointer to it */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(1, list_count(&list->entries));
+
+	/* Append entry and get pointer to it */
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	entry = NULL;
+	rc = ui_list_entry_append(list, &attr, &entry);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_EQUALS(attr.arg, entry->arg);
+
+	PCUT_ASSERT_INT_EQUALS(2, list_count(&list->entries));
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_entry_move_up() moves entry up */
+PCUT_TEST(entry_move_up)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	ui_list_entry_t *e1, *e2, *e3;
+	ui_list_entry_t *e;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Create entries */
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, &e1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, &e2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, &e3);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	e = ui_list_first(list);
+	PCUT_ASSERT_EQUALS(e1, e);
+
+	/* Moving first entry up should have no effect */
+	ui_list_entry_move_up(e1);
+
+	e = ui_list_first(list);
+	PCUT_ASSERT_EQUALS(e1, e);
+
+	e = ui_list_next(e);
+	PCUT_ASSERT_EQUALS(e2, e);
+
+	e = ui_list_next(e);
+	PCUT_ASSERT_EQUALS(e3, e);
+
+	e = ui_list_next(e);
+	PCUT_ASSERT_NULL(e);
+
+	/* Move second entry up */
+	ui_list_entry_move_up(e2);
+
+	e = ui_list_first(list);
+	PCUT_ASSERT_EQUALS(e2, e);
+
+	e = ui_list_next(e);
+	PCUT_ASSERT_EQUALS(e1, e);
+
+	e = ui_list_next(e);
+	PCUT_ASSERT_EQUALS(e3, e);
+
+	e = ui_list_next(e);
+	PCUT_ASSERT_NULL(e);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_entry_move_down() moves entry down */
+PCUT_TEST(entry_move_down)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	ui_list_entry_t *e1, *e2, *e3;
+	ui_list_entry_t *e;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Create entries */
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, &e1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, &e2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, &e3);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	e = ui_list_first(list);
+	PCUT_ASSERT_EQUALS(e1, e);
+
+	/* Moving last entry down should have no effect */
+	ui_list_entry_move_down(e3);
+
+	e = ui_list_first(list);
+	PCUT_ASSERT_EQUALS(e1, e);
+
+	e = ui_list_next(e);
+	PCUT_ASSERT_EQUALS(e2, e);
+
+	e = ui_list_next(e);
+	PCUT_ASSERT_EQUALS(e3, e);
+
+	e = ui_list_next(e);
+	PCUT_ASSERT_NULL(e);
+
+	/* Move second-to-last entry down */
+	ui_list_entry_move_down(e2);
+
+	e = ui_list_first(list);
+	PCUT_ASSERT_EQUALS(e1, e);
+
+	e = ui_list_next(e);
+	PCUT_ASSERT_EQUALS(e3, e);
+
+	e = ui_list_next(e);
+	PCUT_ASSERT_EQUALS(e2, e);
+
+	e = ui_list_next(e);
+	PCUT_ASSERT_NULL(e);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_entry_delete() deletes entry */
+PCUT_TEST(entry_delete)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *entry;
+	ui_list_entry_attr_t attr;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(2, list_count(&list->entries));
+
+	entry = ui_list_first(list);
+	ui_list_entry_delete(entry);
+
+	PCUT_ASSERT_INT_EQUALS(1, list_count(&list->entries));
+
+	entry = ui_list_first(list);
+	ui_list_entry_delete(entry);
+
+	PCUT_ASSERT_INT_EQUALS(0, list_count(&list->entries));
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_entry_get_arg() gets entry argument */
+PCUT_TEST(entry_get_arg)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	ui_list_entry_t *entry;
+	void *arg;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Append entry and get pointer to it */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	entry = NULL;
+	rc = ui_list_entry_append(list, &attr, &entry);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	arg = ui_list_entry_get_arg(entry);
+	PCUT_ASSERT_EQUALS(attr.arg, arg);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_entry_get_list() returns the containing list */
+PCUT_TEST(entry_get_list)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_t *elist;
+	ui_list_entry_attr_t attr;
+	ui_list_entry_t *entry;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Append entry and get pointer to it */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	entry = NULL;
+	rc = ui_list_entry_append(list, &attr, &entry);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	/* Get the containing list */
+	elist = ui_list_entry_get_list(entry);
+	PCUT_ASSERT_EQUALS(list, elist);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_entry_set_caption() sets entry captino */
+PCUT_TEST(entry_set_caption)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	ui_list_entry_t *entry;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Append entry and get pointer to it */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	entry = NULL;
+	rc = ui_list_entry_append(list, &attr, &entry);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	/* Change caption */
+	rc = ui_list_entry_set_caption(entry, "b");
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_STR_EQUALS("b", entry->caption);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_entries_cnt() returns the number of entries */
+PCUT_TEST(entries_cnt)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(0, ui_list_entries_cnt(list));
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Append entry */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(1, ui_list_entries_cnt(list));
+
+	/* Append another entry */
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_entries_cnt(list));
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_sort() sorts UI list entries */
+PCUT_TEST(sort)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *entry;
+	ui_list_entry_attr_t attr;
+	test_resp_t resp;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_set_cb(list, &test_cb, &resp);
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "b";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "a";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_sort(list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	entry = ui_list_first(list);
+	PCUT_ASSERT_STR_EQUALS("a", entry->caption);
+	PCUT_ASSERT_EQUALS((void *)2, entry->arg);
+
+	entry = ui_list_next(entry);
+	PCUT_ASSERT_STR_EQUALS("b", entry->caption);
+	PCUT_ASSERT_EQUALS((void *)1, entry->arg);
+
+	entry = ui_list_next(entry);
+	PCUT_ASSERT_STR_EQUALS("c", entry->caption);
+	PCUT_ASSERT_EQUALS((void *)3, entry->arg);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_cursor_center()...XXX */
+PCUT_TEST(cursor_center)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *a, *b, *c, *d, *e;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	test_resp_t resp;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_set_cb(list, &test_cb, &resp);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 50;
+	rect.p1.y = 80;
+
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(3, ui_list_page_size(list));
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, &a);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, &b);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* We only have two entries, but three fit onto the page */
+	ui_list_cursor_center(list, b);
+	PCUT_ASSERT_EQUALS(b, list->cursor);
+	/* Page should start at the beginning */
+	PCUT_ASSERT_EQUALS(a, list->page);
+	PCUT_ASSERT_EQUALS(0, list->page_idx);
+
+	/* Add more entries */
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, &c);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "d";
+	attr.arg = (void *)4;
+	rc = ui_list_entry_append(list, &attr, &d);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "e";
+	attr.arg = (void *)5;
+	rc = ui_list_entry_append(list, &attr, &e);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_cursor_center(list, c);
+	PCUT_ASSERT_EQUALS(c, list->cursor);
+	/*
+	 * We have enough entries, c should be in the middle of the three
+	 * entries on the page, i.e., page should start on 'b'.
+	 */
+	PCUT_ASSERT_EQUALS(b, list->page);
+	PCUT_ASSERT_EQUALS(1, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_clear_entries() removes all entries from list */
+PCUT_TEST(clear_entries)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "a";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(2, list_count(&list->entries));
+
+	ui_list_clear_entries(list);
+	PCUT_ASSERT_INT_EQUALS(0, list_count(&list->entries));
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_first() returns valid entry or @c NULL as appropriate */
+PCUT_TEST(first)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *entry;
+	ui_list_entry_attr_t attr;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	entry = ui_list_first(list);
+	PCUT_ASSERT_NULL(entry);
+
+	/* Add one entry */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Now try getting it */
+	entry = ui_list_first(list);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("a", entry->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)entry->arg);
+
+	/* Add another entry */
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* We should still get the first entry */
+	entry = ui_list_first(list);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("a", entry->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)entry->arg);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_last() returns valid entry or @c NULL as appropriate */
+PCUT_TEST(last)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *entry;
+	ui_list_entry_attr_t attr;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	entry = ui_list_last(list);
+	PCUT_ASSERT_NULL(entry);
+
+	/* Add one entry */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Now try getting it */
+	entry = ui_list_last(list);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("a", entry->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)entry->arg);
+
+	/* Add another entry */
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* We should get new entry now */
+	entry = ui_list_last(list);
+	PCUT_ASSERT_NOT_NULL(entry);
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	PCUT_ASSERT_STR_EQUALS("b", entry->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)entry->arg);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_next() returns the next entry or @c NULL as appropriate */
+PCUT_TEST(next)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *entry;
+	ui_list_entry_attr_t attr;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Add one entry */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Now try getting its successor */
+	entry = ui_list_first(list);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	entry = ui_list_next(entry);
+	PCUT_ASSERT_NULL(entry);
+
+	/* Add another entry */
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Try getting the successor of first entry again */
+	entry = ui_list_first(list);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	entry = ui_list_next(entry);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("b", entry->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)entry->arg);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_prev() returns the previous entry or @c NULL as appropriate */
+PCUT_TEST(prev)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *entry;
+	ui_list_entry_attr_t attr;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Add one entry */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Now try getting its predecessor */
+	entry = ui_list_last(list);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	entry = ui_list_prev(entry);
+	PCUT_ASSERT_NULL(entry);
+
+	/* Add another entry */
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Try getting the predecessor of the new entry */
+	entry = ui_list_last(list);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	entry = ui_list_prev(entry);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("a", entry->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)entry->arg);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_page_nth_entry() .. */
+PCUT_TEST(page_nth_entry)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *entry;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	size_t idx;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Add some entries */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	list->page = ui_list_next(ui_list_first(list));
+	list->page_idx = 1;
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 100;
+	rect.p1.y = 100;
+	ui_list_set_rect(list, &rect);
+
+	entry = ui_list_page_nth_entry(list, 0, &idx);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("b", entry->caption);
+	PCUT_ASSERT_INT_EQUALS(1, idx);
+
+	entry = ui_list_page_nth_entry(list, 1, &idx);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("c", entry->caption);
+	PCUT_ASSERT_INT_EQUALS(2, idx);
+
+	entry = ui_list_page_nth_entry(list, 2, &idx);
+	PCUT_ASSERT_NULL(entry);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_cursor_move() moves cursor and scrolls */
+PCUT_TEST(cursor_move)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add tree entries (more than page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor to the last entry and page start to the next-to-last entry */
+	list->cursor = ui_list_last(list);
+	list->cursor_idx = 2;
+	list->page = ui_list_prev(list->cursor);
+	list->page_idx = 1;
+
+	/* Move cursor one entry up */
+	ui_list_cursor_move(list, ui_list_prev(list->cursor),
+	    list->cursor_idx - 1);
+
+	/* Cursor and page start should now both be at the second entry */
+	PCUT_ASSERT_STR_EQUALS("b", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->cursor_idx);
+	PCUT_ASSERT_EQUALS(list->cursor, list->page);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	/* Move cursor to the first entry. This should scroll up. */
+	ui_list_cursor_move(list, ui_list_first(list), 0);
+
+	/* Cursor and page start should now both be at the first entry */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_EQUALS(list->cursor, list->page);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	/* Move cursor to the last entry. */
+	ui_list_cursor_move(list, ui_list_last(list), 2);
+
+	/* Cursor should be on the last entry and page on the next to last */
+	PCUT_ASSERT_STR_EQUALS("c", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_cursor_up() moves cursor one entry up */
+PCUT_TEST(cursor_up)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add tree entries (more than page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor to the last entry and page start to the next-to-last entry */
+	list->cursor = ui_list_last(list);
+	list->cursor_idx = 2;
+	list->page = ui_list_prev(list->cursor);
+	list->page_idx = 1;
+
+	/* Move cursor one entry up */
+	ui_list_cursor_up(list);
+
+	/* Cursor and page start should now both be at the second entry */
+	PCUT_ASSERT_STR_EQUALS("b", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->cursor_idx);
+	PCUT_ASSERT_EQUALS(list->cursor, list->page);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	/* Move cursor one entry up. This should scroll up. */
+	ui_list_cursor_up(list);
+
+	/* Cursor and page start should now both be at the first entry */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_EQUALS(list->cursor, list->page);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	/* Moving further up should do nothing (we are at the top). */
+	ui_list_cursor_up(list);
+
+	/* Cursor and page start should still be at the first entry */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_EQUALS(list->cursor, list->page);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_cursor_down() moves cursor one entry down */
+PCUT_TEST(cursor_down)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add tree entries (more than page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor and page start to the first entry */
+	list->cursor = ui_list_first(list);
+	list->cursor_idx = 0;
+	list->page = list->cursor;
+	list->page_idx = 0;
+
+	/* Move cursor one entry down */
+	ui_list_cursor_down(list);
+
+	/* Cursor should now be at the second entry, page stays the same */
+	PCUT_ASSERT_STR_EQUALS("b", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->cursor_idx);
+	PCUT_ASSERT_EQUALS(ui_list_first(list), list->page);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	/* Move cursor one entry down. This should scroll down. */
+	ui_list_cursor_down(list);
+
+	/* Cursor should now be at the third and page at the second entry. */
+	PCUT_ASSERT_STR_EQUALS("c", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	/* Moving further down should do nothing (we are at the bottom). */
+	ui_list_cursor_down(list);
+
+	/* Cursor should still be at the third and page at the second entry. */
+	PCUT_ASSERT_STR_EQUALS("c", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_cursor_top() moves cursor to the first entry */
+PCUT_TEST(cursor_top)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add tree entries (more than page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor to the last entry and page start to the next-to-last entry */
+	list->cursor = ui_list_last(list);
+	list->cursor_idx = 2;
+	list->page = ui_list_prev(list->cursor);
+	list->page_idx = 1;
+
+	/* Move cursor to the top. This should scroll up. */
+	ui_list_cursor_top(list);
+
+	/* Cursor and page start should now both be at the first entry */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_EQUALS(list->cursor, list->page);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_cursor_bottom() moves cursor to the last entry */
+PCUT_TEST(cursor_bottom)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add tree entries (more than page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor and page start to the first entry */
+	list->cursor = ui_list_first(list);
+	list->cursor_idx = 0;
+	list->page = list->cursor;
+	list->page_idx = 0;
+
+	/* Move cursor to the bottom. This should scroll down. */
+	ui_list_cursor_bottom(list);
+
+	/* Cursor should now be at the third and page at the second entry. */
+	PCUT_ASSERT_STR_EQUALS("c", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_page_up() moves one page up */
+PCUT_TEST(page_up)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add five entries (2 full pages, one partial) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "d";
+	attr.arg = (void *)4;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "e";
+	attr.arg = (void *)5;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor to the last entry and page start to the next-to-last entry */
+	list->cursor = ui_list_last(list);
+	list->cursor_idx = 4;
+	list->page = ui_list_prev(list->cursor);
+	list->page_idx = 3;
+
+	/* Move one page up */
+	ui_list_page_up(list);
+
+	/* Page should now start at second entry and cursor at third */
+	PCUT_ASSERT_STR_EQUALS("c", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	/* Move one page up again. */
+	ui_list_page_up(list);
+
+	/* Cursor and page start should now both be at the first entry */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_EQUALS(list->cursor, list->page);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	/* Moving further up should do nothing (we are at the top). */
+	ui_list_page_up(list);
+
+	/* Cursor and page start should still be at the first entry */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_EQUALS(list->cursor, list->page);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_page_up() moves one page down */
+PCUT_TEST(page_down)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add five entries (2 full pages, one partial) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "d";
+	attr.arg = (void *)4;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "e";
+	attr.arg = (void *)5;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor and page to the first entry */
+	list->cursor = ui_list_first(list);
+	list->cursor_idx = 0;
+	list->page = list->cursor;
+	list->page_idx = 0;
+
+	/* Move one page down */
+	ui_list_page_down(list);
+
+	/* Page and cursor should point to the third entry */
+	PCUT_ASSERT_STR_EQUALS("c", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("c", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->page_idx);
+
+	/* Move one page down again. */
+	ui_list_page_down(list);
+
+	/* Cursor should point to last and page to next-to-last entry */
+	PCUT_ASSERT_STR_EQUALS("e", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(5, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(4, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("d", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(4, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(3, list->page_idx);
+
+	/* Moving further down should do nothing (we are at the bottom). */
+	ui_list_page_down(list);
+
+	/* Cursor should still point to last and page to next-to-last entry */
+	PCUT_ASSERT_STR_EQUALS("e", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(5, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(4, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("d", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(4, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(3, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_scroll_up() scrolls up by one row */
+PCUT_TEST(scroll_up)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add tree entries (more than page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor to the last entry, page to the second */
+	list->cursor = ui_list_last(list);
+	list->cursor_idx = 2;
+	list->page = ui_list_prev(list->cursor);
+	list->page_idx = 1;
+
+	/* Scroll one entry up */
+	ui_list_scroll_up(list);
+
+	/* Page should start on the first entry, cursor unchanged */
+	PCUT_ASSERT_STR_EQUALS("c", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("a", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	/* Try scrolling one more entry up */
+	ui_list_scroll_up(list);
+
+	/* We were at the beginning, so nothing should have changed  */
+	PCUT_ASSERT_STR_EQUALS("c", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("a", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_scroll_down() scrolls down by one row */
+PCUT_TEST(scroll_down)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add tree entries (more than page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor and page start to the first entry */
+	list->cursor = ui_list_first(list);
+	list->cursor_idx = 0;
+	list->page = list->cursor;
+	list->page_idx = 0;
+
+	/* Scroll one entry down */
+	ui_list_scroll_down(list);
+
+	/* Page should start on the second entry, cursor unchanged */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	/* Try scrolling one more entry down */
+	ui_list_scroll_down(list);
+
+	/* We were at the end, so nothing should have changed  */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_scroll_page_up() scrolls up by one page */
+PCUT_TEST(scroll_page_up)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add five entries (more than twice the page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "d";
+	attr.arg = (void *)4;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "e";
+	attr.arg = (void *)5;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor to the last entry, page to the second last */
+	list->cursor = ui_list_last(list);
+	list->cursor_idx = 4;
+	list->page = ui_list_prev(list->cursor);
+	list->page_idx = 3;
+
+	/* Scroll one page up */
+	ui_list_scroll_page_up(list);
+
+	/* Page should start on 'b', cursor unchanged */
+	PCUT_ASSERT_STR_EQUALS("e", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(5, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(4, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	/* Page up again */
+	ui_list_scroll_page_up(list);
+
+	/* Page should now be at the beginning, cursor unchanged */
+	PCUT_ASSERT_STR_EQUALS("e", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(5, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(4, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("a", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	/* Page up again */
+	ui_list_scroll_page_up(list);
+
+	/* We were at the beginning, nothing should have changed */
+	PCUT_ASSERT_STR_EQUALS("e", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(5, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(4, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("a", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_scroll_page_up() scrolls down by one page */
+PCUT_TEST(scroll_page_down)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add five entries (more than twice the page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "d";
+	attr.arg = (void *)4;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "e";
+	attr.arg = (void *)5;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor and page to the first entry */
+	list->cursor = ui_list_first(list);
+	list->cursor_idx = 0;
+	list->page = ui_list_first(list);
+	list->page_idx = 0;
+
+	/* Scroll one page down */
+	ui_list_scroll_page_down(list);
+
+	/* Page should start on 'c', cursor unchanged */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("c", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->page_idx);
+
+	/* Page down again */
+	ui_list_scroll_page_down(list);
+
+	/* Page should now start at 'd', cursor unchanged */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("d", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(4, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(3, list->page_idx);
+
+	/* Page down again */
+	ui_list_scroll_page_down(list);
+
+	/* We were at the end, nothing should have changed */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("d", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(4, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(3, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_scroll_pos() scrolls to a particular entry */
+PCUT_TEST(scroll_pos)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add five entries (more than twice the page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "d";
+	attr.arg = (void *)4;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "e";
+	attr.arg = (void *)5;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor and page to the first entry */
+	list->cursor = ui_list_first(list);
+	list->cursor_idx = 0;
+	list->page = ui_list_first(list);
+	list->page_idx = 0;
+
+	/* Scroll to entry 1 (one down) */
+	ui_list_scroll_pos(list, 1);
+
+	/* Page should start on 'b', cursor unchanged */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	/* Scroll to entry 3 (i.e. the end) */
+	ui_list_scroll_pos(list, 3);
+
+	/* Page should now start at 'd', cursor unchanged */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("d", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(4, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(3, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_activate_req() sends activation request */
+PCUT_TEST(activate_req)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	errno_t rc;
+	test_resp_t resp;
+
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_set_cb(list, &test_cb, &resp);
+
+	resp.activate_req = false;
+	resp.activate_req_list = NULL;
+
+	ui_list_activate_req(list);
+	PCUT_ASSERT_TRUE(resp.activate_req);
+	PCUT_ASSERT_EQUALS(list, resp.activate_req_list);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_selected() runs selected callback */
+PCUT_TEST(selected)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	ui_list_entry_t *entry;
+	errno_t rc;
+	test_resp_t resp;
+
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_set_cb(list, &test_cb, &resp);
+
+	ui_list_entry_attr_init(&attr);
+	attr.caption = "Hello";
+	attr.arg = &resp;
+
+	rc = ui_list_entry_append(list, &attr, &entry);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	resp.selected = false;
+	resp.selected_entry = NULL;
+
+	ui_list_selected(entry);
+	PCUT_ASSERT_TRUE(resp.selected);
+	PCUT_ASSERT_EQUALS(entry, resp.selected_entry);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_entry_ptr_cmp compares two indirectly referenced entries */
+PCUT_TEST(entry_ptr_cmp)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *a, *b;
+	ui_list_entry_attr_t attr;
+	test_resp_t resp;
+	int rel;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_set_cb(list, &test_cb, &resp);
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	a = ui_list_first(list);
+	PCUT_ASSERT_NOT_NULL(a);
+	b = ui_list_next(a);
+	PCUT_ASSERT_NOT_NULL(b);
+
+	/* a < b */
+	rel = ui_list_entry_ptr_cmp(&a, &b);
+	PCUT_ASSERT_TRUE(rel < 0);
+
+	/* b > a */
+	rel = ui_list_entry_ptr_cmp(&b, &a);
+	PCUT_ASSERT_TRUE(rel > 0);
+
+	/* a == a */
+	rel = ui_list_entry_ptr_cmp(&a, &a);
+	PCUT_ASSERT_INT_EQUALS(0, rel);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_entry_get_idx() returns entry index */
+PCUT_TEST(entry_get_idx)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *a, *b;
+	ui_list_entry_attr_t attr;
+	test_resp_t resp;
+	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 = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_set_cb(list, &test_cb, &resp);
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, &a);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, &b);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(0, ui_list_entry_get_idx(a));
+	PCUT_ASSERT_INT_EQUALS(1, ui_list_entry_get_idx(b));
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+static void test_list_activate_req(ui_list_t *list, void *arg)
+{
+	test_resp_t *resp = (test_resp_t *)arg;
+
+	resp->activate_req = true;
+	resp->activate_req_list = list;
+}
+
+static void test_list_selected(ui_list_entry_t *entry, void *arg)
+{
+	test_resp_t *resp = (test_resp_t *)arg;
+
+	resp->selected = true;
+	resp->selected_entry = entry;
+}
+
+static int test_list_compare(ui_list_entry_t *a, ui_list_entry_t *b)
+{
+	return str_cmp(a->caption, b->caption);
+}
+
+PCUT_EXPORT(list);
Index: uspace/lib/ui/test/main.c
===================================================================
--- uspace/lib/ui/test/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -31,13 +31,18 @@
 PCUT_INIT;
 
+PCUT_IMPORT(accel);
 PCUT_IMPORT(control);
 PCUT_IMPORT(checkbox);
+PCUT_IMPORT(clickmatic);
 PCUT_IMPORT(entry);
 PCUT_IMPORT(file_dialog);
+PCUT_IMPORT(file_list);
 PCUT_IMPORT(fixed);
 PCUT_IMPORT(image);
 PCUT_IMPORT(label);
+PCUT_IMPORT(list);
 PCUT_IMPORT(menu);
 PCUT_IMPORT(menubar);
+PCUT_IMPORT(menudd);
 PCUT_IMPORT(menuentry);
 PCUT_IMPORT(msg_dialog);
@@ -47,5 +52,10 @@
 PCUT_IMPORT(rbutton);
 PCUT_IMPORT(resource);
+PCUT_IMPORT(scrollbar);
+PCUT_IMPORT(select_dialog);
 PCUT_IMPORT(slider);
+PCUT_IMPORT(tab);
+PCUT_IMPORT(tabset);
+PCUT_IMPORT(testctl);
 PCUT_IMPORT(ui);
 PCUT_IMPORT(wdecor);
Index: uspace/lib/ui/test/menu.c
===================================================================
--- uspace/lib/ui/test/menu.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/test/menu.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -36,4 +36,5 @@
 #include <ui/menu.h>
 #include <ui/menubar.h>
+#include <ui/menuentry.h>
 #include <ui/ui.h>
 #include <ui/window.h>
@@ -45,25 +46,40 @@
 PCUT_TEST_SUITE(menu);
 
+typedef struct {
+	bool left_called;
+	bool right_called;
+	bool close_req_called;
+	bool press_accel_called;
+	ui_menu_t *menu;
+	sysarg_t idev_id;
+	char32_t c;
+} test_resp_t;
+
+static void testmenu_left(ui_menu_t *, void *, sysarg_t);
+static void testmenu_right(ui_menu_t *, void *, sysarg_t);
+static void testmenu_close_req(ui_menu_t *, void *);
+static void testmenu_press_accel(ui_menu_t *, void *, char32_t, sysarg_t);
+
+ui_menu_cb_t testmenu_cb = {
+	.left = testmenu_left,
+	.right = testmenu_right,
+	.close_req = testmenu_close_req,
+	.press_accel = testmenu_press_accel
+};
+
+ui_menu_cb_t dummy_cb = {
+};
+
 /** Create and destroy menu */
 PCUT_TEST(create_destroy)
 {
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	errno_t rc;
-
-	rc = ui_menu_bar_create(NULL, NULL, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	/*
-	 * Normally we don't need to destroy a menu explicitly, it will
-	 * be destroyed along with menu bar, but here we'll test destroying
-	 * it explicitly.
-	 */
-	ui_menu_destroy(menu);
-	ui_menu_bar_destroy(mbar);
+	ui_menu_t *menu = NULL;
+	errno_t rc;
+
+	rc = ui_menu_create(NULL, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	ui_menu_destroy(menu);
 }
 
@@ -74,328 +90,21 @@
 }
 
-/** ui_menu_first() / ui_menu_next() iterate over menus */
-PCUT_TEST(first_next)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu1 = NULL;
-	ui_menu_t *menu2 = NULL;
-	ui_menu_t *m;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test 1", &menu1);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu1);
-
-	rc = ui_menu_create(mbar, "Test 1", &menu2);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu2);
-
-	m = ui_menu_first(mbar);
-	PCUT_ASSERT_EQUALS(menu1, m);
-
-	m = ui_menu_next(m);
-	PCUT_ASSERT_EQUALS(menu2, m);
-
-	m = ui_menu_next(m);
-	PCUT_ASSERT_NULL(m);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_menu_caption() returns the menu's caption */
-PCUT_TEST(caption)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	const char *caption;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	caption = ui_menu_caption(menu);
-	PCUT_ASSERT_NOT_NULL(caption);
-
-	PCUT_ASSERT_INT_EQUALS(0, str_cmp(caption, "Test"));
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_menu_get_rect() returns outer menu rectangle */
-PCUT_TEST(get_rect)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	gfx_coord2_t pos;
-	gfx_rect_t rect;
-	const char *caption;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	caption = ui_menu_caption(menu);
-	PCUT_ASSERT_NOT_NULL(caption);
-
-	pos.x = 0;
-	pos.y = 0;
-	ui_menu_get_rect(menu, &pos, &rect);
-
-	PCUT_ASSERT_INT_EQUALS(0, rect.p0.x);
-	PCUT_ASSERT_INT_EQUALS(0, rect.p0.y);
-	PCUT_ASSERT_INT_EQUALS(16, rect.p1.x);
-	PCUT_ASSERT_INT_EQUALS(8, rect.p1.y);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** Paint background in graphics mode */
-PCUT_TEST(paint_bg_gfx)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	gfx_rect_t prect;
-	gfx_coord2_t pos;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	prect.p0.x = 0;
-	prect.p0.y = 0;
-	prect.p1.x = 0;
-	prect.p1.y = 0;
-
-	/* Menu needs to be open to be able to paint it */
-	rc = ui_menu_open(menu, &prect);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	pos.x = 0;
-	pos.y = 0;
-	rc = ui_menu_paint_bg_gfx(menu, &pos);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** Paint background in text mode */
-PCUT_TEST(paint_bg_text)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	gfx_rect_t prect;
-	gfx_coord2_t pos;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	prect.p0.x = 0;
-	prect.p0.y = 0;
-	prect.p1.x = 0;
-	prect.p1.y = 0;
-
-	/* Menu needs to be open to be able to paint it */
-	rc = ui_menu_open(menu, &prect);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	pos.x = 0;
-	pos.y = 0;
-	rc = ui_menu_paint_bg_text(menu, &pos);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** Paint menu */
-PCUT_TEST(paint)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	gfx_rect_t prect;
-	gfx_coord2_t pos;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	prect.p0.x = 0;
-	prect.p0.y = 0;
-	prect.p1.x = 0;
-	prect.p1.y = 0;
-
-	/* Menu needs to be open to be able to paint it */
-	rc = ui_menu_open(menu, &prect);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	pos.x = 0;
-	pos.y = 0;
-	rc = ui_menu_paint(menu, &pos);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_menu_pos_event() inside menu is claimed */
-PCUT_TEST(pos_event_inside)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	ui_evclaim_t claimed;
-	gfx_coord2_t pos;
-	pos_event_t event;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	pos.x = 0;
-	pos.y = 0;
-	event.type = POS_PRESS;
-	event.hpos = 0;
-	event.vpos = 0;
-	claimed = ui_menu_pos_event(menu, &pos, &event);
-	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
+/** ui_menu_set_cb() sets the internal fields */
+PCUT_TEST(set_cb)
+{
+	ui_menu_t *menu = NULL;
+	ui_menu_cb_t cb;
+	int obj;
+	errno_t rc;
+
+	rc = ui_menu_create(NULL, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	ui_menu_set_cb(menu, &cb, (void *)&obj);
+	PCUT_ASSERT_EQUALS(&cb, menu->cb);
+	PCUT_ASSERT_EQUALS((void *)&obj, menu->arg);
+
+	ui_menu_destroy(menu);
 }
 
@@ -406,5 +115,4 @@
 	ui_window_t *window = NULL;
 	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
 	ui_menu_t *menu = NULL;
 	ui_menu_geom_t geom;
@@ -422,9 +130,5 @@
 	PCUT_ASSERT_NOT_NULL(window);
 
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_create(window, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -443,7 +147,695 @@
 	PCUT_ASSERT_INT_EQUALS(4, geom.entries_rect.p1.y);
 
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
+	ui_menu_destroy(menu);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_get_res() gets the menu's resource */
+PCUT_TEST(get_res)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	ui_resource_t *res;
+	gfx_rect_t prect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* The menu must be open first */
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	res = ui_menu_get_res(menu);
+	PCUT_ASSERT_NOT_NULL(res);
+
+	ui_menu_destroy(menu);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Open and close menu with ui_menu_open() / ui_menu_close() */
+PCUT_TEST(open_close)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	gfx_rect_t prect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Open and close */
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_menu_close(menu);
+
+	ui_menu_destroy(menu);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_is_open() correctly returns menu state */
+PCUT_TEST(is_open)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	gfx_rect_t prect;
+	bool open;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	open = ui_menu_is_open(menu);
+	PCUT_ASSERT_FALSE(open);
+
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	open = ui_menu_is_open(menu);
+	PCUT_ASSERT_TRUE(open);
+
+	ui_menu_close(menu);
+
+	open = ui_menu_is_open(menu);
+	PCUT_ASSERT_FALSE(open);
+
+	ui_menu_destroy(menu);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Paint background in graphics mode */
+PCUT_TEST(paint_bg_gfx)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	gfx_rect_t prect;
+	gfx_coord2_t pos;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Menu needs to be open to be able to paint it */
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	pos.x = 0;
+	pos.y = 0;
+	rc = ui_menu_paint_bg_gfx(menu, &pos);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Paint background in text mode */
+PCUT_TEST(paint_bg_text)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	gfx_rect_t prect;
+	gfx_coord2_t pos;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Menu needs to be open to be able to paint it */
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	pos.x = 0;
+	pos.y = 0;
+	rc = ui_menu_paint_bg_text(menu, &pos);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Paint menu */
+PCUT_TEST(paint)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	gfx_rect_t prect;
+	gfx_coord2_t pos;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Menu needs to be open to be able to paint it */
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	pos.x = 0;
+	pos.y = 0;
+	rc = ui_menu_paint(menu, &pos);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_pos_event() inside menu is claimed */
+PCUT_TEST(pos_event_inside)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	ui_evclaim_t claimed;
+	gfx_coord2_t pos;
+	pos_event_t event;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	pos.x = 0;
+	pos.y = 0;
+	event.type = POS_PRESS;
+	event.hpos = 0;
+	event.vpos = 0;
+	claimed = ui_menu_pos_event(menu, &pos, &event);
+	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_up() with empty menu does nothing */
+PCUT_TEST(up_empty)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	gfx_rect_t prect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Menu needs to be open to be able to move around it */
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_menu_up(menu);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_up() moves one entry up, skips separators, wraps around */
+PCUT_TEST(up)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	ui_menu_entry_t *mentry1 = NULL;
+	ui_menu_entry_t *mentry2 = NULL;
+	ui_menu_entry_t *mentry3 = NULL;
+	gfx_rect_t prect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	rc = ui_menu_entry_create(menu, "Foo", "F1", &mentry1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mentry1);
+
+	rc = ui_menu_entry_sep_create(menu, &mentry2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mentry2);
+
+	rc = ui_menu_entry_create(menu, "Bar", "F2", &mentry3);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mentry3);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Menu needs to be open to be able to move around it */
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* When menu is open, the first entry is selected */
+	PCUT_ASSERT_EQUALS(mentry1, menu->selected);
+
+	ui_menu_up(menu);
+
+	/* Now we've wrapped around to the last entry */
+	PCUT_ASSERT_EQUALS(mentry3, menu->selected);
+
+	ui_menu_up(menu);
+
+	/* mentry2 is a separator and was skipped */
+	PCUT_ASSERT_EQUALS(mentry1, menu->selected);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_down() with empty menu does nothing */
+PCUT_TEST(down_empty)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	gfx_rect_t prect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Menu needs to be open to be able to move around it */
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_menu_down(menu);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_down() moves one entry down, skips separators, wraps around */
+PCUT_TEST(down)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_t *menu = NULL;
+	ui_menu_entry_t *mentry1 = NULL;
+	ui_menu_entry_t *mentry2 = NULL;
+	ui_menu_entry_t *mentry3 = NULL;
+	gfx_rect_t prect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_create(window, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	rc = ui_menu_entry_create(menu, "Foo", "F1", &mentry1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mentry1);
+
+	rc = ui_menu_entry_sep_create(menu, &mentry2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mentry2);
+
+	rc = ui_menu_entry_create(menu, "Bar", "F2", &mentry3);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mentry3);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Menu needs to be open to be able to move around it */
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* When menu is open, the first entry is selected */
+	PCUT_ASSERT_EQUALS(mentry1, menu->selected);
+
+	ui_menu_down(menu);
+
+	/* mentry2 is a separator and was skipped */
+	PCUT_ASSERT_EQUALS(mentry3, menu->selected);
+
+	ui_menu_up(menu);
+
+	/* Now we've wrapped around to the first entry */
+	PCUT_ASSERT_EQUALS(mentry1, menu->selected);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Sending an unhandled event does nothing. */
+PCUT_TEST(send_unhandled)
+{
+	ui_menu_t *menu = NULL;
+	errno_t rc;
+	sysarg_t idev_id;
+	char32_t c;
+
+	rc = ui_menu_create(NULL, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	/* Send events without setting callback */
+	c = 'A';
+	idev_id = 42;
+	ui_menu_left(menu, idev_id);
+	ui_menu_right(menu, idev_id);
+	ui_menu_close_req(menu);
+	ui_menu_press_accel(menu, c, idev_id);
+
+	/* Set dummy callback structure */
+	ui_menu_set_cb(menu, &dummy_cb, NULL);
+
+	/* Send unhandled events */
+	ui_menu_left(menu, idev_id);
+	ui_menu_right(menu, idev_id);
+	ui_menu_close_req(menu);
+	ui_menu_press_accel(menu, c, idev_id);
+
+	ui_menu_destroy(menu);
+}
+
+/** ui_menu_left() sends left event */
+PCUT_TEST(left)
+{
+	ui_menu_t *menu = NULL;
+	errno_t rc;
+	test_resp_t resp;
+	sysarg_t idev_id;
+
+	rc = ui_menu_create(NULL, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	ui_menu_set_cb(menu, &testmenu_cb, (void *)&resp);
+
+	memset(&resp, 0, sizeof(resp));
+	PCUT_ASSERT_FALSE(resp.left_called);
+
+	idev_id = 42;
+	ui_menu_left(menu, idev_id);
+
+	PCUT_ASSERT_TRUE(resp.left_called);
+	PCUT_ASSERT_EQUALS(menu, resp.menu);
+	PCUT_ASSERT_INT_EQUALS(idev_id, resp.idev_id);
+
+	ui_menu_destroy(menu);
+}
+
+/** ui_menu_right() sends right event */
+PCUT_TEST(right)
+{
+	ui_menu_t *menu = NULL;
+	errno_t rc;
+	test_resp_t resp;
+	sysarg_t idev_id;
+
+	rc = ui_menu_create(NULL, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	ui_menu_set_cb(menu, &testmenu_cb, (void *)&resp);
+
+	memset(&resp, 0, sizeof(resp));
+	PCUT_ASSERT_FALSE(resp.right_called);
+
+	idev_id = 42;
+	ui_menu_right(menu, idev_id);
+
+	PCUT_ASSERT_TRUE(resp.right_called);
+	PCUT_ASSERT_EQUALS(menu, resp.menu);
+	PCUT_ASSERT_INT_EQUALS(idev_id, resp.idev_id);
+
+	ui_menu_destroy(menu);
+}
+
+/** ui_menu_close_req() sends close_req event */
+PCUT_TEST(close_req)
+{
+	ui_menu_t *menu = NULL;
+	errno_t rc;
+	test_resp_t resp;
+
+	rc = ui_menu_create(NULL, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	ui_menu_set_cb(menu, &testmenu_cb, (void *)&resp);
+
+	memset(&resp, 0, sizeof(resp));
+	PCUT_ASSERT_FALSE(resp.close_req_called);
+
+	ui_menu_close_req(menu);
+
+	PCUT_ASSERT_TRUE(resp.close_req_called);
+	PCUT_ASSERT_EQUALS(menu, resp.menu);
+
+	ui_menu_destroy(menu);
+}
+
+/** ui_menu_press_accel() sends press_accel event */
+PCUT_TEST(press_accel)
+{
+	ui_menu_t *menu = NULL;
+	errno_t rc;
+	test_resp_t resp;
+	char32_t c;
+	sysarg_t idev_id;
+
+	rc = ui_menu_create(NULL, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	ui_menu_set_cb(menu, &testmenu_cb, (void *)&resp);
+
+	memset(&resp, 0, sizeof(resp));
+	PCUT_ASSERT_FALSE(resp.press_accel_called);
+
+	c = 'A';
+	idev_id = 42;
+	ui_menu_press_accel(menu, c, idev_id);
+
+	PCUT_ASSERT_TRUE(resp.press_accel_called);
+	PCUT_ASSERT_EQUALS(menu, resp.menu);
+	PCUT_ASSERT_EQUALS(c, resp.c);
+	PCUT_ASSERT_INT_EQUALS(idev_id, resp.idev_id);
+
+	ui_menu_destroy(menu);
+}
+
+/** Test menu left callback */
+static void testmenu_left(ui_menu_t *menu, void *arg, sysarg_t idev_id)
+{
+	test_resp_t *resp = (test_resp_t *)arg;
+
+	resp->left_called = true;
+	resp->menu = menu;
+	resp->idev_id = idev_id;
+}
+
+/** Test menu right callback */
+static void testmenu_right(ui_menu_t *menu, void *arg, sysarg_t idev_id)
+{
+	test_resp_t *resp = (test_resp_t *)arg;
+
+	resp->right_called = true;
+	resp->menu = menu;
+	resp->idev_id = idev_id;
+}
+
+/** Test menu close callback */
+static void testmenu_close_req(ui_menu_t *menu, void *arg)
+{
+	test_resp_t *resp = (test_resp_t *)arg;
+
+	resp->close_req_called = true;
+	resp->menu = menu;
+}
+
+/** Test menu press accel callback */
+static void testmenu_press_accel(ui_menu_t *menu, void *arg,
+    char32_t c, sysarg_t kbd_id)
+{
+	test_resp_t *resp = (test_resp_t *)arg;
+
+	resp->press_accel_called = true;
+	resp->menu = menu;
+	resp->c = c;
+	resp->idev_id = kbd_id;
 }
 
Index: uspace/lib/ui/test/menubar.c
===================================================================
--- uspace/lib/ui/test/menubar.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/test/menubar.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -32,9 +32,11 @@
 #include <stdbool.h>
 #include <ui/control.h>
-#include <ui/menu.h>
 #include <ui/menubar.h>
+#include <ui/menudd.h>
 #include <ui/ui.h>
 #include <ui/window.h>
 #include "../private/menubar.h"
+#include "../private/wdecor.h"
+#include "../private/window.h"
 
 PCUT_INIT;
@@ -45,12 +47,27 @@
 PCUT_TEST(create_destroy)
 {
-	ui_menu_bar_t *mbar = NULL;
-	errno_t rc;
-
-	rc = ui_menu_bar_create(NULL, NULL, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	ui_menu_bar_destroy(mbar);
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
 }
 
@@ -64,9 +81,22 @@
 PCUT_TEST(ctl)
 {
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
 	ui_menu_bar_t *mbar = NULL;
 	ui_control_t *control;
 	errno_t rc;
 
-	rc = ui_menu_bar_create(NULL, NULL, &mbar);
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -76,4 +106,6 @@
 
 	ui_control_destroy(control);
+	ui_window_destroy(window);
+	ui_destroy(ui);
 }
 
@@ -149,6 +181,6 @@
 }
 
-/** Press event on menu bar entry selects menu */
-PCUT_TEST(pos_event_select)
+/** Deliver menu bar keyboard event */
+PCUT_TEST(kbd_event)
 {
 	ui_t *ui = NULL;
@@ -158,5 +190,5 @@
 	ui_menu_t *menu = NULL;
 	ui_evclaim_t claimed;
-	pos_event_t event;
+	kbd_event_t event;
 	gfx_rect_t rect;
 	errno_t rc;
@@ -182,5 +214,103 @@
 	ui_menu_bar_set_rect(mbar, &rect);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	event.type = KEY_PRESS;
+	event.key = KC_F10;
+	event.mods = 0;
+	claimed = ui_menu_bar_kbd_event(mbar, &event);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_bar_press_accel() opens the corresponding menu */
+PCUT_TEST(press_accel)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_t *menu = NULL;
+	ui_menu_dd_t *mdd = NULL;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 50;
+	rect.p1.y = 25;
+	ui_menu_bar_set_rect(mbar, &rect);
+
+	rc = ui_menu_dd_create(mbar, "~T~est", &mdd, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	PCUT_ASSERT_FALSE(ui_menu_dd_is_open(mdd));
+
+	ui_menu_bar_press_accel(mbar, 't', 0);
+
+	PCUT_ASSERT_TRUE(ui_menu_dd_is_open(mdd));
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Press event on menu bar entry selects menu */
+PCUT_TEST(pos_event_select)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_t *menu = NULL;
+	ui_menu_dd_t *mdd = NULL;
+	ui_evclaim_t claimed;
+	pos_event_t event;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 50;
+	rect.p1.y = 25;
+	ui_menu_bar_set_rect(mbar, &rect);
+
+	rc = ui_menu_dd_create(mbar, "Test", &mdd, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -193,51 +323,6 @@
 	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
 
-	/* Clicking the menu bar entry should select menu */
-	PCUT_ASSERT_EQUALS(menu, mbar->selected);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** Calling ui_menu_bar_select() with the same menu twice deselects it */
-PCUT_TEST(select_same)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	gfx_rect_t rect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	rect.p0.x = 0;
-	rect.p0.y = 0;
-	rect.p1.x = 0;
-	rect.p1.y = 0;
-	ui_menu_bar_select(mbar, &rect, menu);
-	PCUT_ASSERT_EQUALS(menu, mbar->selected);
-
-	/* Selecting again should unselect the menu */
-	ui_menu_bar_select(mbar, &rect, menu);
-	PCUT_ASSERT_NULL(mbar->selected);
+	/* Clicking the menu bar entry should select menu drop-down */
+	PCUT_ASSERT_EQUALS(mdd, mbar->selected);
 
 	ui_menu_bar_destroy(mbar);
@@ -253,41 +338,160 @@
 	ui_wnd_params_t params;
 	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu1 = NULL;
-	ui_menu_t *menu2 = NULL;
-	gfx_rect_t rect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test 1", &menu1);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu1);
-
-	rc = ui_menu_create(mbar, "Test 2", &menu2);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu2);
-
-	rect.p0.x = 0;
-	rect.p0.y = 0;
-	rect.p1.x = 0;
-	rect.p1.y = 0;
-	ui_menu_bar_select(mbar, &rect, menu1);
-	PCUT_ASSERT_EQUALS(menu1, mbar->selected);
+	ui_menu_dd_t *mdd1 = NULL;
+	ui_menu_dd_t *mdd2 = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test 1", &mdd1, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd1);
+
+	rc = ui_menu_dd_create(mbar, "Test 2", &mdd2, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd2);
+
+	ui_menu_bar_select(mbar, mdd1, true, 0);
+	PCUT_ASSERT_EQUALS(mdd1, mbar->selected);
 
 	/* Selecting different menu should select it */
-	ui_menu_bar_select(mbar, &rect, menu2);
-	PCUT_ASSERT_EQUALS(menu2, mbar->selected);
+	ui_menu_bar_select(mbar, mdd2, true, 0);
+	PCUT_ASSERT_EQUALS(mdd2, mbar->selected);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_bar_select_first/last() selects first/last drop-down */
+PCUT_TEST(select_first_last)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_dd_t *mdd1 = NULL;
+	ui_menu_dd_t *mdd2 = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test 1", &mdd1, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd1);
+
+	rc = ui_menu_dd_create(mbar, "Test 2", &mdd2, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd2);
+
+	ui_menu_bar_select_first(mbar, true, 0);
+	PCUT_ASSERT_EQUALS(mdd1, mbar->selected);
+
+	ui_menu_bar_select_last(mbar, true, 0);
+	PCUT_ASSERT_EQUALS(mdd2, mbar->selected);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_bar_select_sysmenu() activates system menu */
+PCUT_TEST(select_sysmenu)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_dd_t *mdd1 = NULL;
+	ui_menu_dd_t *mdd2 = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test 1", &mdd1, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd1);
+
+	rc = ui_menu_dd_create(mbar, "Test 2", &mdd2, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd2);
+
+	ui_menu_bar_select_sysmenu(mbar, false, 0);
+	PCUT_ASSERT_TRUE(window->wdecor->sysmenu_hdl_active);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_bar_activate() activates/deactivates menu bar */
+PCUT_TEST(activate_deactivate)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_dd_t *mdd = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test", &mdd, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd);
+
+	ui_menu_bar_activate(mbar);
+	PCUT_ASSERT_EQUALS(mdd, mbar->selected);
+
+	ui_menu_bar_deactivate(mbar);
+	PCUT_ASSERT_NULL(mbar->selected);
 
 	ui_menu_bar_destroy(mbar);
Index: uspace/lib/ui/test/menudd.c
===================================================================
--- uspace/lib/ui/test/menudd.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/test/menudd.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,367 @@
+/*
+ * 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.
+ */
+
+#include <gfx/context.h>
+#include <gfx/coord.h>
+#include <mem.h>
+#include <pcut/pcut.h>
+#include <stdbool.h>
+#include <str.h>
+#include <ui/control.h>
+#include <ui/menudd.h>
+#include <ui/menubar.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include "../private/menu.h"
+#include "../private/menubar.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(menudd);
+
+/** Create and destroy menu drop-down */
+PCUT_TEST(create_destroy)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_dd_t *mdd = NULL;
+	ui_menu_t *menu = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_menu_dd_create(mbar, "Test", &mdd, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	/*
+	 * Normally we don't need to destroy a menu drop-down explicitly,
+	 * it will be destroyed along with menu bar, but here we'll test
+	 * destroying it explicitly.
+	 */
+	ui_menu_dd_destroy(mdd);
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_dd_destroy() can take NULL argument (no-op) */
+PCUT_TEST(destroy_null)
+{
+	ui_menu_dd_destroy(NULL);
+}
+
+/** ui_menu_dd_first() / ui_menu_dd_next() iterate over menu drop-downs */
+PCUT_TEST(first_next)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_dd_t *mdd1 = NULL;
+	ui_menu_dd_t *mdd2 = NULL;
+	ui_menu_dd_t *m;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test 1", &mdd1, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd1);
+
+	rc = ui_menu_dd_create(mbar, "Test 1", &mdd2, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd2);
+
+	m = ui_menu_dd_first(mbar);
+	PCUT_ASSERT_EQUALS(mdd1, m);
+
+	m = ui_menu_dd_next(m);
+	PCUT_ASSERT_EQUALS(mdd2, m);
+
+	m = ui_menu_dd_next(m);
+	PCUT_ASSERT_NULL(m);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_dd_last() / ui_menu_dd_prev() iterate over menu drop-downs
+ * in reverse.
+ */
+PCUT_TEST(last_prev)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_dd_t *mdd1 = NULL;
+	ui_menu_dd_t *mdd2 = NULL;
+	ui_menu_dd_t *m;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test 1", &mdd1, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd1);
+
+	rc = ui_menu_dd_create(mbar, "Test 1", &mdd2, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd2);
+
+	m = ui_menu_dd_last(mbar);
+	PCUT_ASSERT_EQUALS(mdd2, m);
+
+	m = ui_menu_dd_prev(m);
+	PCUT_ASSERT_EQUALS(mdd1, m);
+
+	m = ui_menu_dd_prev(m);
+	PCUT_ASSERT_NULL(m);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_dd_caption() returns the drop down's caption */
+PCUT_TEST(caption)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_dd_t *mdd = NULL;
+	const char *caption;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test", &mdd, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd);
+
+	caption = ui_menu_dd_caption(mdd);
+	PCUT_ASSERT_NOT_NULL(caption);
+
+	PCUT_ASSERT_INT_EQUALS(0, str_cmp(caption, "Test"));
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_dd_get_accel() returns the accelerator character */
+PCUT_TEST(get_accel)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_dd_t *mdd = NULL;
+	char32_t accel;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "~T~est", &mdd, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd);
+
+	accel = ui_menu_dd_get_accel(mdd);
+	printf("accel='%c' (%d)\n", (char)accel, (int)accel);
+	PCUT_ASSERT_EQUALS((char32_t)'t', accel);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Open and close menu drop-down with ui_menu_dd_open() / ui_menu_dd_close() */
+PCUT_TEST(open_close)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_dd_t *mdd = NULL;
+	gfx_rect_t prect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test", &mdd, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Open and close */
+	rc = ui_menu_dd_open(mdd, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_menu_dd_close(mdd);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_dd_is_open() correctly returns menu drop-down state */
+PCUT_TEST(is_open)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_dd_t *mdd = NULL;
+	gfx_rect_t prect;
+	bool open;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test", &mdd, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mdd);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	open = ui_menu_dd_is_open(mdd);
+	PCUT_ASSERT_FALSE(open);
+
+	rc = ui_menu_dd_open(mdd, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	open = ui_menu_dd_is_open(mdd);
+	PCUT_ASSERT_TRUE(open);
+
+	ui_menu_dd_close(mdd);
+
+	open = ui_menu_dd_is_open(mdd);
+	PCUT_ASSERT_FALSE(open);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+PCUT_EXPORT(menudd);
Index: uspace/lib/ui/test/menuentry.c
===================================================================
--- uspace/lib/ui/test/menuentry.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/test/menuentry.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -35,4 +35,5 @@
 #include <ui/menu.h>
 #include <ui/menubar.h>
+#include <ui/menudd.h>
 #include <ui/menuentry.h>
 #include <ui/ui.h>
@@ -75,5 +76,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -116,5 +117,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -164,5 +165,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -210,5 +211,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -229,4 +230,57 @@
 
 	e = ui_menu_entry_next(e);
+	PCUT_ASSERT_NULL(e);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_menu_entry_last() / ui_menu_entry_prev() iterate over entries in reverse */
+PCUT_TEST(last_prev)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_t *menu = NULL;
+	ui_menu_entry_t *entry1 = NULL;
+	ui_menu_entry_t *entry2 = NULL;
+	ui_menu_entry_t *e;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	rc = ui_menu_entry_create(menu, "Foo", "F1", &entry1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(entry1);
+
+	rc = ui_menu_entry_create(menu, "Bar", "F2", &entry2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(entry2);
+
+	e = ui_menu_entry_last(menu);
+	PCUT_ASSERT_EQUALS(entry2, e);
+
+	e = ui_menu_entry_prev(e);
+	PCUT_ASSERT_EQUALS(entry1, e);
+
+	e = ui_menu_entry_prev(e);
 	PCUT_ASSERT_NULL(e);
 
@@ -265,5 +319,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -315,5 +369,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -328,5 +382,5 @@
 	prect.p1.y = 0;
 
-	rc = ui_menu_open(menu, &prect);
+	rc = ui_menu_open(menu, &prect, 0);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -341,4 +395,58 @@
 }
 
+/** ui_menu_entry_selectable() returns correct value based on entry type */
+PCUT_TEST(selectable)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_t *menu = NULL;
+	ui_menu_entry_t *mentry = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	/* Selectable entry */
+
+	rc = ui_menu_entry_create(menu, "Foo", "F1", &mentry);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mentry);
+
+	PCUT_ASSERT_TRUE(ui_menu_entry_selectable(mentry));
+
+	ui_menu_entry_destroy(mentry);
+
+	/* Non-selectable separator entry */
+
+	rc = ui_menu_entry_sep_create(menu, &mentry);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mentry);
+
+	PCUT_ASSERT_FALSE(ui_menu_entry_selectable(mentry));
+
+	ui_menu_entry_destroy(mentry);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
 /** Press and release activates menu entry */
 PCUT_TEST(press_release)
@@ -369,5 +477,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -385,5 +493,5 @@
 	prect.p1.y = 0;
 
-	rc = ui_menu_open(menu, &prect);
+	rc = ui_menu_open(menu, &prect, 0);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -432,5 +540,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -448,5 +556,5 @@
 	prect.p1.y = 0;
 
-	rc = ui_menu_open(menu, &prect);
+	rc = ui_menu_open(menu, &prect, 0);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -500,5 +608,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -516,5 +624,5 @@
 	prect.p1.y = 0;
 
-	rc = ui_menu_open(menu, &prect);
+	rc = ui_menu_open(menu, &prect, 0);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -545,33 +653,32 @@
 }
 
-/** Press event inside menu entry */
-PCUT_TEST(pos_press_inside)
-{
-	ui_t *ui = NULL;
-	ui_window_t *window = NULL;
-	ui_wnd_params_t params;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	ui_menu_entry_t *mentry = NULL;
-	gfx_coord2_t pos;
+/** ui_menu_entry_activate() activates menu entry */
+PCUT_TEST(activate)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_t *menu = NULL;
+	ui_menu_entry_t *mentry = NULL;
 	gfx_rect_t prect;
-	pos_event_t event;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(window);
-
-	rc = ui_menu_bar_create(ui, window, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
+	test_resp_t resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -580,4 +687,7 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mentry);
+
+	ui_menu_entry_set_cb(mentry, test_entry_cb, &resp);
+	resp.activated = false;
 
 	prect.p0.x = 0;
@@ -586,25 +696,20 @@
 	prect.p1.y = 0;
 
-	rc = ui_menu_open(menu, &prect);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	pos.x = 0;
-	pos.y = 0;
-
-	event.type = POS_PRESS;
-	event.hpos = 4;
-	event.vpos = 4;
-
-	ui_menu_entry_pos_event(mentry, &pos, &event);
-	PCUT_ASSERT_TRUE(mentry->inside);
-	PCUT_ASSERT_TRUE(mentry->held);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** Press event outside menu entry */
-PCUT_TEST(pos_press_outside)
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(resp.activated);
+	ui_menu_entry_activate(mentry);
+
+	ui_menu_entry_release(mentry);
+	PCUT_ASSERT_TRUE(resp.activated);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Press event inside menu entry */
+PCUT_TEST(pos_press_inside)
 {
 	ui_t *ui = NULL;
@@ -633,5 +738,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -646,5 +751,5 @@
 	prect.p1.y = 0;
 
-	rc = ui_menu_open(menu, &prect);
+	rc = ui_menu_open(menu, &prect, 0);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -653,18 +758,18 @@
 
 	event.type = POS_PRESS;
-	event.hpos = 40;
-	event.vpos = 20;
+	event.hpos = 4;
+	event.vpos = 4;
 
 	ui_menu_entry_pos_event(mentry, &pos, &event);
-	PCUT_ASSERT_FALSE(mentry->inside);
-	PCUT_ASSERT_FALSE(mentry->held);
-
-	ui_menu_bar_destroy(mbar);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** Position event moving out of menu entry */
-PCUT_TEST(pos_move_out)
+	PCUT_ASSERT_TRUE(mentry->inside);
+	PCUT_ASSERT_TRUE(mentry->held);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Press event outside menu entry */
+PCUT_TEST(pos_press_outside)
 {
 	ui_t *ui = NULL;
@@ -693,5 +798,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -706,5 +811,65 @@
 	prect.p1.y = 0;
 
-	rc = ui_menu_open(menu, &prect);
+	rc = ui_menu_open(menu, &prect, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	pos.x = 0;
+	pos.y = 0;
+
+	event.type = POS_PRESS;
+	event.hpos = 40;
+	event.vpos = 20;
+
+	ui_menu_entry_pos_event(mentry, &pos, &event);
+	PCUT_ASSERT_FALSE(mentry->inside);
+	PCUT_ASSERT_FALSE(mentry->held);
+
+	ui_menu_bar_destroy(mbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Position event moving out of menu entry */
+PCUT_TEST(pos_move_out)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_t *menu = NULL;
+	ui_menu_entry_t *mentry = NULL;
+	gfx_coord2_t pos;
+	gfx_rect_t prect;
+	pos_event_t event;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_menu_bar_create(ui, window, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	rc = ui_menu_entry_create(menu, "X", "Y", &mentry);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mentry);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	rc = ui_menu_open(menu, &prect, 0);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -756,5 +921,5 @@
 	PCUT_ASSERT_NOT_NULL(mbar);
 
-	rc = ui_menu_create(mbar, "Test", &menu);
+	rc = ui_menu_dd_create(mbar, "Test", NULL, &menu);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(menu);
@@ -769,5 +934,5 @@
 	prect.p1.y = 0;
 
-	rc = ui_menu_open(menu, &prect);
+	rc = ui_menu_open(menu, &prect, 0);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
Index: uspace/lib/ui/test/paint.c
===================================================================
--- uspace/lib/ui/test/paint.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/test/paint.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -75,4 +75,13 @@
 } testgc_bitmap_t;
 
+/** Test box characters */
+static ui_box_chars_t test_box_chars = {
+	{
+		{ "A", "B", "C" },
+		{ "D", " ", "E" },
+		{ "F", "G", "H" }
+	}
+};
+
 /** Paint bevel */
 PCUT_TEST(bevel)
@@ -225,4 +234,7 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
+	center.x = 0;
+	center.y = 0;
+
 	/* Paint filled circle / upper-left half */
 	rc = ui_paint_filled_circle(gc, &center, 10, ui_fcircle_upleft);
@@ -241,4 +253,198 @@
 }
 
+/** Paint up pointing triangle */
+PCUT_TEST(up_triangle)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	gfx_coord2_t center;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	center.x = 0;
+	center.y = 0;
+
+	rc = ui_paint_up_triangle(gc, &center, 5);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Paint down pointing triangle */
+PCUT_TEST(down_triangle)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	gfx_coord2_t center;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	center.x = 0;
+	center.y = 0;
+
+	rc = ui_paint_down_triangle(gc, &center, 5);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Paint left pointing triangle */
+PCUT_TEST(left_triangle)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	gfx_coord2_t center;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	center.x = 0;
+	center.y = 0;
+
+	rc = ui_paint_left_triangle(gc, &center, 5);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Paint right pointing triangle */
+PCUT_TEST(right_triangle)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	gfx_coord2_t center;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	center.x = 0;
+	center.y = 0;
+
+	rc = ui_paint_right_triangle(gc, &center, 5);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Paint diagonal cross (X) */
+PCUT_TEST(cross)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	gfx_coord2_t center;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	center.x = 0;
+	center.y = 0;
+
+	rc = ui_paint_cross(gc, &center, 5, 1, 2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Paint mimimize icon */
+PCUT_TEST(minicon)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	ui_resource_t *resource = NULL;
+	test_gc_t tgc;
+	gfx_coord2_t center;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	center.x = 0;
+	center.y = 0;
+
+	rc = ui_paint_minicon(resource, &center, 8, 6);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_resource_destroy(resource);
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Paint maximize icon */
+PCUT_TEST(maxicon)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	ui_resource_t *resource = NULL;
+	test_gc_t tgc;
+	gfx_coord2_t center;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	center.x = 0;
+	center.y = 0;
+
+	rc = ui_paint_maxicon(resource, &center, 8, 6);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_resource_destroy(resource);
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Paint unmaximize icon */
+PCUT_TEST(unmaxicon)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	ui_resource_t *resource = NULL;
+	test_gc_t tgc;
+	gfx_coord2_t center;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	center.x = 0;
+	center.y = 0;
+
+	rc = ui_paint_unmaxicon(resource, &center, 8, 8, 3, 3);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_resource_destroy(resource);
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
 /** Paint text box */
 PCUT_TEST(text_box)
@@ -269,4 +475,40 @@
 	/* Paint text box */
 	rc = ui_paint_text_box(resource, &rect, ui_box_single, color);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	gfx_color_delete(color);
+	ui_resource_destroy(resource);
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Paint custom text box */
+PCUT_TEST(text_box_custom)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	ui_resource_t *resource = NULL;
+	gfx_color_t *color = NULL;
+	test_gc_t tgc;
+	gfx_rect_t rect;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = gfx_color_new_rgb_i16(1, 2, 3, &color);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 30;
+	rect.p1.y = 40;
+
+	/* Paint text box */
+	rc = ui_paint_text_box_custom(resource, &rect, &test_box_chars, color);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -306,4 +548,40 @@
 	rc = ui_paint_text_hbrace(resource, &rect, ui_box_single,
 	    color);
+
+	gfx_color_delete(color);
+	ui_resource_destroy(resource);
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Paint text rectangle */
+PCUT_TEST(text_rect)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	ui_resource_t *resource = NULL;
+	gfx_color_t *color = NULL;
+	test_gc_t tgc;
+	gfx_rect_t rect;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = gfx_color_new_rgb_i16(1, 2, 3, &color);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 30;
+	rect.p1.y = 40;
+
+	/* Paint text box */
+	rc = ui_paint_text_rect(resource, &rect, color, "A");
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
 	gfx_color_delete(color);
Index: uspace/lib/ui/test/pbutton.c
===================================================================
--- uspace/lib/ui/test/pbutton.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/test/pbutton.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -63,7 +63,11 @@
 
 static void test_pbutton_clicked(ui_pbutton_t *, void *);
+static void test_pbutton_down(ui_pbutton_t *, void *);
+static void test_pbutton_up(ui_pbutton_t *, void *);
 
 static ui_pbutton_cb_t test_pbutton_cb = {
-	.clicked = test_pbutton_clicked
+	.clicked = test_pbutton_clicked,
+	.down = test_pbutton_down,
+	.up = test_pbutton_up
 };
 
@@ -90,4 +94,6 @@
 typedef struct {
 	bool clicked;
+	bool down;
+	bool up;
 } test_cb_resp_t;
 
@@ -125,4 +131,19 @@
 
 	ui_control_destroy(control);
+}
+
+/** Set flags sets internal field */
+PCUT_TEST(set_flags)
+{
+	ui_pbutton_t *pbutton;
+	errno_t rc;
+
+	rc = ui_pbutton_create(NULL, "Hello", &pbutton);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_pbutton_set_flags(pbutton, ui_pbf_no_text_depress);
+	PCUT_ASSERT_INT_EQUALS(ui_pbf_no_text_depress, pbutton->flags);
+
+	ui_pbutton_destroy(pbutton);
 }
 
@@ -169,4 +190,59 @@
 }
 
+/** Get light gets internal field */
+PCUT_TEST(get_light)
+{
+	ui_pbutton_t *pbutton;
+	errno_t rc;
+
+	rc = ui_pbutton_create(NULL, "Hello", &pbutton);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	pbutton->light = true;
+	PCUT_ASSERT_TRUE(ui_pbutton_get_light(pbutton));
+
+	pbutton->light = false;
+	PCUT_ASSERT_FALSE(ui_pbutton_get_light(pbutton));
+
+	ui_pbutton_destroy(pbutton);
+}
+
+/** Set light sets internal field */
+PCUT_TEST(set_light)
+{
+	ui_pbutton_t *pbutton;
+	errno_t rc;
+
+	rc = ui_pbutton_create(NULL, "Hello", &pbutton);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_pbutton_set_light(pbutton, true);
+	PCUT_ASSERT_TRUE(pbutton->light);
+
+	ui_pbutton_set_light(pbutton, false);
+	PCUT_ASSERT_FALSE(pbutton->light);
+
+	ui_pbutton_destroy(pbutton);
+}
+
+/** Set caption sets internal field */
+PCUT_TEST(set_caption)
+{
+	ui_pbutton_t *pbutton;
+	errno_t rc;
+
+	rc = ui_pbutton_create(NULL, "Hello", &pbutton);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_STR_EQUALS("Hello", pbutton->caption);
+
+	rc = ui_pbutton_set_caption(pbutton, "World");
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_STR_EQUALS("World", pbutton->caption);
+
+	ui_pbutton_destroy(pbutton);
+}
+
 /** Paint button */
 PCUT_TEST(paint)
@@ -225,4 +301,56 @@
 }
 
+/** Test ui_pbutton_down() */
+PCUT_TEST(down)
+{
+	errno_t rc;
+	ui_pbutton_t *pbutton;
+	test_cb_resp_t resp;
+
+	rc = ui_pbutton_create(NULL, "Hello", &pbutton);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Down with no callbacks set */
+	ui_pbutton_clicked(pbutton);
+
+	/* Down with callback not implementing down */
+	ui_pbutton_set_cb(pbutton, &dummy_pbutton_cb, NULL);
+	ui_pbutton_down(pbutton);
+
+	/* Down with real callback set */
+	resp.down = false;
+	ui_pbutton_set_cb(pbutton, &test_pbutton_cb, &resp);
+	ui_pbutton_down(pbutton);
+	PCUT_ASSERT_TRUE(resp.down);
+
+	ui_pbutton_destroy(pbutton);
+}
+
+/** Test ui_pbutton_up() */
+PCUT_TEST(up)
+{
+	errno_t rc;
+	ui_pbutton_t *pbutton;
+	test_cb_resp_t resp;
+
+	rc = ui_pbutton_create(NULL, "Hello", &pbutton);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Up with no callbacks set */
+	ui_pbutton_clicked(pbutton);
+
+	/* Up with callback not implementing up */
+	ui_pbutton_set_cb(pbutton, &dummy_pbutton_cb, NULL);
+	ui_pbutton_up(pbutton);
+
+	/* Up with real callback set */
+	resp.up = false;
+	ui_pbutton_set_cb(pbutton, &test_pbutton_cb, &resp);
+	ui_pbutton_up(pbutton);
+	PCUT_ASSERT_TRUE(resp.up);
+
+	ui_pbutton_destroy(pbutton);
+}
+
 /** Press and release button */
 PCUT_TEST(press_release)
@@ -247,4 +375,6 @@
 
 	resp.clicked = false;
+	resp.down = false;
+	resp.up = false;
 	ui_pbutton_set_cb(pbutton, &test_pbutton_cb, &resp);
 
@@ -255,4 +385,6 @@
 	PCUT_ASSERT_TRUE(pbutton->held);
 	PCUT_ASSERT_TRUE(pbutton->inside);
+	PCUT_ASSERT_TRUE(resp.down);
+	PCUT_ASSERT_FALSE(resp.up);
 	PCUT_ASSERT_FALSE(resp.clicked);
 
@@ -260,4 +392,5 @@
 	PCUT_ASSERT_FALSE(pbutton->held);
 	PCUT_ASSERT_TRUE(pbutton->inside);
+	PCUT_ASSERT_TRUE(resp.up);
 	PCUT_ASSERT_TRUE(resp.clicked);
 
@@ -588,3 +721,17 @@
 }
 
+static void test_pbutton_down(ui_pbutton_t *pbutton, void *arg)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->down = true;
+}
+
+static void test_pbutton_up(ui_pbutton_t *pbutton, void *arg)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->up = true;
+}
+
 PCUT_EXPORT(pbutton);
Index: uspace/lib/ui/test/popup.c
===================================================================
--- uspace/lib/ui/test/popup.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/test/popup.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -64,5 +64,5 @@
 static errno_t test_ctl_paint(void *);
 static ui_evclaim_t test_ctl_pos_event(void *, pos_event_t *);
-static void test_ctl_unfocus(void *);
+static void test_ctl_unfocus(void *, unsigned);
 
 static ui_control_ops_t test_ctl_ops = {
@@ -91,4 +91,5 @@
 	pos_event_t pos_event;
 	bool unfocus;
+	unsigned unfocus_nfocus;
 } test_ctl_resp_t;
 
@@ -98,4 +99,6 @@
 	errno_t rc;
 	ui_t *ui = NULL;
+	ui_wnd_params_t wparams;
+	ui_window_t *window = NULL;
 	ui_popup_params_t params;
 	ui_popup_t *popup = NULL;
@@ -104,11 +107,19 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
+	ui_wnd_params_init(&wparams);
+	wparams.caption = "Hello";
+
+	rc = ui_window_create(ui, &wparams, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
 	ui_popup_params_init(&params);
 
-	rc = ui_popup_create(ui, NULL, &params, &popup);
+	rc = ui_popup_create(ui, window, &params, &popup);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(popup);
 
 	ui_popup_destroy(popup);
+	ui_window_destroy(window);
 	ui_destroy(ui);
 }
@@ -125,4 +136,6 @@
 	errno_t rc;
 	ui_t *ui = NULL;
+	ui_wnd_params_t wparams;
+	ui_window_t *window = NULL;
 	ui_popup_params_t params;
 	ui_popup_t *popup = NULL;
@@ -133,7 +146,14 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
+	ui_wnd_params_init(&wparams);
+	wparams.caption = "Hello";
+
+	rc = ui_window_create(ui, &wparams, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
 	ui_popup_params_init(&params);
 
-	rc = ui_popup_create(ui, NULL, &params, &popup);
+	rc = ui_popup_create(ui, window, &params, &popup);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(popup);
@@ -171,4 +191,5 @@
 
 	ui_popup_destroy(popup);
+	ui_window_destroy(window);
 	ui_destroy(ui);
 }
@@ -179,28 +200,34 @@
 	errno_t rc;
 	ui_t *ui = NULL;
-	ui_wnd_params_t params;
+	ui_wnd_params_t wparams;
 	ui_window_t *window = NULL;
+	ui_popup_params_t params;
+	ui_popup_t *popup = NULL;
 	ui_resource_t *res;
 	gfx_context_t *gc;
-	gfx_rect_t rect;
 
 	rc = ui_create_disp(NULL, &ui);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	ui_wnd_params_init(&params);
-	params.caption = "Hello";
-
-	rc = ui_window_create(ui, &params, &window);
+	ui_wnd_params_init(&wparams);
+	wparams.caption = "Hello";
+
+	rc = ui_window_create(ui, &wparams, &window);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(window);
 
-	res = ui_window_get_res(window);
+	ui_popup_params_init(&params);
+
+	rc = ui_popup_create(ui, window, &params, &popup);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(popup);
+
+	res = ui_popup_get_res(popup);
 	PCUT_ASSERT_NOT_NULL(res);
 
-	gc = ui_window_get_gc(window);
+	gc = ui_popup_get_gc(popup);
 	PCUT_ASSERT_NOT_NULL(gc);
 
-	ui_window_get_app_rect(window, &rect);
-
+	ui_popup_destroy(popup);
 	ui_window_destroy(window);
 	ui_destroy(ui);
@@ -212,4 +239,6 @@
 	errno_t rc;
 	ui_t *ui = NULL;
+	ui_wnd_params_t wparams;
+	ui_window_t *window = NULL;
 	ui_popup_params_t params;
 	ui_popup_t *popup = NULL;
@@ -220,7 +249,14 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
+	ui_wnd_params_init(&wparams);
+	wparams.caption = "Hello";
+
+	rc = ui_window_create(ui, &wparams, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
 	ui_popup_params_init(&params);
 
-	rc = ui_popup_create(ui, NULL, &params, &popup);
+	rc = ui_popup_create(ui, window, &params, &popup);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(popup);
@@ -251,4 +287,5 @@
 
 	ui_popup_destroy(popup);
+	ui_window_destroy(window);
 	ui_destroy(ui);
 }
@@ -305,9 +342,10 @@
 }
 
-static void test_ctl_unfocus(void *arg)
+static void test_ctl_unfocus(void *arg, unsigned nfocus)
 {
 	test_ctl_resp_t *resp = (test_ctl_resp_t *) arg;
 
 	resp->unfocus = true;
+	resp->unfocus_nfocus = nfocus;
 }
 
Index: uspace/lib/ui/test/resource.c
===================================================================
--- uspace/lib/ui/test/resource.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/test/resource.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -134,4 +134,110 @@
 }
 
+/** ui_resource_get_font() returns the font */
+PCUT_TEST(get_font)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	ui_resource_t *resource = NULL;
+	gfx_font_t *font;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	font = ui_resource_get_font(resource);
+	PCUT_ASSERT_EQUALS(resource->font, font);
+
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** ui_resource_is_textmode() returns the textmode flag */
+PCUT_TEST(is_textmode)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	ui_resource_t *resource = NULL;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	/* To make sure let's test both true and false case */
+	resource->textmode = true;
+	PCUT_ASSERT_TRUE(ui_resource_is_textmode(resource));
+	resource->textmode = false;
+	PCUT_ASSERT_FALSE(ui_resource_is_textmode(resource));
+
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** ui_resource_get_wnd_face_color() returns window face color */
+PCUT_TEST(get_wnd_face_color)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	ui_resource_t *resource = NULL;
+	gfx_color_t *color;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	color = ui_resource_get_wnd_face_color(resource);
+	PCUT_ASSERT_EQUALS(resource->wnd_face_color, color);
+
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** ui_resource_get_wnd_text_color() returns window text color */
+PCUT_TEST(get_wnd_text_color)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	ui_resource_t *resource = NULL;
+	gfx_color_t *color;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	color = ui_resource_get_wnd_text_color(resource);
+	PCUT_ASSERT_EQUALS(resource->wnd_text_color, color);
+
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
 static errno_t testgc_bitmap_create(void *arg, gfx_bitmap_params_t *params,
     gfx_bitmap_alloc_t *alloc, void **rbm)
Index: uspace/lib/ui/test/scrollbar.c
===================================================================
--- uspace/lib/ui/test/scrollbar.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/test/scrollbar.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,1277 @@
+/*
+ * 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.
+ */
+
+#include <gfx/context.h>
+#include <gfx/coord.h>
+#include <mem.h>
+#include <pcut/pcut.h>
+#include <stdbool.h>
+#include <ui/control.h>
+#include <ui/scrollbar.h>
+#include <ui/resource.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include "../private/pbutton.h"
+#include "../private/scrollbar.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(scrollbar);
+
+static void test_scrollbar_up(ui_scrollbar_t *, void *);
+static void test_scrollbar_down(ui_scrollbar_t *, void *);
+static void test_scrollbar_page_up(ui_scrollbar_t *, void *);
+static void test_scrollbar_page_down(ui_scrollbar_t *, void *);
+static void test_scrollbar_moved(ui_scrollbar_t *, void *, gfx_coord_t);
+
+static ui_scrollbar_cb_t test_scrollbar_cb = {
+	.up = test_scrollbar_up,
+	.down = test_scrollbar_down,
+	.page_up = test_scrollbar_page_up,
+	.page_down = test_scrollbar_page_down,
+	.moved = test_scrollbar_moved
+};
+
+static ui_scrollbar_cb_t dummy_scrollbar_cb = {
+};
+
+typedef struct {
+	bool up;
+	bool down;
+	bool page_up;
+	bool page_down;
+	bool moved;
+	gfx_coord_t pos;
+} test_cb_resp_t;
+
+/** Create and destroy scrollbar */
+PCUT_TEST(create_destroy)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(scrollbar);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_scrollbar_destroy() can take NULL argument (no-op) */
+PCUT_TEST(destroy_null)
+{
+	ui_scrollbar_destroy(NULL);
+}
+
+/** ui_scrollbar_ctl() returns control that has a working virtual destructor */
+PCUT_TEST(ctl)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar = NULL;
+	ui_control_t *control;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(scrollbar);
+
+	control = ui_scrollbar_ctl(scrollbar);
+	PCUT_ASSERT_NOT_NULL(control);
+
+	ui_control_destroy(control);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Set scrollbar rectangle sets internal field */
+PCUT_TEST(set_rect)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar = NULL;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(scrollbar);
+
+	rect.p0.x = 1;
+	rect.p0.y = 2;
+	rect.p1.x = 3;
+	rect.p1.y = 4;
+
+	ui_scrollbar_set_rect(scrollbar, &rect);
+	PCUT_ASSERT_INT_EQUALS(rect.p0.x, scrollbar->rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(rect.p0.y, scrollbar->rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(rect.p1.x, scrollbar->rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(rect.p1.y, scrollbar->rect.p1.y);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Paint scrollbar in graphics mode */
+PCUT_TEST(paint_gfx)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_scrollbar_paint_gfx(scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Paint scrollbar in text mode */
+PCUT_TEST(paint_text_horiz)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 1;
+	rect.p0.y = 1;
+	rect.p1.x = 10;
+	rect.p1.y = 2;
+	ui_scrollbar_set_rect(scrollbar, &rect);
+
+	rc = ui_scrollbar_paint_text(scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_scrollbar_get_geom() returns scrollbar geometry */
+PCUT_TEST(get_geom)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar;
+	ui_scrollbar_geom_t geom;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 10;
+	rect.p1.x = 100;
+	rect.p1.y = 30;
+	ui_scrollbar_set_rect(scrollbar, &rect);
+
+	ui_scrollbar_get_geom(scrollbar, &geom);
+	PCUT_ASSERT_INT_EQUALS(11, geom.up_btn_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(11, geom.up_btn_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(99, geom.down_btn_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(29, geom.down_btn_rect.p1.y);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_scrollbar_trough_length() gives correct scrollbar trough length */
+PCUT_TEST(trough_length)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar;
+	gfx_coord_t length;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(scrollbar->thumb_held);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 110;
+	rect.p1.y = 120;
+	ui_scrollbar_set_rect(scrollbar, &rect);
+
+	length = ui_scrollbar_trough_length(scrollbar);
+
+	/* Total length minus buttons */
+	PCUT_ASSERT_INT_EQUALS(110 - 10 - 2 * 21, length);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_scrollbar_move_length() gives correct scrollbar move length */
+PCUT_TEST(move_length)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar;
+	gfx_coord_t length;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(scrollbar->thumb_held);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 110;
+	rect.p1.y = 120;
+	ui_scrollbar_set_rect(scrollbar, &rect);
+
+	length = ui_scrollbar_move_length(scrollbar);
+
+	/* Total length minus buttons minus default thumb length */
+	PCUT_ASSERT_INT_EQUALS(110 - 10 - 2 * 21 - 21, length);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_scrollbar_get_pos() returns scrollbar position */
+PCUT_TEST(get_pos)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar;
+	gfx_coord_t pos;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(scrollbar->thumb_held);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 110;
+	rect.p1.y = 120;
+	ui_scrollbar_set_rect(scrollbar, &rect);
+
+	scrollbar->pos = 42;
+	pos = ui_scrollbar_get_pos(scrollbar);
+	PCUT_ASSERT_INT_EQUALS(42, pos);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_scrollbar_set_thumb_length() sets thumb length */
+PCUT_TEST(set_thumb_length)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(scrollbar->thumb_held);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 110;
+	rect.p1.y = 120;
+	ui_scrollbar_set_rect(scrollbar, &rect);
+
+	ui_scrollbar_set_thumb_length(scrollbar, 42);
+	PCUT_ASSERT_INT_EQUALS(42, scrollbar->thumb_len);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_scrollbar_set_pos() sets thumb position */
+PCUT_TEST(set_pos)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar;
+	gfx_coord_t pos;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(scrollbar->thumb_held);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 110;
+	rect.p1.y = 120;
+	ui_scrollbar_set_rect(scrollbar, &rect);
+
+	ui_scrollbar_set_pos(scrollbar, -1);
+	pos = ui_scrollbar_get_pos(scrollbar);
+	/* The value is clipped to the minimum possible position (0) */
+	PCUT_ASSERT_INT_EQUALS(0, pos);
+
+	ui_scrollbar_set_pos(scrollbar, 12);
+	pos = ui_scrollbar_get_pos(scrollbar);
+	/* The value is set to the requested value */
+	PCUT_ASSERT_INT_EQUALS(12, pos);
+
+	ui_scrollbar_set_pos(scrollbar, 42);
+	pos = ui_scrollbar_get_pos(scrollbar);
+	/* The value is clipped to the maximum possible position (37) */
+	PCUT_ASSERT_INT_EQUALS(37, pos);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Press and release scrollbar thumb */
+PCUT_TEST(thumb_press_release)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	gfx_coord2_t pos;
+	gfx_rect_t rect;
+	ui_scrollbar_t *scrollbar;
+	test_cb_resp_t resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 110;
+	rect.p1.y = 120;
+	ui_scrollbar_set_rect(scrollbar, &rect);
+
+	resp.moved = false;
+	ui_scrollbar_set_cb(scrollbar, &test_scrollbar_cb, &resp);
+
+	PCUT_ASSERT_FALSE(scrollbar->thumb_held);
+
+	pos.x = 11;
+	pos.y = 22;
+
+	ui_scrollbar_thumb_press(scrollbar, &pos);
+	PCUT_ASSERT_TRUE(scrollbar->thumb_held);
+	PCUT_ASSERT_FALSE(resp.moved);
+
+	pos.x = 21;
+	pos.y = 32;
+
+	ui_scrollbar_release(scrollbar, &pos);
+	PCUT_ASSERT_FALSE(scrollbar->thumb_held);
+	PCUT_ASSERT_TRUE(resp.moved);
+	PCUT_ASSERT_INT_EQUALS(10, scrollbar->pos);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Press, update and release scrollbar */
+PCUT_TEST(thumb_press_update_release)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	gfx_coord2_t pos;
+	gfx_rect_t rect;
+	ui_scrollbar_t *scrollbar;
+	test_cb_resp_t resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 110;
+	rect.p1.y = 120;
+	ui_scrollbar_set_rect(scrollbar, &rect);
+
+	resp.moved = false;
+	ui_scrollbar_set_cb(scrollbar, &test_scrollbar_cb, &resp);
+
+	PCUT_ASSERT_FALSE(scrollbar->thumb_held);
+
+	pos.x = 11;
+	pos.y = 22;
+
+	ui_scrollbar_thumb_press(scrollbar, &pos);
+	PCUT_ASSERT_TRUE(scrollbar->thumb_held);
+	PCUT_ASSERT_FALSE(resp.moved);
+
+	pos.x = 21;
+	pos.y = 32;
+
+	ui_scrollbar_update(scrollbar, &pos);
+	PCUT_ASSERT_TRUE(scrollbar->thumb_held);
+	PCUT_ASSERT_TRUE(resp.moved);
+	PCUT_ASSERT_INT_EQUALS(10, scrollbar->pos);
+
+	pos.x = 31;
+	pos.y = 42;
+
+	ui_scrollbar_release(scrollbar, &pos);
+	PCUT_ASSERT_FALSE(scrollbar->thumb_held);
+	PCUT_ASSERT_TRUE(resp.moved);
+	PCUT_ASSERT_INT_EQUALS(20, scrollbar->pos);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Press and release upper trough */
+PCUT_TEST(upper_trough_press_release)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	gfx_coord2_t pos;
+	gfx_rect_t rect;
+	ui_scrollbar_t *scrollbar;
+	test_cb_resp_t resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 110;
+	rect.p1.y = 120;
+	ui_scrollbar_set_rect(scrollbar, &rect);
+
+	resp.page_up = false;
+	ui_scrollbar_set_cb(scrollbar, &test_scrollbar_cb, &resp);
+
+	PCUT_ASSERT_FALSE(scrollbar->upper_trough_held);
+
+	ui_scrollbar_upper_trough_press(scrollbar);
+	PCUT_ASSERT_TRUE(scrollbar->upper_trough_held);
+	PCUT_ASSERT_TRUE(resp.page_up);
+
+	/* Position does not matter here */
+	pos.x = 11;
+	pos.y = 22;
+
+	ui_scrollbar_release(scrollbar, &pos);
+	PCUT_ASSERT_FALSE(scrollbar->upper_trough_held);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Press and release lower trough */
+PCUT_TEST(lower_trough_press_release)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	gfx_coord2_t pos;
+	gfx_rect_t rect;
+	ui_scrollbar_t *scrollbar;
+	test_cb_resp_t resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 110;
+	rect.p1.y = 120;
+	ui_scrollbar_set_rect(scrollbar, &rect);
+
+	resp.page_down = false;
+	ui_scrollbar_set_cb(scrollbar, &test_scrollbar_cb, &resp);
+
+	PCUT_ASSERT_FALSE(scrollbar->lower_trough_held);
+
+	ui_scrollbar_lower_trough_press(scrollbar);
+	PCUT_ASSERT_TRUE(scrollbar->lower_trough_held);
+	PCUT_ASSERT_TRUE(resp.page_down);
+
+	/* Position does not matter here */
+	pos.x = 11;
+	pos.y = 22;
+
+	ui_scrollbar_release(scrollbar, &pos);
+	PCUT_ASSERT_FALSE(scrollbar->lower_trough_held);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Updating state of troughs when cursor or thumb moves */
+PCUT_TEST(troughs_update)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	gfx_coord2_t pos;
+	gfx_rect_t rect;
+	ui_scrollbar_t *scrollbar;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 110;
+	rect.p1.y = 120;
+	ui_scrollbar_set_rect(scrollbar, &rect);
+
+	PCUT_ASSERT_FALSE(scrollbar->lower_trough_inside);
+
+	pos.x = 60;
+	pos.y = 22;
+
+	ui_scrollbar_troughs_update(scrollbar, &pos);
+	PCUT_ASSERT_TRUE(scrollbar->lower_trough_inside);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_scrollbar_up() delivers up event */
+PCUT_TEST(up)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar;
+	test_cb_resp_t resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Up with no callbacks set */
+	ui_scrollbar_up(scrollbar);
+
+	/* Up with callback not implementing up */
+	ui_scrollbar_set_cb(scrollbar, &dummy_scrollbar_cb, NULL);
+	ui_scrollbar_up(scrollbar);
+
+	/* Up with real callback set */
+	resp.up = false;
+	ui_scrollbar_set_cb(scrollbar, &test_scrollbar_cb, &resp);
+	ui_scrollbar_up(scrollbar);
+	PCUT_ASSERT_TRUE(resp.up);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_scrollbar_down() delivers down event */
+PCUT_TEST(down)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar;
+	test_cb_resp_t resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Down with no callbacks set */
+	ui_scrollbar_down(scrollbar);
+
+	/* Down with callback not implementing down */
+	ui_scrollbar_set_cb(scrollbar, &dummy_scrollbar_cb, NULL);
+	ui_scrollbar_down(scrollbar);
+
+	/* Down with real callback set */
+	resp.down = false;
+	ui_scrollbar_set_cb(scrollbar, &test_scrollbar_cb, &resp);
+	ui_scrollbar_down(scrollbar);
+	PCUT_ASSERT_TRUE(resp.down);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_scrollbar_page_up() delivers page up event */
+PCUT_TEST(page_up)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar;
+	test_cb_resp_t resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Page up with no callbacks set */
+	ui_scrollbar_page_up(scrollbar);
+
+	/* Pge up with callback not implementing page up */
+	ui_scrollbar_set_cb(scrollbar, &dummy_scrollbar_cb, NULL);
+	ui_scrollbar_page_up(scrollbar);
+
+	/* Page up with real callback set */
+	resp.page_up = false;
+	ui_scrollbar_set_cb(scrollbar, &test_scrollbar_cb, &resp);
+	ui_scrollbar_page_up(scrollbar);
+	PCUT_ASSERT_TRUE(resp.page_up);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_scrollbar_page_down() delivers page down event */
+PCUT_TEST(page_down)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar;
+	test_cb_resp_t resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Page down with no callbacks set */
+	ui_scrollbar_page_down(scrollbar);
+
+	/* Page down with callback not implementing page down */
+	ui_scrollbar_set_cb(scrollbar, &dummy_scrollbar_cb, NULL);
+	ui_scrollbar_page_down(scrollbar);
+
+	/* Page down with real callback set */
+	resp.page_down = false;
+	ui_scrollbar_set_cb(scrollbar, &test_scrollbar_cb, &resp);
+	ui_scrollbar_page_down(scrollbar);
+	PCUT_ASSERT_TRUE(resp.page_down);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_scrollbar_moved() delivers moved event */
+PCUT_TEST(moved)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar;
+	test_cb_resp_t resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Moved with no callbacks set */
+	ui_scrollbar_moved(scrollbar, 42);
+
+	/* Moved with callback not implementing moved */
+	ui_scrollbar_set_cb(scrollbar, &dummy_scrollbar_cb, NULL);
+	ui_scrollbar_moved(scrollbar, 42);
+
+	/* Moved with real callback set */
+	resp.moved = false;
+	resp.pos = 0;
+	ui_scrollbar_set_cb(scrollbar, &test_scrollbar_cb, &resp);
+	ui_scrollbar_moved(scrollbar, 42);
+	PCUT_ASSERT_TRUE(resp.moved);
+	PCUT_ASSERT_INT_EQUALS(42, resp.pos);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_scrollbar_pos_event() detects thumb press/release */
+PCUT_TEST(pos_event_press_release_thumb)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar;
+	ui_evclaim_t claim;
+	pos_event_t event;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(scrollbar->thumb_held);
+
+	rect.p0.x = 20;
+	rect.p0.y = 10;
+	rect.p1.x = 100;
+	rect.p1.y = 30;
+	ui_scrollbar_set_rect(scrollbar, &rect);
+
+	/* Press outside is not claimed and does nothing */
+	event.type = POS_PRESS;
+	event.hpos = 1;
+	event.vpos = 2;
+	claim = ui_scrollbar_pos_event(scrollbar, &event);
+	PCUT_ASSERT_FALSE(scrollbar->thumb_held);
+	PCUT_ASSERT_EQUALS(ui_unclaimed, claim);
+
+	/* Press inside thumb is claimed and depresses it */
+	event.type = POS_PRESS;
+	event.hpos = 50;
+	event.vpos = 20;
+	claim = ui_scrollbar_pos_event(scrollbar, &event);
+	PCUT_ASSERT_TRUE(scrollbar->thumb_held);
+	PCUT_ASSERT_EQUALS(ui_claimed, claim);
+
+	/* Release outside (or anywhere) is claimed and relases thumb */
+	event.type = POS_RELEASE;
+	event.hpos = 41;
+	event.vpos = 32;
+	claim = ui_scrollbar_pos_event(scrollbar, &event);
+	PCUT_ASSERT_FALSE(scrollbar->thumb_held);
+	PCUT_ASSERT_EQUALS(ui_claimed, claim);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_scrollbar_pos_event() detects up button press/release */
+PCUT_TEST(pos_event_press_release_up_btn)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar;
+	ui_evclaim_t claim;
+	pos_event_t event;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(scrollbar->thumb_held);
+
+	rect.p0.x = 20;
+	rect.p0.y = 10;
+	rect.p1.x = 100;
+	rect.p1.y = 30;
+	ui_scrollbar_set_rect(scrollbar, &rect);
+
+	/* Press inside up button is claimed and depresses it */
+	event.type = POS_PRESS;
+	event.hpos = 30;
+	event.vpos = 20;
+	claim = ui_scrollbar_pos_event(scrollbar, &event);
+	PCUT_ASSERT_TRUE(scrollbar->up_btn->held);
+	PCUT_ASSERT_EQUALS(ui_claimed, claim);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_scrollbar_pos_event() detects upper trough press/release */
+PCUT_TEST(pos_event_press_release_upper_trough)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar;
+	ui_evclaim_t claim;
+	pos_event_t event;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(scrollbar->upper_trough_held);
+
+	rect.p0.x = 20;
+	rect.p0.y = 10;
+	rect.p1.x = 100;
+	rect.p1.y = 30;
+	ui_scrollbar_set_rect(scrollbar, &rect);
+
+	/* Need to move thumb so that upper trough can be accessed */
+	ui_scrollbar_set_pos(scrollbar, 42);
+
+	/* Press inside upper trough is claimed and depresses it */
+	event.type = POS_PRESS;
+	event.hpos = 50;
+	event.vpos = 20;
+	claim = ui_scrollbar_pos_event(scrollbar, &event);
+	PCUT_ASSERT_TRUE(scrollbar->upper_trough_held);
+	PCUT_ASSERT_EQUALS(ui_claimed, claim);
+
+	/* Release outside (or anywhere) is claimed and relases upper trough */
+	event.type = POS_RELEASE;
+	event.hpos = 41;
+	event.vpos = 32;
+	claim = ui_scrollbar_pos_event(scrollbar, &event);
+	PCUT_ASSERT_FALSE(scrollbar->upper_trough_held);
+	PCUT_ASSERT_EQUALS(ui_claimed, claim);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_scrollbar_pos_event() detects lower trough press/release */
+PCUT_TEST(pos_event_press_release_lower_trough)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar;
+	ui_evclaim_t claim;
+	pos_event_t event;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(scrollbar->upper_trough_held);
+
+	rect.p0.x = 20;
+	rect.p0.y = 10;
+	rect.p1.x = 100;
+	rect.p1.y = 30;
+	ui_scrollbar_set_rect(scrollbar, &rect);
+
+	/* Press inside lower trough is claimed and depresses it */
+	event.type = POS_PRESS;
+	event.hpos = 70;
+	event.vpos = 20;
+	claim = ui_scrollbar_pos_event(scrollbar, &event);
+	PCUT_ASSERT_TRUE(scrollbar->lower_trough_held);
+	PCUT_ASSERT_EQUALS(ui_claimed, claim);
+
+	/* Release outside (or anywhere) is claimed and relases upper trough */
+	event.type = POS_RELEASE;
+	event.hpos = 41;
+	event.vpos = 32;
+	claim = ui_scrollbar_pos_event(scrollbar, &event);
+	PCUT_ASSERT_FALSE(scrollbar->lower_trough_held);
+	PCUT_ASSERT_EQUALS(ui_claimed, claim);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_scrollbar_pos_event() detects down button press/release */
+PCUT_TEST(pos_event_press_relese_down_btn)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_scrollbar_t *scrollbar;
+	ui_evclaim_t claim;
+	pos_event_t event;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	rc = ui_scrollbar_create(ui, window, ui_sbd_horiz, &scrollbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(scrollbar->thumb_held);
+
+	rect.p0.x = 20;
+	rect.p0.y = 10;
+	rect.p1.x = 100;
+	rect.p1.y = 30;
+	ui_scrollbar_set_rect(scrollbar, &rect);
+
+	/* Press inside down button is claimed and depresses it */
+	event.type = POS_PRESS;
+	event.hpos = 90;
+	event.vpos = 20;
+	claim = ui_scrollbar_pos_event(scrollbar, &event);
+	PCUT_ASSERT_TRUE(scrollbar->down_btn->held);
+	PCUT_ASSERT_EQUALS(ui_claimed, claim);
+
+	ui_scrollbar_destroy(scrollbar);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+static void test_scrollbar_up(ui_scrollbar_t *scrollbar, void *arg)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->up = true;
+}
+
+static void test_scrollbar_down(ui_scrollbar_t *scrollbar, void *arg)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->down = true;
+}
+
+static void test_scrollbar_page_up(ui_scrollbar_t *scrollbar, void *arg)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->page_up = true;
+}
+
+static void test_scrollbar_page_down(ui_scrollbar_t *scrollbar, void *arg)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->page_down = true;
+}
+
+static void test_scrollbar_moved(ui_scrollbar_t *scrollbar, void *arg, gfx_coord_t pos)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->moved = true;
+	resp->pos = pos;
+}
+
+PCUT_EXPORT(scrollbar);
Index: uspace/lib/ui/test/selectdialog.c
===================================================================
--- uspace/lib/ui/test/selectdialog.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/test/selectdialog.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,372 @@
+/*
+ * 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.
+ */
+
+#include <pcut/pcut.h>
+#include <stdbool.h>
+#include <ui/list.h>
+#include <ui/pbutton.h>
+#include <ui/ui.h>
+#include <ui/selectdialog.h>
+#include "../private/list.h"
+#include "../private/selectdialog.h"
+#include "../private/window.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(select_dialog);
+
+static void test_dialog_bok(ui_select_dialog_t *, void *, void *);
+static void test_dialog_bcancel(ui_select_dialog_t *, void *);
+static void test_dialog_close(ui_select_dialog_t *, void *);
+
+static ui_select_dialog_cb_t test_select_dialog_cb = {
+	.bok = test_dialog_bok,
+	.bcancel = test_dialog_bcancel,
+	.close = test_dialog_close
+};
+
+static ui_select_dialog_cb_t dummy_select_dialog_cb = {
+};
+
+typedef struct {
+	bool bok;
+	const char *fname;
+	bool bcancel;
+	bool close;
+} test_cb_resp_t;
+
+/** Create and destroy select dialog */
+PCUT_TEST(create_destroy)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_select_dialog_params_t params;
+	ui_select_dialog_t *dialog = NULL;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_select_dialog_params_init(&params);
+	params.caption = "Select one";
+	params.prompt = "Please select";
+
+	rc = ui_select_dialog_create(ui, &params, &dialog);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dialog);
+
+	ui_select_dialog_destroy(dialog);
+	ui_destroy(ui);
+}
+
+/** ui_select_dialog_destroy() can take NULL argument (no-op) */
+PCUT_TEST(destroy_null)
+{
+	ui_select_dialog_destroy(NULL);
+}
+
+/** Clicking OK invokes callback set via ui_select_dialog_set_cb() */
+PCUT_TEST(bok_cb)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_select_dialog_params_t params;
+	ui_select_dialog_t *dialog = NULL;
+	test_cb_resp_t resp;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_select_dialog_params_init(&params);
+	params.caption = "Select one";
+	params.prompt = "Please select";
+
+	rc = ui_select_dialog_create(ui, &params, &dialog);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dialog);
+
+	/* OK button callback with no callbacks set */
+	ui_pbutton_clicked(dialog->bok);
+
+	/* OK button callback with callback not implemented */
+	ui_select_dialog_set_cb(dialog, &dummy_select_dialog_cb, NULL);
+	ui_pbutton_clicked(dialog->bok);
+
+	/* OK button callback with real callback set */
+	resp.bok = false;
+	ui_select_dialog_set_cb(dialog, &test_select_dialog_cb, &resp);
+	ui_pbutton_clicked(dialog->bok);
+	PCUT_ASSERT_TRUE(resp.bok);
+
+	ui_select_dialog_destroy(dialog);
+	ui_destroy(ui);
+}
+
+/** Clicking Cancel invokes callback set via ui_select_dialog_set_cb() */
+PCUT_TEST(bcancel_cb)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_select_dialog_params_t params;
+	ui_select_dialog_t *dialog = NULL;
+	test_cb_resp_t resp;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_select_dialog_params_init(&params);
+	params.caption = "Select one";
+	params.prompt = "Please select";
+
+	rc = ui_select_dialog_create(ui, &params, &dialog);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dialog);
+
+	/* Cancel button callback with no callbacks set */
+	ui_pbutton_clicked(dialog->bcancel);
+
+	/* Cancel button callback with callback not implemented */
+	ui_select_dialog_set_cb(dialog, &dummy_select_dialog_cb, NULL);
+	ui_pbutton_clicked(dialog->bcancel);
+
+	/* OK button callback with real callback set */
+	resp.bcancel = false;
+	ui_select_dialog_set_cb(dialog, &test_select_dialog_cb, &resp);
+	ui_pbutton_clicked(dialog->bcancel);
+	PCUT_ASSERT_TRUE(resp.bcancel);
+
+	ui_select_dialog_destroy(dialog);
+	ui_destroy(ui);
+}
+
+/** Selecting a list entry invokes bok callback set via ui_select_dialog_set_cb() */
+PCUT_TEST(lselect_cb)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_list_entry_t *entry;
+	ui_select_dialog_params_t params;
+	ui_select_dialog_t *dialog = NULL;
+	ui_list_entry_attr_t attr;
+	test_cb_resp_t resp;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_select_dialog_params_init(&params);
+	params.caption = "Select one";
+	params.prompt = "Please select";
+
+	rc = ui_select_dialog_create(ui, &params, &dialog);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dialog);
+
+	/* Need an entry to select */
+	ui_list_entry_attr_init(&attr);
+	attr.caption = "Entry";
+	rc = ui_select_dialog_append(dialog, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	entry = ui_list_first(dialog->list);
+
+	/* Select entry with no callbacks set */
+	ui_list_selected(entry);
+
+	/* Select entry with callback not implemented */
+	ui_select_dialog_set_cb(dialog, &dummy_select_dialog_cb, NULL);
+	ui_list_selected(entry);
+
+	/* Select entry with real callback set */
+	resp.bok = false;
+	ui_select_dialog_set_cb(dialog, &test_select_dialog_cb, &resp);
+	ui_list_selected(entry);
+	PCUT_ASSERT_TRUE(resp.bok);
+
+	ui_select_dialog_destroy(dialog);
+	ui_destroy(ui);
+}
+
+/** Sending window close request invokes callback set via
+ * ui_select_dialog_set_cb()
+ */
+PCUT_TEST(close_cb)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_select_dialog_params_t params;
+	ui_select_dialog_t *dialog = NULL;
+	test_cb_resp_t resp;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_select_dialog_params_init(&params);
+	params.caption = "Select one";
+	params.prompt = "Please select";
+
+	rc = ui_select_dialog_create(ui, &params, &dialog);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dialog);
+
+	/* Button callback with no callbacks set */
+	ui_window_send_close(dialog->window);
+
+	/* Button callback with unfocus callback not implemented */
+	ui_select_dialog_set_cb(dialog, &dummy_select_dialog_cb, NULL);
+	ui_window_send_close(dialog->window);
+
+	/* Button callback with real callback set */
+	resp.close = false;
+	ui_select_dialog_set_cb(dialog, &test_select_dialog_cb, &resp);
+	ui_window_send_close(dialog->window);
+	PCUT_ASSERT_TRUE(resp.close);
+
+	ui_select_dialog_destroy(dialog);
+	ui_destroy(ui);
+}
+
+/** ui_select_dialog_append() appends entries TBD */
+PCUT_TEST(append)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_select_dialog_params_t params;
+	ui_select_dialog_t *dialog = NULL;
+	ui_list_entry_attr_t attr;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_select_dialog_params_init(&params);
+	params.caption = "Select one";
+	params.prompt = "Please select";
+
+	rc = ui_select_dialog_create(ui, &params, &dialog);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dialog);
+
+	PCUT_ASSERT_INT_EQUALS(0, dialog->list->entries_cnt);
+
+	/* Add one entry */
+	ui_list_entry_attr_init(&attr);
+	attr.caption = "Entry";
+	rc = ui_select_dialog_append(dialog, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(1, dialog->list->entries_cnt);
+
+	ui_select_dialog_destroy(dialog);
+	ui_destroy(ui);
+}
+
+/** ui_select_dialog_paint() succeeds */
+PCUT_TEST(paint)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_select_dialog_params_t params;
+	ui_select_dialog_t *dialog = NULL;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_select_dialog_params_init(&params);
+	params.caption = "Select one";
+	params.prompt = "Please select";
+
+	rc = ui_select_dialog_create(ui, &params, &dialog);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dialog);
+
+	rc = ui_select_dialog_paint(dialog);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_select_dialog_destroy(dialog);
+	ui_destroy(ui);
+}
+
+/** ui_select_dialog_list() returns the UI list */
+PCUT_TEST(list)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_select_dialog_params_t params;
+	ui_select_dialog_t *dialog = NULL;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_select_dialog_params_init(&params);
+	params.caption = "Select one";
+	params.prompt = "Please select";
+
+	rc = ui_select_dialog_create(ui, &params, &dialog);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dialog);
+
+	list = ui_select_dialog_list(dialog);
+	PCUT_ASSERT_NOT_NULL(list);
+
+	PCUT_ASSERT_INT_EQUALS(0, ui_list_entries_cnt(list));
+
+	/* Add one entry */
+	ui_list_entry_attr_init(&attr);
+	attr.caption = "Entry";
+	rc = ui_select_dialog_append(dialog, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(1, ui_list_entries_cnt(list));
+
+	ui_select_dialog_destroy(dialog);
+	ui_destroy(ui);
+}
+
+static void test_dialog_bok(ui_select_dialog_t *dialog, void *arg,
+    void *earg)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->bok = true;
+}
+
+static void test_dialog_bcancel(ui_select_dialog_t *dialog, void *arg)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->bcancel = true;
+}
+
+static void test_dialog_close(ui_select_dialog_t *dialog, void *arg)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->close = true;
+}
+
+PCUT_EXPORT(select_dialog);
Index: uspace/lib/ui/test/slider.c
===================================================================
--- uspace/lib/ui/test/slider.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/test/slider.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -99,5 +99,5 @@
 	errno_t rc;
 
-	rc = ui_slider_create(NULL, "Hello", &slider);
+	rc = ui_slider_create(NULL, &slider);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(slider);
@@ -119,5 +119,5 @@
 	errno_t rc;
 
-	rc = ui_slider_create(NULL, "Hello", &slider);
+	rc = ui_slider_create(NULL, &slider);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -135,5 +135,5 @@
 	errno_t rc;
 
-	rc = ui_slider_create(NULL, "Hello", &slider);
+	rc = ui_slider_create(NULL, &slider);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -169,5 +169,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_slider_create(resource, "Hello", &slider);
+	rc = ui_slider_create(resource, &slider);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -199,5 +199,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_slider_create(resource, "Hello", &slider);
+	rc = ui_slider_create(resource, &slider);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -219,5 +219,5 @@
 	test_cb_resp_t resp;
 
-	rc = ui_slider_create(NULL, "Hello", &slider);
+	rc = ui_slider_create(NULL, &slider);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -225,5 +225,5 @@
 	ui_slider_moved(slider, 42);
 
-	/* Moved with callback not implementing clicked */
+	/* Moved with callback not implementing moved */
 	ui_slider_set_cb(slider, &dummy_slider_cb, NULL);
 	ui_slider_moved(slider, 42);
@@ -260,5 +260,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_slider_create(resource, "Hello", &slider);
+	rc = ui_slider_create(resource, &slider);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -316,5 +316,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_slider_create(resource, "Hello", &slider);
+	rc = ui_slider_create(resource, &slider);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -380,5 +380,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_slider_create(resource, "Hello", &slider);
+	rc = ui_slider_create(resource, &slider);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -441,5 +441,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_slider_create(resource, "Hello", &slider);
+	rc = ui_slider_create(resource, &slider);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
Index: uspace/lib/ui/test/tab.c
===================================================================
--- uspace/lib/ui/test/tab.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/test/tab.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,844 @@
+/*
+ * 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.
+ */
+
+#include <gfx/context.h>
+#include <gfx/coord.h>
+#include <mem.h>
+#include <pcut/pcut.h>
+#include <stdbool.h>
+#include <str.h>
+#include <ui/control.h>
+#include <ui/tab.h>
+#include <ui/tabset.h>
+#include <ui/testctl.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include "../private/resource.h"
+#include "../private/tab.h"
+#include "../private/tabset.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(tab);
+
+/** Create and destroy tab */
+PCUT_TEST(create_destroy)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	ui_tab_t *tab = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_tab_create(tabset, "Test", &tab);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tab);
+
+	/*
+	 * Normally we don't need to destroy a tab explicitly, it will
+	 * be destroyed along with tab bar, but here we'll test destroying
+	 * it explicitly.
+	 */
+	ui_tab_destroy(tab);
+	ui_tab_set_destroy(tabset);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Destroy tab implicitly by destroying the tab set */
+PCUT_TEST(implicit_destroy)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	ui_tab_t *tab = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_tab_create(tabset, "Test", &tab);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tab);
+
+	/* Let the tab be destroyed as part of destroying tab set */
+	ui_tab_set_destroy(tabset);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_tab_destroy() can take NULL argument (no-op) */
+PCUT_TEST(destroy_null)
+{
+	ui_tab_destroy(NULL);
+}
+
+/** ui_tab_first() / ui_tab_next() iterate over tabs */
+PCUT_TEST(first_next)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	ui_tab_t *tab1 = NULL;
+	ui_tab_t *tab2 = NULL;
+	ui_tab_t *t;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	rc = ui_tab_create(tabset, "Test 1", &tab1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tab1);
+
+	rc = ui_tab_create(tabset, "Test 2", &tab2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tab2);
+
+	t = ui_tab_first(tabset);
+	PCUT_ASSERT_EQUALS(tab1, t);
+
+	t = ui_tab_next(t);
+	PCUT_ASSERT_EQUALS(tab2, t);
+
+	t = ui_tab_next(t);
+	PCUT_ASSERT_NULL(t);
+
+	ui_tab_set_destroy(tabset);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_tab_last() / ui_tab_prev() iterate over tabs in reverse */
+PCUT_TEST(last_prev)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	ui_tab_t *tab1 = NULL;
+	ui_tab_t *tab2 = NULL;
+	ui_tab_t *t;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	rc = ui_tab_create(tabset, "Test 1", &tab1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tab1);
+
+	rc = ui_tab_create(tabset, "Test 2", &tab2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tab2);
+
+	t = ui_tab_last(tabset);
+	PCUT_ASSERT_EQUALS(tab2, t);
+
+	t = ui_tab_prev(t);
+	PCUT_ASSERT_EQUALS(tab1, t);
+
+	t = ui_tab_prev(t);
+	PCUT_ASSERT_NULL(t);
+
+	ui_tab_set_destroy(tabset);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_tab_is_selected() correctly returns tab state */
+PCUT_TEST(is_selected)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	ui_tab_t *tab1 = NULL;
+	ui_tab_t *tab2 = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	rc = ui_tab_create(tabset, "Test 1", &tab1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* The first added tab should be automatically selected */
+	PCUT_ASSERT_TRUE(ui_tab_is_selected(tab1));
+
+	rc = ui_tab_create(tabset, "Test 2", &tab2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* After adding a second tab the first should still be selected */
+	PCUT_ASSERT_TRUE(ui_tab_is_selected(tab1));
+	PCUT_ASSERT_FALSE(ui_tab_is_selected(tab2));
+
+	/* Select second tab */
+	ui_tab_set_select(tabset, tab2);
+
+	/* Now the second tab should be selected */
+	PCUT_ASSERT_FALSE(ui_tab_is_selected(tab1));
+	PCUT_ASSERT_TRUE(ui_tab_is_selected(tab2));
+
+	ui_tab_set_destroy(tabset);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_tab_add() adds control to tab */
+PCUT_TEST(add)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	ui_tab_t *tab = NULL;
+	ui_test_ctl_t *testctl = NULL;
+	ui_tc_resp_t resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	rc = ui_tab_create(tabset, "Test", &tab);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tab);
+
+	rc = ui_test_ctl_create(&resp, &testctl);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Add test control to tab */
+	ui_tab_add(tab, ui_test_ctl_ctl(testctl));
+
+	resp.destroy = false;
+
+	ui_tab_set_destroy(tabset);
+
+	/* Destroying the tab should have destroyed the control as well */
+	PCUT_ASSERT_TRUE(resp.destroy);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_tab_remove() removes control to tab */
+PCUT_TEST(remove)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	ui_tab_t *tab = NULL;
+	ui_test_ctl_t *testctl = NULL;
+	ui_tc_resp_t resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	rc = ui_tab_create(tabset, "Test", &tab);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tab);
+
+	rc = ui_test_ctl_create(&resp, &testctl);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Add test control to tab */
+	ui_tab_add(tab, ui_test_ctl_ctl(testctl));
+
+	/* Rmove control from tab */
+	ui_tab_remove(tab, ui_test_ctl_ctl(testctl));
+
+	resp.destroy = false;
+
+	ui_tab_set_destroy(tabset);
+
+	/* Destroying the tab should NOT have destroyed the control */
+	PCUT_ASSERT_FALSE(resp.destroy);
+
+	ui_test_ctl_destroy(testctl);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Paint tab */
+PCUT_TEST(paint)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	ui_tab_t *tab = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	rc = ui_tab_create(tabset, "Test", &tab);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tab);
+
+	rc = ui_tab_paint(tab);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_tab_set_destroy(tabset);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_tab_kbd_event() delivers keyboard event */
+PCUT_TEST(kbd_event)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	ui_tab_t *tab = NULL;
+	ui_evclaim_t claimed;
+	kbd_event_t event;
+	ui_test_ctl_t *testctl = NULL;
+	ui_tc_resp_t resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	/* Without anytabs, event should be unclaimed */
+	event.type = KEY_PRESS;
+	event.key = KC_ENTER;
+	event.mods = 0;
+	claimed = ui_tab_set_kbd_event(tabset, &event);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS(ui_unclaimed, claimed);
+
+	rc = ui_tab_create(tabset, "Test", &tab);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tab);
+
+	rc = ui_test_ctl_create(&resp, &testctl);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Set up response */
+	ui_tab_add(tab, ui_test_ctl_ctl(testctl));
+	resp.claim = ui_claimed;
+	resp.kbd = false;
+
+	/* Send keyboard event */
+	event.type = KEY_PRESS;
+	event.key = KC_F10;
+	event.mods = 0;
+	claimed = ui_tab_kbd_event(tab, &event);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
+
+	/* Make sure event was delivered */
+	PCUT_ASSERT_TRUE(resp.kbd);
+	PCUT_ASSERT_EQUALS(event.type, resp.kevent.type);
+	PCUT_ASSERT_EQUALS(event.key, resp.kevent.key);
+	PCUT_ASSERT_EQUALS(event.mods, resp.kevent.mods);
+
+	ui_tab_set_destroy(tabset);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_tab_pos_event() delivers position event */
+PCUT_TEST(pos_event)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	gfx_rect_t rect;
+	ui_tab_t *tab = NULL;
+	ui_evclaim_t claimed;
+	pos_event_t event;
+	ui_test_ctl_t *testctl = NULL;
+	ui_tc_resp_t resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 100;
+	rect.p1.y = 200;
+	ui_tab_set_set_rect(tabset, &rect);
+
+	rc = ui_tab_create(tabset, "Test", &tab);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tab);
+
+	rc = ui_test_ctl_create(&resp, &testctl);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Set up response */
+	ui_tab_add(tab, ui_test_ctl_ctl(testctl));
+	resp.claim = ui_claimed;
+	resp.pos = false;
+
+	/* Send position event */
+	event.type = POS_PRESS;
+	event.hpos = 10;
+	event.vpos = 40;
+	claimed = ui_tab_pos_event(tab, &event);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
+
+	/* Make sure event was delivered */
+	PCUT_ASSERT_TRUE(resp.pos);
+	PCUT_ASSERT_EQUALS(event.type, resp.pevent.type);
+	PCUT_ASSERT_EQUALS(event.hpos, resp.pevent.hpos);
+	PCUT_ASSERT_EQUALS(event.vpos, resp.pevent.vpos);
+
+	ui_tab_set_destroy(tabset);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_tab_handle_width() and ui_tab_handle_height() return dimensions */
+PCUT_TEST(handle_width_height)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	ui_tab_t *tab = NULL;
+	gfx_coord_t w, h;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	rc = ui_tab_create(tabset, "Test", &tab);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tab);
+
+	w = ui_tab_handle_width(tab);
+	h = ui_tab_handle_height(tab);
+
+	PCUT_ASSERT_INT_EQUALS(50, w);
+	PCUT_ASSERT_INT_EQUALS(25, h);
+
+	ui_tab_set_destroy(tabset);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Computing tab geometry */
+PCUT_TEST(get_geom)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	ui_tab_t *tab = NULL;
+	ui_tab_geom_t geom;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	rect.p0.x = 1000;
+	rect.p0.y = 2000;
+	rect.p1.x = 1100;
+	rect.p1.y = 2200;
+	ui_tab_set_set_rect(tabset, &rect);
+
+	rc = ui_tab_create(tabset, "Test", &tab);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tab);
+
+	ui_tab_get_geom(tab, &geom);
+
+	PCUT_ASSERT_INT_EQUALS(1006, geom.handle.p0.x);
+	PCUT_ASSERT_INT_EQUALS(2000, geom.handle.p0.y);
+	PCUT_ASSERT_INT_EQUALS(1056, geom.handle.p1.x);
+	PCUT_ASSERT_INT_EQUALS(2027, geom.handle.p1.y);
+
+	PCUT_ASSERT_INT_EQUALS(1006, geom.handle_area.p0.x);
+	PCUT_ASSERT_INT_EQUALS(2000, geom.handle_area.p0.y);
+	PCUT_ASSERT_INT_EQUALS(1056, geom.handle_area.p1.x);
+	PCUT_ASSERT_INT_EQUALS(2027, geom.handle_area.p1.y);
+
+	PCUT_ASSERT_INT_EQUALS(1000, geom.body.p0.x);
+	PCUT_ASSERT_INT_EQUALS(2025, geom.body.p0.y);
+	PCUT_ASSERT_INT_EQUALS(1100, geom.body.p1.x);
+	PCUT_ASSERT_INT_EQUALS(2200, geom.body.p1.y);
+
+	PCUT_ASSERT_INT_EQUALS(1014, geom.text_pos.x);
+	PCUT_ASSERT_INT_EQUALS(2007, geom.text_pos.y);
+
+	ui_tab_set_destroy(tabset);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_tab_patint_handle_frame() */
+PCUT_TEST(paint_handle_frame)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	gfx_context_t *gc;
+	gfx_rect_t rect;
+	gfx_rect_t irect;
+	gfx_coord_t chamfer;
+	gfx_color_t *hi_color;
+	gfx_color_t *sh_color;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+	gc = ui_window_get_gc(window);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 100;
+	rect.p1.y = 200;
+
+	chamfer = 4;
+
+	hi_color = res->wnd_highlight_color;
+	sh_color = res->wnd_shadow_color;
+
+	rc = ui_tab_paint_handle_frame(gc, &rect, chamfer, hi_color, sh_color,
+	    true, &irect);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_tab_paint_handle_frame(gc, &rect, chamfer, hi_color, sh_color,
+	    false, &irect);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_tab_paint_body_frame() */
+PCUT_TEST(paint_body_frame)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	ui_tab_t *tab = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	rc = ui_tab_create(tabset, "Test", &tab);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tab);
+
+	rc = ui_tab_paint_body_frame(tab);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_tab_set_destroy(tabset);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_tab_paint_frame() */
+PCUT_TEST(paint_frame)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	ui_tab_t *tab = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	rc = ui_tab_create(tabset, "Test", &tab);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tab);
+
+	rc = ui_tab_paint_frame(tab);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_tab_set_destroy(tabset);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_tab_get_res() returns the resource */
+PCUT_TEST(get_res)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	ui_tab_t *tab = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	rc = ui_tab_create(tabset, "Test", &tab);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tab);
+
+	PCUT_ASSERT_EQUALS(res, ui_tab_get_res(tab));
+
+	ui_tab_set_destroy(tabset);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+PCUT_EXPORT(tab);
Index: uspace/lib/ui/test/tabset.c
===================================================================
--- uspace/lib/ui/test/tabset.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/test/tabset.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,346 @@
+/*
+ * 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.
+ */
+
+#include <gfx/coord.h>
+#include <mem.h>
+#include <pcut/pcut.h>
+#include <stdbool.h>
+#include <ui/control.h>
+#include <ui/tab.h>
+#include <ui/tabset.h>
+#include <ui/testctl.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include "../private/tabset.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(tabset);
+
+/** Create and destroy tab set */
+PCUT_TEST(create_destroy)
+{
+	ui_tab_set_t *tabset = NULL;
+	errno_t rc;
+
+	rc = ui_tab_set_create(NULL, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	ui_tab_set_destroy(tabset);
+}
+
+/** ui_tab_set_destroy() can take NULL argument (no-op) */
+PCUT_TEST(destroy_null)
+{
+	ui_tab_set_destroy(NULL);
+}
+
+/** ui_tab_set_ctl() returns control that has a working virtual destructor */
+PCUT_TEST(ctl)
+{
+	ui_tab_set_t *tabset = NULL;
+	ui_control_t *control;
+	errno_t rc;
+
+	rc = ui_tab_set_create(NULL, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	control = ui_tab_set_ctl(tabset);
+	PCUT_ASSERT_NOT_NULL(control);
+
+	ui_control_destroy(control);
+}
+
+/** Set tab set rectangle sets internal field */
+PCUT_TEST(set_rect)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	gfx_rect_t rect;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	rect.p0.x = 1;
+	rect.p0.y = 2;
+	rect.p1.x = 3;
+	rect.p1.y = 4;
+
+	ui_tab_set_set_rect(tabset, &rect);
+	PCUT_ASSERT_INT_EQUALS(rect.p0.x, tabset->rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(rect.p0.y, tabset->rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(rect.p1.x, tabset->rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(rect.p1.y, tabset->rect.p1.y);
+
+	ui_tab_set_destroy(tabset);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Paint tab set */
+PCUT_TEST(paint)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	rc = ui_tab_set_paint(tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_tab_set_destroy(tabset);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Deliver tab set keyboard event */
+PCUT_TEST(kbd_event)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	ui_tab_t *tab = NULL;
+	ui_evclaim_t claimed;
+	kbd_event_t event;
+	ui_test_ctl_t *testctl = NULL;
+	ui_tc_resp_t resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	/* Without anytabs, event should be unclaimed */
+	event.type = KEY_PRESS;
+	event.key = KC_ENTER;
+	event.mods = 0;
+	claimed = ui_tab_set_kbd_event(tabset, &event);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS(ui_unclaimed, claimed);
+
+	rc = ui_tab_create(tabset, "Test", &tab);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tab);
+
+	rc = ui_test_ctl_create(&resp, &testctl);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Set up response */
+	ui_tab_add(tab, ui_test_ctl_ctl(testctl));
+	resp.claim = ui_claimed;
+	resp.kbd = false;
+
+	/* Send keyboard event */
+	event.type = KEY_PRESS;
+	event.key = KC_F10;
+	event.mods = 0;
+	claimed = ui_tab_set_kbd_event(tabset, &event);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
+
+	/* Make sure event was delivered */
+	PCUT_ASSERT_TRUE(resp.kbd);
+	PCUT_ASSERT_EQUALS(event.type, resp.kevent.type);
+	PCUT_ASSERT_EQUALS(event.key, resp.kevent.key);
+	PCUT_ASSERT_EQUALS(event.mods, resp.kevent.mods);
+
+	ui_tab_set_destroy(tabset);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Press event on tab handle selects tab */
+PCUT_TEST(pos_event_select)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	ui_tab_t *tab1 = NULL;
+	ui_tab_t *tab2 = NULL;
+	ui_evclaim_t claimed;
+	pos_event_t event;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	/* Without any tabs, event should be unclaimed */
+	event.type = POS_PRESS;
+	event.hpos = 80;
+	event.vpos = 4;
+	event.btn_num = 1;
+	claimed = ui_tab_set_pos_event(tabset, &event);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS(ui_unclaimed, claimed);
+
+	rc = ui_tab_create(tabset, "Test 1", &tab1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* The first added tab should be automatically selected */
+	PCUT_ASSERT_EQUALS(tab1, tabset->selected);
+
+	rc = ui_tab_create(tabset, "Test 2", &tab2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* After adding a second tab the first should still be selected */
+	PCUT_ASSERT_EQUALS(tab1, tabset->selected);
+
+	event.type = POS_PRESS;
+	event.hpos = 80;
+	event.vpos = 4;
+	event.btn_num = 1;
+	claimed = ui_tab_set_pos_event(tabset, &event);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
+
+	/* Clicking the second tab handle should select tab2 */
+	PCUT_ASSERT_EQUALS(tab2, tabset->selected);
+
+	ui_tab_set_destroy(tabset);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_tab_set_select() selects tab */
+PCUT_TEST(select)
+{
+	ui_t *ui = NULL;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t params;
+	ui_resource_t *res;
+	ui_tab_set_t *tabset = NULL;
+	ui_tab_t *tab1 = NULL;
+	ui_tab_t *tab2 = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+
+	rc = ui_tab_set_create(res, &tabset);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(tabset);
+
+	rc = ui_tab_create(tabset, "Test 1", &tab1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* The first added tab should be automatically selected */
+	PCUT_ASSERT_EQUALS(tab1, tabset->selected);
+
+	rc = ui_tab_create(tabset, "Test 2", &tab2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* After adding a second tab the first should still be selected */
+	PCUT_ASSERT_EQUALS(tab1, tabset->selected);
+
+	/* Select second tab */
+	ui_tab_set_select(tabset, tab2);
+
+	/* Now the second tab should be selected */
+	PCUT_ASSERT_EQUALS(tab2, tabset->selected);
+
+	ui_tab_set_destroy(tabset);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+PCUT_EXPORT(tabset);
Index: uspace/lib/ui/test/testctl.c
===================================================================
--- uspace/lib/ui/test/testctl.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/ui/test/testctl.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+#include <pcut/pcut.h>
+#include <ui/control.h>
+#include <ui/testctl.h>
+#include "../private/testctl.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(testctl);
+
+/** Create and destroy test control */
+PCUT_TEST(create_destroy)
+{
+	ui_test_ctl_t *testctl = NULL;
+	ui_tc_resp_t resp;
+	errno_t rc;
+
+	rc = ui_test_ctl_create(&resp, &testctl);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(testctl);
+
+	ui_test_ctl_destroy(testctl);
+}
+
+/** ui_test_ctl_destroy() can take NULL argument (no-op) */
+PCUT_TEST(destroy_null)
+{
+	ui_test_ctl_destroy(NULL);
+}
+
+/** ui_test_ctl_ctl() returns control that has a working virtual destructor */
+PCUT_TEST(ctl)
+{
+	ui_control_t *control;
+	ui_test_ctl_t *testctl;
+	errno_t rc;
+	ui_tc_resp_t resp;
+
+	rc = ui_test_ctl_create(&resp, &testctl);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	control = ui_test_ctl_ctl(testctl);
+	PCUT_ASSERT_NOT_NULL(control);
+
+	ui_control_destroy(control);
+}
+
+PCUT_EXPORT(testctl);
Index: uspace/lib/ui/test/ui.c
===================================================================
--- uspace/lib/ui/test/ui.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/test/ui.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -69,4 +69,31 @@
 }
 
+/** ui_suspend() / ui_resume() do nothing if we don't have a console,
+ * ui_is_suspended() returns suspend status
+ */
+PCUT_TEST(suspend_resume)
+{
+	ui_t *ui = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(ui);
+
+	PCUT_ASSERT_FALSE(ui_is_suspended(ui));
+
+	rc = ui_suspend(ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_TRUE(ui_is_suspended(ui));
+
+	rc = ui_resume(ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(ui_is_suspended(ui));
+
+	ui_destroy(ui);
+}
+
 /** ui_run() / ui_quit() */
 PCUT_TEST(run_quit)
@@ -151,3 +178,37 @@
 }
 
+/** ui_is_get_rect() */
+PCUT_TEST(get_rect)
+{
+	ui_t *ui = NULL;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(ui);
+
+	/* This won't work without a display service */
+	rc = ui_get_rect(ui, &rect);
+	PCUT_ASSERT_ERRNO_VAL(ENOTSUP, rc);
+
+	ui_destroy(ui);
+}
+
+/** ui_lock(), ui_unlock() */
+PCUT_TEST(lock_unlock)
+{
+	ui_t *ui = NULL;
+	errno_t rc;
+
+	rc = ui_create_disp((display_t *)(-1), &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(ui);
+
+	ui_lock(ui);
+	ui_unlock(ui);
+
+	ui_destroy(ui);
+}
+
 PCUT_EXPORT(ui);
Index: uspace/lib/ui/test/wdecor.c
===================================================================
--- uspace/lib/ui/test/wdecor.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/test/wdecor.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 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"
@@ -62,11 +63,26 @@
 };
 
+static void test_wdecor_sysmenu_open(ui_wdecor_t *, void *, sysarg_t);
+static void test_wdecor_sysmenu_left(ui_wdecor_t *, void *, sysarg_t);
+static void test_wdecor_sysmenu_right(ui_wdecor_t *, void *, sysarg_t);
+static void test_wdecor_sysmenu_accel(ui_wdecor_t *, void *, char32_t,
+    sysarg_t);
+static void test_wdecor_minimize(ui_wdecor_t *, void *);
+static void test_wdecor_maximize(ui_wdecor_t *, void *);
+static void test_wdecor_unmaximize(ui_wdecor_t *, void *);
 static void test_wdecor_close(ui_wdecor_t *, void *);
-static void test_wdecor_move(ui_wdecor_t *, void *, gfx_coord2_t *);
+static void test_wdecor_move(ui_wdecor_t *, void *, gfx_coord2_t *, sysarg_t);
 static void test_wdecor_resize(ui_wdecor_t *, void *, ui_wdecor_rsztype_t,
-    gfx_coord2_t *);
+    gfx_coord2_t *, sysarg_t);
 static void test_wdecor_set_cursor(ui_wdecor_t *, void *, ui_stock_cursor_t);
 
 static ui_wdecor_cb_t test_wdecor_cb = {
+	.sysmenu_open = test_wdecor_sysmenu_open,
+	.sysmenu_left = test_wdecor_sysmenu_left,
+	.sysmenu_right = test_wdecor_sysmenu_right,
+	.sysmenu_accel = test_wdecor_sysmenu_accel,
+	.minimize = test_wdecor_minimize,
+	.maximize = test_wdecor_maximize,
+	.unmaximize = test_wdecor_unmaximize,
 	.close = test_wdecor_close,
 	.move = test_wdecor_move,
@@ -96,7 +112,17 @@
 
 typedef struct {
+	bool sysmenu_open;
+	bool sysmenu_left;
+	bool sysmenu_right;
+	bool sysmenu_accel;
+	bool minimize;
+	bool maximize;
+	bool unmaximize;
 	bool close;
 	bool move;
 	gfx_coord2_t pos;
+	sysarg_t pos_id;
+	sysarg_t idev_id;
+	char32_t accel;
 	bool resize;
 	ui_wdecor_rsztype_t rsztype;
@@ -105,5 +131,5 @@
 } test_cb_resp_t;
 
-/** Create and destroy button */
+/** Create and destroy window decoration */
 PCUT_TEST(create_destroy)
 {
@@ -127,9 +153,20 @@
 PCUT_TEST(set_rect)
 {
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	ui_resource_t *resource = NULL;
 	ui_wdecor_t *wdecor;
 	gfx_rect_t rect;
 	errno_t rc;
 
-	rc = ui_wdecor_create(NULL, "Hello", ui_wds_none, &wdecor);
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_wdecor_create(resource, "Hello", ui_wds_none, &wdecor);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -146,4 +183,8 @@
 
 	ui_wdecor_destroy(wdecor);
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 }
 
@@ -168,6 +209,26 @@
 }
 
-/** Paint button */
-PCUT_TEST(paint)
+/** Set window decoration maximized sets internal field */
+PCUT_TEST(set_maximized)
+{
+	ui_wdecor_t *wdecor;
+	errno_t rc;
+
+	rc = ui_wdecor_create(NULL, "Hello", ui_wds_none, &wdecor);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_TRUE(wdecor->active);
+
+	ui_wdecor_set_maximized(wdecor, false);
+	PCUT_ASSERT_FALSE(wdecor->maximized);
+
+	ui_wdecor_set_maximized(wdecor, true);
+	PCUT_ASSERT_TRUE(wdecor->maximized);
+
+	ui_wdecor_destroy(wdecor);
+}
+
+/** Setting system menu handle as active/inactive */
+PCUT_TEST(sysmenu_hdl_set_active)
 {
 	errno_t rc;
@@ -185,5 +246,70 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_wdecor_create(resource, "Hello", ui_wds_none, &wdecor);
+	rc = ui_wdecor_create(resource, "Hello", ui_wds_decorated, &wdecor);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(wdecor->sysmenu_hdl_active);
+	ui_wdecor_sysmenu_hdl_set_active(wdecor, true);
+	PCUT_ASSERT_TRUE(wdecor->sysmenu_hdl_active);
+	ui_wdecor_sysmenu_hdl_set_active(wdecor, false);
+	PCUT_ASSERT_FALSE(wdecor->sysmenu_hdl_active);
+
+	ui_wdecor_destroy(wdecor);
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Paint system menu handle */
+PCUT_TEST(sysmenu_hdl_paint)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	ui_resource_t *resource = NULL;
+	ui_wdecor_t *wdecor;
+	ui_wdecor_geom_t geom;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_wdecor_create(resource, "Hello", ui_wds_decorated, &wdecor);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wdecor_get_geom(wdecor, &geom);
+	rc = ui_wdecor_sysmenu_hdl_paint(wdecor, &geom.sysmenu_hdl_rect);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wdecor_destroy(wdecor);
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Paint window decoration */
+PCUT_TEST(paint)
+{
+	errno_t rc;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	ui_resource_t *resource = NULL;
+	ui_wdecor_t *wdecor;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_wdecor_create(resource, "Hello", ui_wds_decorated, &wdecor);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -196,4 +322,195 @@
 	rc = gfx_context_delete(gc);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Test ui_wdecor_sysmenu_open() */
+PCUT_TEST(sysmenu_open)
+{
+	errno_t rc;
+	ui_wdecor_t *wdecor;
+	test_cb_resp_t resp;
+
+	rc = ui_wdecor_create(NULL, "Hello", ui_wds_none, &wdecor);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Sysmenu open callback with no callbacks set */
+	ui_wdecor_sysmenu_open(wdecor, 42);
+
+	/* Sysmenu open callback with sysmenu callback not implemented */
+	ui_wdecor_set_cb(wdecor, &dummy_wdecor_cb, NULL);
+	ui_wdecor_sysmenu_open(wdecor, 42);
+
+	/* Sysmenu open callback with real callback set */
+	resp.sysmenu_open = false;
+	resp.idev_id = 0;
+	ui_wdecor_set_cb(wdecor, &test_wdecor_cb, &resp);
+	ui_wdecor_sysmenu_open(wdecor, 42);
+	PCUT_ASSERT_TRUE(resp.sysmenu_open);
+	PCUT_ASSERT_INT_EQUALS(42, resp.idev_id);
+
+	ui_wdecor_destroy(wdecor);
+}
+
+/** Test ui_wdecor_sysmenu_left() */
+PCUT_TEST(sysmenu_left)
+{
+	errno_t rc;
+	ui_wdecor_t *wdecor;
+	test_cb_resp_t resp;
+
+	rc = ui_wdecor_create(NULL, "Hello", ui_wds_none, &wdecor);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Sysmenu left callback with no callbacks set */
+	ui_wdecor_sysmenu_left(wdecor, 42);
+
+	/* Sysmenu left callback with sysmenu callback not implemented */
+	ui_wdecor_set_cb(wdecor, &dummy_wdecor_cb, NULL);
+	ui_wdecor_sysmenu_left(wdecor, 42);
+
+	/* Sysmenu left callback with real callback set */
+	resp.sysmenu_left = false;
+	resp.idev_id = 0;
+	ui_wdecor_set_cb(wdecor, &test_wdecor_cb, &resp);
+	ui_wdecor_sysmenu_left(wdecor, 42);
+	PCUT_ASSERT_TRUE(resp.sysmenu_left);
+	PCUT_ASSERT_INT_EQUALS(42, resp.idev_id);
+
+	ui_wdecor_destroy(wdecor);
+}
+
+/** Test ui_wdecor_sysmenu_right() */
+PCUT_TEST(sysmenu_right)
+{
+	errno_t rc;
+	ui_wdecor_t *wdecor;
+	test_cb_resp_t resp;
+
+	rc = ui_wdecor_create(NULL, "Hello", ui_wds_none, &wdecor);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Sysmenu right callback with no callbacks set */
+	ui_wdecor_sysmenu_right(wdecor, 42);
+
+	/* Sysmenu right callback with sysmenu callback not implemented */
+	ui_wdecor_set_cb(wdecor, &dummy_wdecor_cb, NULL);
+	ui_wdecor_sysmenu_right(wdecor, 42);
+
+	/* Sysmenu right callback with real callback set */
+	resp.sysmenu_right = false;
+	resp.idev_id = 0;
+	ui_wdecor_set_cb(wdecor, &test_wdecor_cb, &resp);
+	ui_wdecor_sysmenu_right(wdecor, 42);
+	PCUT_ASSERT_TRUE(resp.sysmenu_right);
+	PCUT_ASSERT_INT_EQUALS(42, resp.idev_id);
+
+	ui_wdecor_destroy(wdecor);
+}
+
+/** Test ui_wdecor_sysmenu_accel() */
+PCUT_TEST(sysmenu_accel)
+{
+	errno_t rc;
+	ui_wdecor_t *wdecor;
+	test_cb_resp_t resp;
+
+	rc = ui_wdecor_create(NULL, "Hello", ui_wds_none, &wdecor);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Sysmenu accelerator callback with no callbacks set */
+	ui_wdecor_sysmenu_accel(wdecor, 'a', 42);
+
+	/* Sysmenu accelerator callback with sysmenu callback not implemented */
+	ui_wdecor_set_cb(wdecor, &dummy_wdecor_cb, NULL);
+	ui_wdecor_sysmenu_accel(wdecor, 'a', 42);
+
+	/* Sysmenu accelerator callback with real callback set */
+	resp.sysmenu_accel = false;
+	resp.idev_id = 0;
+	ui_wdecor_set_cb(wdecor, &test_wdecor_cb, &resp);
+	ui_wdecor_sysmenu_accel(wdecor, 'a', 42);
+	PCUT_ASSERT_TRUE(resp.sysmenu_accel);
+	PCUT_ASSERT_INT_EQUALS('a', resp.accel);
+	PCUT_ASSERT_INT_EQUALS(42, resp.idev_id);
+
+	ui_wdecor_destroy(wdecor);
+}
+
+/** Test ui_wdecor_minimize() */
+PCUT_TEST(minimize)
+{
+	errno_t rc;
+	ui_wdecor_t *wdecor;
+	test_cb_resp_t resp;
+
+	rc = ui_wdecor_create(NULL, "Hello", ui_wds_none, &wdecor);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Minimize callback with no callbacks set */
+	ui_wdecor_minimize(wdecor);
+
+	/* Minimize callback with minimize callback not implemented */
+	ui_wdecor_set_cb(wdecor, &dummy_wdecor_cb, NULL);
+	ui_wdecor_minimize(wdecor);
+
+	/* Minimize callback with real callback set */
+	resp.minimize = false;
+	ui_wdecor_set_cb(wdecor, &test_wdecor_cb, &resp);
+	ui_wdecor_minimize(wdecor);
+	PCUT_ASSERT_TRUE(resp.minimize);
+
+	ui_wdecor_destroy(wdecor);
+}
+
+/** Test ui_wdecor_maximize() */
+PCUT_TEST(maximize)
+{
+	errno_t rc;
+	ui_wdecor_t *wdecor;
+	test_cb_resp_t resp;
+
+	rc = ui_wdecor_create(NULL, "Hello", ui_wds_none, &wdecor);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Maximize callback with no callbacks set */
+	ui_wdecor_maximize(wdecor);
+
+	/* Maxmimize callback with maximize callback not implemented */
+	ui_wdecor_set_cb(wdecor, &dummy_wdecor_cb, NULL);
+	ui_wdecor_maximize(wdecor);
+
+	/* Maximize callback with real callback set */
+	resp.maximize = false;
+	ui_wdecor_set_cb(wdecor, &test_wdecor_cb, &resp);
+	ui_wdecor_maximize(wdecor);
+	PCUT_ASSERT_TRUE(resp.maximize);
+
+	ui_wdecor_destroy(wdecor);
+}
+
+/** Test ui_wdecor_unmaximize() */
+PCUT_TEST(unmaximize)
+{
+	errno_t rc;
+	ui_wdecor_t *wdecor;
+	test_cb_resp_t resp;
+
+	rc = ui_wdecor_create(NULL, "Hello", ui_wds_none, &wdecor);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Unmaximize callback with no callbacks set */
+	ui_wdecor_unmaximize(wdecor);
+
+	/* Unmaximize callback with unmaximize callback not implemented */
+	ui_wdecor_set_cb(wdecor, &dummy_wdecor_cb, NULL);
+	ui_wdecor_unmaximize(wdecor);
+
+	/* Unmaximize callback with real callback set */
+	resp.unmaximize = false;
+	ui_wdecor_set_cb(wdecor, &test_wdecor_cb, &resp);
+	ui_wdecor_unmaximize(wdecor);
+	PCUT_ASSERT_TRUE(resp.unmaximize);
+
+	ui_wdecor_destroy(wdecor);
 }
 
@@ -231,4 +548,5 @@
 	test_cb_resp_t resp;
 	gfx_coord2_t pos;
+	sysarg_t pos_id;
 
 	rc = ui_wdecor_create(NULL, "Hello", ui_wds_none, &wdecor);
@@ -237,11 +555,12 @@
 	pos.x = 3;
 	pos.y = 4;
+	pos_id = 5;
 
 	/* Move callback with no callbacks set */
-	ui_wdecor_move(wdecor, &pos);
+	ui_wdecor_move(wdecor, &pos, pos_id);
 
 	/* Move callback with move callback not implemented */
 	ui_wdecor_set_cb(wdecor, &dummy_wdecor_cb, NULL);
-	ui_wdecor_move(wdecor, &pos);
+	ui_wdecor_move(wdecor, &pos, pos_id);
 
 	/* Move callback with real callback set */
@@ -249,9 +568,11 @@
 	resp.pos.x = 0;
 	resp.pos.y = 0;
+	resp.pos_id = 0;
 	ui_wdecor_set_cb(wdecor, &test_wdecor_cb, &resp);
-	ui_wdecor_move(wdecor, &pos);
+	ui_wdecor_move(wdecor, &pos, pos_id);
 	PCUT_ASSERT_TRUE(resp.move);
 	PCUT_ASSERT_INT_EQUALS(pos.x, resp.pos.x);
 	PCUT_ASSERT_INT_EQUALS(pos.y, resp.pos.y);
+	PCUT_ASSERT_INT_EQUALS(pos_id, resp.pos_id);
 
 	ui_wdecor_destroy(wdecor);
@@ -266,4 +587,5 @@
 	ui_wdecor_rsztype_t rsztype;
 	gfx_coord2_t pos;
+	sysarg_t pos_id;
 
 	rc = ui_wdecor_create(NULL, "Hello", ui_wds_none, &wdecor);
@@ -273,11 +595,12 @@
 	pos.x = 3;
 	pos.y = 4;
+	pos_id = 5;
 
 	/* Resize callback with no callbacks set */
-	ui_wdecor_resize(wdecor, rsztype, &pos);
+	ui_wdecor_resize(wdecor, rsztype, &pos, pos_id);
 
 	/* Resize callback with move callback not implemented */
 	ui_wdecor_set_cb(wdecor, &dummy_wdecor_cb, NULL);
-	ui_wdecor_resize(wdecor, rsztype, &pos);
+	ui_wdecor_resize(wdecor, rsztype, &pos, pos_id);
 
 	/* Resize callback with real callback set */
@@ -287,9 +610,10 @@
 	resp.pos.y = 0;
 	ui_wdecor_set_cb(wdecor, &test_wdecor_cb, &resp);
-	ui_wdecor_resize(wdecor, rsztype, &pos);
+	ui_wdecor_resize(wdecor, rsztype, &pos, pos_id);
 	PCUT_ASSERT_TRUE(resp.resize);
 	PCUT_ASSERT_INT_EQUALS(rsztype, resp.rsztype);
 	PCUT_ASSERT_INT_EQUALS(pos.x, resp.pos.x);
 	PCUT_ASSERT_INT_EQUALS(pos.y, resp.pos.y);
+	PCUT_ASSERT_INT_EQUALS(pos_id, resp.pos_id);
 
 	ui_wdecor_destroy(wdecor);
@@ -330,4 +654,7 @@
 PCUT_TEST(close_btn_clicked)
 {
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	ui_resource_t *resource = NULL;
 	ui_wdecor_t *wdecor;
 	gfx_rect_t rect;
@@ -335,5 +662,13 @@
 	errno_t rc;
 
-	rc = ui_wdecor_create(NULL, "Hello", ui_wds_none, &wdecor);
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_wdecor_create(resource, "Hello", ui_wds_decorated, &wdecor);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -353,4 +688,8 @@
 
 	ui_wdecor_destroy(wdecor);
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 }
 
@@ -407,14 +746,19 @@
 }
 
-/** ui_wdecor_get_geom() with ui_wds_none produces the correct geometry */
-PCUT_TEST(get_geom_none)
-{
+/** Pressing F10 generates sysmenu event.
+ *
+ * Note that in a window with menu bar the menu bar would claim F10
+ * so it would never be delivered to window decoration.
+ */
+PCUT_TEST(kbd_f10_sysmenu)
+{
+	errno_t rc;
+	gfx_rect_t rect;
+	kbd_event_t event;
 	gfx_context_t *gc = NULL;
 	test_gc_t tgc;
+	test_cb_resp_t resp;
 	ui_resource_t *resource = NULL;
 	ui_wdecor_t *wdecor;
-	gfx_rect_t rect;
-	ui_wdecor_geom_t geom;
-	errno_t rc;
 
 	memset(&tgc, 0, sizeof(tgc));
@@ -426,5 +770,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_wdecor_create(resource, "Hello", ui_wds_none, &wdecor);
+	rc = ui_wdecor_create(resource, "Hello", ui_wds_decorated, &wdecor);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -435,4 +779,263 @@
 
 	ui_wdecor_set_rect(wdecor, &rect);
+
+	ui_wdecor_set_cb(wdecor, &test_wdecor_cb, (void *) &resp);
+
+	resp.sysmenu_open = false;
+
+	event.type = KEY_PRESS;
+	event.mods = 0;
+	event.key = KC_F10;
+	event.kbd_id = 42;
+	ui_wdecor_kbd_event(wdecor, &event);
+
+	PCUT_ASSERT_TRUE(resp.sysmenu_open);
+	PCUT_ASSERT_INT_EQUALS(event.kbd_id, resp.idev_id);
+
+	ui_wdecor_destroy(wdecor);
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Down key with active sysmenu handle generates sysmenu open event */
+PCUT_TEST(kbd_down_sysmenu)
+{
+	errno_t rc;
+	gfx_rect_t rect;
+	kbd_event_t event;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	test_cb_resp_t resp;
+	ui_resource_t *resource = NULL;
+	ui_wdecor_t *wdecor;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_wdecor_create(resource, "Hello", ui_wds_decorated, &wdecor);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 100;
+	rect.p1.y = 200;
+
+	ui_wdecor_set_rect(wdecor, &rect);
+
+	ui_wdecor_set_cb(wdecor, &test_wdecor_cb, (void *) &resp);
+
+	ui_wdecor_sysmenu_hdl_set_active(wdecor, true);
+
+	resp.sysmenu_open = false;
+
+	event.type = KEY_PRESS;
+	event.mods = 0;
+	event.key = KC_DOWN;
+	event.kbd_id = 42;
+	ui_wdecor_kbd_event(wdecor, &event);
+
+	PCUT_ASSERT_TRUE(resp.sysmenu_open);
+	PCUT_ASSERT_INT_EQUALS(event.kbd_id, resp.idev_id);
+
+	ui_wdecor_destroy(wdecor);
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Left key with active sysmenu handle generates sysmenu left event */
+PCUT_TEST(kbd_left_sysmenu)
+{
+	errno_t rc;
+	gfx_rect_t rect;
+	kbd_event_t event;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	test_cb_resp_t resp;
+	ui_resource_t *resource = NULL;
+	ui_wdecor_t *wdecor;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_wdecor_create(resource, "Hello", ui_wds_decorated, &wdecor);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 100;
+	rect.p1.y = 200;
+
+	ui_wdecor_set_rect(wdecor, &rect);
+
+	ui_wdecor_set_cb(wdecor, &test_wdecor_cb, (void *) &resp);
+
+	ui_wdecor_sysmenu_hdl_set_active(wdecor, true);
+
+	resp.sysmenu_left = false;
+
+	event.type = KEY_PRESS;
+	event.mods = 0;
+	event.key = KC_LEFT;
+	event.kbd_id = 42;
+	ui_wdecor_kbd_event(wdecor, &event);
+
+	PCUT_ASSERT_TRUE(resp.sysmenu_left);
+	PCUT_ASSERT_INT_EQUALS(event.kbd_id, resp.idev_id);
+
+	ui_wdecor_destroy(wdecor);
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Right key with active sysmenu handle generates sysmenu right event */
+PCUT_TEST(kbd_right_sysmenu)
+{
+	errno_t rc;
+	gfx_rect_t rect;
+	kbd_event_t event;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	test_cb_resp_t resp;
+	ui_resource_t *resource = NULL;
+	ui_wdecor_t *wdecor;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_wdecor_create(resource, "Hello", ui_wds_decorated, &wdecor);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 100;
+	rect.p1.y = 200;
+
+	ui_wdecor_set_rect(wdecor, &rect);
+
+	ui_wdecor_set_cb(wdecor, &test_wdecor_cb, (void *) &resp);
+
+	ui_wdecor_sysmenu_hdl_set_active(wdecor, true);
+
+	resp.sysmenu_right = false;
+
+	event.type = KEY_PRESS;
+	event.mods = 0;
+	event.key = KC_RIGHT;
+	event.kbd_id = 42;
+	ui_wdecor_kbd_event(wdecor, &event);
+
+	PCUT_ASSERT_TRUE(resp.sysmenu_right);
+	PCUT_ASSERT_INT_EQUALS(event.kbd_id, resp.idev_id);
+
+	ui_wdecor_destroy(wdecor);
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Character key with active sysmenu handle generates sysmenu accel event */
+PCUT_TEST(kbd_accel_sysmenu)
+{
+	errno_t rc;
+	gfx_rect_t rect;
+	kbd_event_t event;
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	test_cb_resp_t resp;
+	ui_resource_t *resource = NULL;
+	ui_wdecor_t *wdecor;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_wdecor_create(resource, "Hello", ui_wds_decorated, &wdecor);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 100;
+	rect.p1.y = 200;
+
+	ui_wdecor_set_rect(wdecor, &rect);
+
+	ui_wdecor_set_cb(wdecor, &test_wdecor_cb, (void *) &resp);
+
+	ui_wdecor_sysmenu_hdl_set_active(wdecor, true);
+
+	resp.sysmenu_accel = false;
+
+	event.type = KEY_PRESS;
+	event.mods = 0;
+	event.key = KC_A;
+	event.c = 'a';
+	event.kbd_id = 42;
+	ui_wdecor_kbd_event(wdecor, &event);
+
+	PCUT_ASSERT_TRUE(resp.sysmenu_accel);
+	PCUT_ASSERT_INT_EQUALS(event.c, resp.accel);
+	PCUT_ASSERT_INT_EQUALS(event.kbd_id, resp.idev_id);
+
+	ui_wdecor_destroy(wdecor);
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** ui_wdecor_get_geom() with ui_wds_none produces the correct geometry */
+PCUT_TEST(get_geom_none)
+{
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	ui_resource_t *resource = NULL;
+	ui_wdecor_t *wdecor;
+	gfx_rect_t rect;
+	ui_wdecor_geom_t geom;
+	errno_t rc;
+
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_wdecor_create(resource, "Hello", ui_wds_none, &wdecor);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 100;
+	rect.p1.y = 200;
+
+	ui_wdecor_set_rect(wdecor, &rect);
 	ui_wdecor_get_geom(wdecor, &geom);
 
@@ -447,4 +1050,24 @@
 	PCUT_ASSERT_INT_EQUALS(0, geom.title_bar_rect.p1.y);
 
+	PCUT_ASSERT_INT_EQUALS(0, geom.sysmenu_hdl_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.sysmenu_hdl_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(0, geom.sysmenu_hdl_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.sysmenu_hdl_rect.p1.y);
+
+	PCUT_ASSERT_INT_EQUALS(0, geom.caption_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.caption_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(0, geom.caption_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.caption_rect.p1.y);
+
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_min_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_min_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_min_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_min_rect.p1.y);
+
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_max_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_max_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_max_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_max_rect.p1.y);
+
 	PCUT_ASSERT_INT_EQUALS(0, geom.btn_close_rect.p0.x);
 	PCUT_ASSERT_INT_EQUALS(0, geom.btn_close_rect.p0.y);
@@ -504,4 +1127,24 @@
 	PCUT_ASSERT_INT_EQUALS(0, geom.title_bar_rect.p1.y);
 
+	PCUT_ASSERT_INT_EQUALS(0, geom.sysmenu_hdl_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.sysmenu_hdl_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(0, geom.sysmenu_hdl_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.sysmenu_hdl_rect.p1.y);
+
+	PCUT_ASSERT_INT_EQUALS(0, geom.caption_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.caption_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(0, geom.caption_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.caption_rect.p1.y);
+
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_min_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_min_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_min_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_min_rect.p1.y);
+
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_max_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_max_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_max_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_max_rect.p1.y);
+
 	PCUT_ASSERT_INT_EQUALS(0, geom.btn_close_rect.p0.x);
 	PCUT_ASSERT_INT_EQUALS(0, geom.btn_close_rect.p0.y);
@@ -562,4 +1205,24 @@
 	PCUT_ASSERT_INT_EQUALS(46, geom.title_bar_rect.p1.y);
 
+	PCUT_ASSERT_INT_EQUALS(0, geom.sysmenu_hdl_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.sysmenu_hdl_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(0, geom.sysmenu_hdl_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.sysmenu_hdl_rect.p1.y);
+
+	PCUT_ASSERT_INT_EQUALS(18, geom.caption_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(24, geom.caption_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(91, geom.caption_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(46, geom.caption_rect.p1.y);
+
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_min_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_min_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_min_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_min_rect.p1.y);
+
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_max_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_max_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_max_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_max_rect.p1.y);
+
 	PCUT_ASSERT_INT_EQUALS(0, geom.btn_close_rect.p0.x);
 	PCUT_ASSERT_INT_EQUALS(0, geom.btn_close_rect.p0.y);
@@ -619,4 +1282,25 @@
 	PCUT_ASSERT_INT_EQUALS(46, geom.title_bar_rect.p1.y);
 
+	PCUT_ASSERT_INT_EQUALS(15, geom.sysmenu_hdl_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(25, geom.sysmenu_hdl_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(35, geom.sysmenu_hdl_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(45, geom.sysmenu_hdl_rect.p1.y);
+
+	PCUT_ASSERT_INT_EQUALS(38, geom.caption_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(24, geom.caption_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(51, geom.caption_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(46, geom.caption_rect.p1.y);
+
+	PCUT_ASSERT_INT_EQUALS(55, geom.btn_min_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(25, geom.btn_min_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(75, geom.btn_min_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(45, geom.btn_min_rect.p1.y);
+
+	/* Maximize button is not in ui_wds_decorated */
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_max_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_max_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_max_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(0, geom.btn_max_rect.p1.y);
+
 	PCUT_ASSERT_INT_EQUALS(75, geom.btn_close_rect.p0.x);
 	PCUT_ASSERT_INT_EQUALS(25, geom.btn_close_rect.p0.y);
@@ -639,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;
@@ -647,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);
@@ -654,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);
@@ -661,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);
@@ -668,4 +1357,5 @@
 	PCUT_ASSERT_INT_EQUALS(200, rect.p1.y);
 
+	ui_destroy(ui);
 }
 
@@ -673,4 +1363,7 @@
 PCUT_TEST(get_rsztype)
 {
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	ui_resource_t *resource = NULL;
 	ui_wdecor_t *wdecor;
 	gfx_rect_t rect;
@@ -679,5 +1372,13 @@
 	errno_t rc;
 
-	rc = ui_wdecor_create(NULL, "Hello", ui_wds_resizable, &wdecor);
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_wdecor_create(resource, "Hello", ui_wds_resizable, &wdecor);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -759,5 +1460,5 @@
 	/* Non-resizable window */
 
-	rc = ui_wdecor_create(NULL, "Hello", ui_wds_none, &wdecor);
+	rc = ui_wdecor_create(resource, "Hello", ui_wds_none, &wdecor);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -775,4 +1476,8 @@
 
 	ui_wdecor_destroy(wdecor);
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 }
 
@@ -803,4 +1508,7 @@
 PCUT_TEST(frame_pos_event)
 {
+	gfx_context_t *gc = NULL;
+	test_gc_t tgc;
+	ui_resource_t *resource = NULL;
 	ui_wdecor_t *wdecor;
 	gfx_rect_t rect;
@@ -809,5 +1517,13 @@
 	errno_t rc;
 
-	rc = ui_wdecor_create(NULL, "Hello", ui_wds_resizable, &wdecor);
+	memset(&tgc, 0, sizeof(tgc));
+	rc = gfx_context_new(&ops, &tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_wdecor_create(resource, "Hello", ui_wds_resizable, &wdecor);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -845,4 +1561,8 @@
 
 	ui_wdecor_destroy(wdecor);
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 }
 
@@ -936,4 +1656,62 @@
 }
 
+static void test_wdecor_sysmenu_open(ui_wdecor_t *wdecor, void *arg,
+    sysarg_t idev_id)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->sysmenu_open = true;
+	resp->idev_id = idev_id;
+}
+
+static void test_wdecor_sysmenu_left(ui_wdecor_t *wdecor, void *arg,
+    sysarg_t idev_id)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->sysmenu_left = true;
+	resp->idev_id = idev_id;
+}
+
+static void test_wdecor_sysmenu_right(ui_wdecor_t *wdecor, void *arg,
+    sysarg_t idev_id)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->sysmenu_right = true;
+	resp->idev_id = idev_id;
+}
+
+static void test_wdecor_sysmenu_accel(ui_wdecor_t *wdecor, void *arg,
+    char32_t accel, sysarg_t idev_id)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->sysmenu_accel = true;
+	resp->accel = accel;
+	resp->idev_id = idev_id;
+}
+
+static void test_wdecor_minimize(ui_wdecor_t *wdecor, void *arg)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->minimize = true;
+}
+
+static void test_wdecor_maximize(ui_wdecor_t *wdecor, void *arg)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->maximize = true;
+}
+
+static void test_wdecor_unmaximize(ui_wdecor_t *wdecor, void *arg)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->unmaximize = true;
+}
+
 static void test_wdecor_close(ui_wdecor_t *wdecor, void *arg)
 {
@@ -943,5 +1721,6 @@
 }
 
-static void test_wdecor_move(ui_wdecor_t *wdecor, void *arg, gfx_coord2_t *pos)
+static void test_wdecor_move(ui_wdecor_t *wdecor, void *arg, gfx_coord2_t *pos,
+    sysarg_t pos_id)
 {
 	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
@@ -949,8 +1728,9 @@
 	resp->move = true;
 	resp->pos = *pos;
+	resp->pos_id = pos_id;
 }
 
 static void test_wdecor_resize(ui_wdecor_t *wdecor, void *arg,
-    ui_wdecor_rsztype_t rsztype, gfx_coord2_t *pos)
+    ui_wdecor_rsztype_t rsztype, gfx_coord2_t *pos, sysarg_t pos_id)
 {
 	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
@@ -959,4 +1739,5 @@
 	resp->rsztype = rsztype;
 	resp->pos = *pos;
+	resp->pos_id = pos_id;
 }
 
Index: uspace/lib/ui/test/window.c
===================================================================
--- uspace/lib/ui/test/window.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/ui/test/window.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -45,12 +45,20 @@
 PCUT_TEST_SUITE(window);
 
+static void test_window_sysmenu(ui_window_t *, void *, sysarg_t);
+static void test_window_minimize(ui_window_t *, void *);
+static void test_window_maximize(ui_window_t *, void *);
+static void test_window_unmaximize(ui_window_t *, void *);
 static void test_window_close(ui_window_t *, void *);
-static void test_window_focus(ui_window_t *, void *);
+static void test_window_focus(ui_window_t *, void *, unsigned);
 static void test_window_kbd(ui_window_t *, void *, kbd_event_t *);
 static errno_t test_window_paint(ui_window_t *, void *);
 static void test_window_pos(ui_window_t *, void *, pos_event_t *);
-static void test_window_unfocus(ui_window_t *, void *);
+static void test_window_unfocus(ui_window_t *, void *, unsigned);
 
 static ui_window_cb_t test_window_cb = {
+	.sysmenu = test_window_sysmenu,
+	.minimize = test_window_minimize,
+	.maximize = test_window_maximize,
+	.unmaximize = test_window_unmaximize,
 	.close = test_window_close,
 	.focus = test_window_focus,
@@ -66,5 +74,5 @@
 static errno_t test_ctl_paint(void *);
 static ui_evclaim_t test_ctl_pos_event(void *, pos_event_t *);
-static void test_ctl_unfocus(void *);
+static void test_ctl_unfocus(void *, unsigned);
 
 static ui_control_ops_t test_ctl_ops = {
@@ -76,6 +84,12 @@
 typedef struct {
 	errno_t rc;
+	bool sysmenu;
+	sysarg_t sysmenu_idev_id;
+	bool minimize;
+	bool maximize;
+	bool unmaximize;
 	bool close;
 	bool focus;
+	unsigned focus_nfocus;
 	bool kbd;
 	kbd_event_t kbd_event;
@@ -84,4 +98,5 @@
 	pos_event_t pos_event;
 	bool unfocus;
+	unsigned unfocus_nfocus;
 } test_cb_resp_t;
 
@@ -93,4 +108,5 @@
 	pos_event_t pos_event;
 	bool unfocus;
+	unsigned unfocus_nfocus;
 } test_ctl_resp_t;
 
@@ -521,9 +537,156 @@
 	resp.unfocus = false;
 
-	ui_window_def_unfocus(window);
+	ui_window_def_unfocus(window, 42);
 	PCUT_ASSERT_TRUE(resp.unfocus);
+	PCUT_ASSERT_INT_EQUALS(42, resp.unfocus_nfocus);
 
 	/* Need to remove first because we didn't implement the destructor */
 	ui_window_remove(window, control);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_window_send_sysmenu() calls sysmenu callback set via ui_window_set_cb() */
+PCUT_TEST(send_sysmenu)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	test_cb_resp_t resp;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	/* Sysmenu callback with no callbacks set */
+	ui_window_send_sysmenu(window, 42);
+
+	/* Sysmenu callback with sysmenu callback not implemented */
+	ui_window_set_cb(window, &dummy_window_cb, NULL);
+	ui_window_send_sysmenu(window, 42);
+
+	/* Sysmenu callback with real callback set */
+	resp.sysmenu = false;
+	resp.sysmenu_idev_id = 0;
+	ui_window_set_cb(window, &test_window_cb, &resp);
+	ui_window_send_sysmenu(window, 42);
+	PCUT_ASSERT_TRUE(resp.sysmenu);
+	PCUT_ASSERT_INT_EQUALS(42, resp.sysmenu_idev_id);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_window_send_minimize() calls minimize callback set via ui_window_set_cb() */
+PCUT_TEST(send_minimize)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	test_cb_resp_t resp;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	/* Minimize callback with no callbacks set */
+	ui_window_send_minimize(window);
+
+	/* Minimize callback with minimize callback not implemented */
+	ui_window_set_cb(window, &dummy_window_cb, NULL);
+	ui_window_send_minimize(window);
+
+	/* Minimize callback with real callback set */
+	resp.minimize = false;
+	ui_window_set_cb(window, &test_window_cb, &resp);
+	ui_window_send_minimize(window);
+	PCUT_ASSERT_TRUE(resp.minimize);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_window_send_maximize() calls maximize callback set via ui_window_set_cb() */
+PCUT_TEST(send_maximize)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	test_cb_resp_t resp;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	/* Maximize callback with no callbacks set */
+	ui_window_send_maximize(window);
+
+	/* Maximize callback with maximize callback not implemented */
+	ui_window_set_cb(window, &dummy_window_cb, NULL);
+	ui_window_send_maximize(window);
+
+	/* Maximize callback with real callback set */
+	resp.maximize = false;
+	ui_window_set_cb(window, &test_window_cb, &resp);
+	ui_window_send_maximize(window);
+	PCUT_ASSERT_TRUE(resp.maximize);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_window_send_unmaximize() calls unmaximize callback set via ui_window_set_cb() */
+PCUT_TEST(send_unmaximize)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	test_cb_resp_t resp;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	/* Unmaximize callback with no callbacks set */
+	ui_window_send_unmaximize(window);
+
+	/* Unmaximize callback with unmaximize callback not implemented */
+	ui_window_set_cb(window, &dummy_window_cb, NULL);
+	ui_window_send_unmaximize(window);
+
+	/* Unmaximize callback with real callback set */
+	resp.unmaximize = false;
+	ui_window_set_cb(window, &test_window_cb, &resp);
+	ui_window_send_unmaximize(window);
+	PCUT_ASSERT_TRUE(resp.unmaximize);
 
 	ui_window_destroy(window);
@@ -587,15 +750,16 @@
 
 	/* Focus callback with no callbacks set */
-	ui_window_send_focus(window);
+	ui_window_send_focus(window, 42);
 
 	/* Focus callback with focus callback not implemented */
 	ui_window_set_cb(window, &dummy_window_cb, NULL);
-	ui_window_send_focus(window);
+	ui_window_send_focus(window, 42);
 
 	/* Focus callback with real callback set */
 	resp.focus = false;
 	ui_window_set_cb(window, &test_window_cb, &resp);
-	ui_window_send_focus(window);
+	ui_window_send_focus(window, 42);
 	PCUT_ASSERT_TRUE(resp.focus);
+	PCUT_ASSERT_INT_EQUALS(42, resp.focus_nfocus);
 
 	ui_window_destroy(window);
@@ -623,5 +787,5 @@
 	PCUT_ASSERT_NOT_NULL(window);
 
-	kbd_event.type = POS_PRESS;
+	kbd_event.type = KEY_PRESS;
 	kbd_event.key = KC_X;
 	kbd_event.mods = 0;
@@ -755,18 +919,48 @@
 
 	/* Unfocus callback with no callbacks set */
-	ui_window_send_unfocus(window);
+	ui_window_send_unfocus(window, 42);
 
 	/* Unfocus callback with unfocus callback not implemented */
 	ui_window_set_cb(window, &dummy_window_cb, NULL);
-	ui_window_send_unfocus(window);
+	ui_window_send_unfocus(window, 42);
 
 	/* Unfocus callback with real callback set */
 	resp.close = false;
 	ui_window_set_cb(window, &test_window_cb, &resp);
-	ui_window_send_unfocus(window);
+	ui_window_send_unfocus(window, 42);
 	PCUT_ASSERT_TRUE(resp.unfocus);
-
-	ui_window_destroy(window);
-	ui_destroy(ui);
+	PCUT_ASSERT_INT_EQUALS(42, resp.unfocus_nfocus);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+static void test_window_sysmenu(ui_window_t *window, void *arg, sysarg_t idev_id)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->sysmenu = true;
+	resp->sysmenu_idev_id = idev_id;
+}
+
+static void test_window_minimize(ui_window_t *window, void *arg)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->minimize = true;
+}
+
+static void test_window_maximize(ui_window_t *window, void *arg)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->maximize = true;
+}
+
+static void test_window_unmaximize(ui_window_t *window, void *arg)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->unmaximize = true;
 }
 
@@ -778,9 +972,10 @@
 }
 
-static void test_window_focus(ui_window_t *window, void *arg)
+static void test_window_focus(ui_window_t *window, void *arg, unsigned nfocus)
 {
 	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
 
 	resp->focus = true;
+	resp->focus_nfocus = nfocus;
 }
 
@@ -811,9 +1006,10 @@
 }
 
-static void test_window_unfocus(ui_window_t *window, void *arg)
+static void test_window_unfocus(ui_window_t *window, void *arg, unsigned nfocus)
 {
 	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
 
 	resp->unfocus = true;
+	resp->unfocus_nfocus = nfocus;
 }
 
@@ -836,9 +1032,10 @@
 }
 
-static void test_ctl_unfocus(void *arg)
+static void test_ctl_unfocus(void *arg, unsigned nfocus)
 {
 	test_ctl_resp_t *resp = (test_ctl_resp_t *) arg;
 
 	resp->unfocus = true;
+	resp->unfocus_nfocus = nfocus;
 }
 
Index: uspace/lib/usbdev/include/usb/dev/device.h
===================================================================
--- uspace/lib/usbdev/include/usb/dev/device.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/usbdev/include/usb/dev/device.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -91,5 +91,5 @@
 
 usb_address_t usb_device_get_address(const usb_device_t *);
-usb_speed_t usb_device_get_depth(const usb_device_t *);
+unsigned usb_device_get_depth(const usb_device_t *);
 usb_speed_t usb_device_get_speed(const usb_device_t *);
 int usb_device_get_iface_number(const usb_device_t *);
Index: uspace/lib/usbhid/src/hidparser.c
===================================================================
--- uspace/lib/usbhid/src/hidparser.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/usbhid/src/hidparser.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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/lib/usbhost/src/hcd.c
===================================================================
--- uspace/lib/usbhost/src/hcd.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/usbhost/src/hcd.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -95,9 +95,11 @@
  * TODO: Make the bus mechanism less flexible in irq handling and remove the
  * lookup.
- */
-static void irq_handler(ipc_call_t *call, ddf_dev_t *dev)
-{
-	assert(dev);
-	hc_device_t *hcd = dev_to_hcd(dev);
+ *
+ * @param call Interrupt notification
+ * @param arg Argument (hc_device_t *)
+ */
+static void irq_handler(ipc_call_t *call, void *arg)
+{
+	hc_device_t *hcd = (hc_device_t *)arg;
 
 	const uint32_t status = ipc_get_arg1(call);
@@ -178,5 +180,5 @@
 	cap_irq_handle_t ihandle;
 	ret = register_interrupt_handler(hcd->ddf_dev, irq, irq_handler,
-	    &irq_code, &ihandle);
+	    (void *)hcd, &irq_code, &ihandle);
 	irq_code_clean(&irq_code);
 	if (ret != EOK) {
Index: uspace/lib/usbvirt/src/ipc_dev.c
===================================================================
--- uspace/lib/usbvirt/src/ipc_dev.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/lib/usbvirt/src/ipc_dev.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2011 Vojtech Horky
  * All rights reserved.
@@ -59,4 +60,5 @@
 	size_t accepted_size;
 	if (!async_data_read_receive(&call, &accepted_size)) {
+		async_answer_0(&call, EINVAL);
 		async_answer_0(icall, EINVAL);
 		return;
@@ -94,4 +96,5 @@
 	ipc_call_t data;
 	if (!async_data_read_receive(&data, &data_len)) {
+		async_answer_0(&data, EPARTY);
 		async_answer_0(icall, EPARTY);
 		free(setup_packet);
@@ -101,4 +104,5 @@
 	void *buffer = malloc(data_len);
 	if (buffer == NULL) {
+		async_answer_0(&data, EPARTY);
 		async_answer_0(icall, ENOMEM);
 		free(setup_packet);
@@ -184,4 +188,5 @@
 	ipc_call_t data;
 	if (!async_data_read_receive(&data, &data_len)) {
+		async_answer_0(&data, EPARTY);
 		async_answer_0(icall, EPARTY);
 		return;
@@ -190,4 +195,5 @@
 	void *buffer = malloc(data_len);
 	if (buffer == NULL) {
+		async_answer_0(&data, EPARTY);
 		async_answer_0(icall, ENOMEM);
 		return;
Index: uspace/lib/wndmgt/doc/doxygroups.h
===================================================================
--- uspace/lib/wndmgt/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/wndmgt/doc/doxygroups.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,3 @@
+/** @addtogroup libwndmgt libwndmgt
+ * @ingroup libs
+ */
Index: uspace/lib/wndmgt/include/ipc/wndmgt.h
===================================================================
--- uspace/lib/wndmgt/include/ipc/wndmgt.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/wndmgt/include/ipc/wndmgt.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libwndmgt
+ * @{
+ */
+/** @file
+ */
+
+#ifndef LIBWNDMGT_IPC_WNDMGT_H
+#define LIBWNDMGT_IPC_WNDMGT_H
+
+#include <ipc/common.h>
+
+typedef enum {
+	WNDMGT_CALLBACK_CREATE = IPC_FIRST_USER_METHOD,
+	WNDMGT_GET_WINDOW_LIST,
+	WNDMGT_GET_WINDOW_INFO,
+	WNDMGT_ACTIVATE_WINDOW,
+	WNDMGT_CLOSE_WINDOW,
+	WNDMGT_GET_EVENT,
+} wndmgt_request_t;
+
+typedef enum {
+	WNDMGT_EV_PENDING = IPC_FIRST_USER_METHOD
+} wndmgt_event_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/wndmgt/include/types/wndmgt.h
===================================================================
--- uspace/lib/wndmgt/include/types/wndmgt.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/wndmgt/include/types/wndmgt.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,98 @@
+/*
+ * 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 libwndmgt
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBWNDMGT_TYPES_WNDMGT_H_
+#define _LIBWNDMGT_TYPES_WNDMGT_H_
+
+#include <ipc/services.h>
+#include <types/common.h>
+
+/** Use the default window management service (argument to wndmgt_open() */
+#define WNDMGT_DEFAULT SERVICE_NAME_WNDMGT
+
+struct wndmgt;
+
+/** Window management session */
+typedef struct wndmgt wndmgt_t;
+
+/** Window management callbacks */
+typedef struct {
+	/** Window added */
+	void (*window_added)(void *, sysarg_t);
+	/** Window removed */
+	void (*window_removed)(void *, sysarg_t);
+	/** Window changed */
+	void (*window_changed)(void *, sysarg_t);
+} wndmgt_cb_t;
+
+/** Window management event type */
+typedef enum {
+	/** Window added */
+	wmev_window_added,
+	/** Window removed */
+	wmev_window_removed,
+	/** Window changed */
+	wmev_window_changed
+} wndmgt_ev_type_t;
+
+/** Window management event */
+typedef struct {
+	/** Event type */
+	wndmgt_ev_type_t etype;
+	/** Window ID */
+	sysarg_t wnd_id;
+} wndmgt_ev_t;
+
+/** Window list */
+typedef struct {
+	/** Number of windows */
+	size_t nwindows;
+	/** ID for each window */
+	sysarg_t *windows;
+} wndmgt_window_list_t;
+
+/** Window information */
+typedef struct {
+	/** Window caption */
+	char *caption;
+	/** Window flags */
+	unsigned flags;
+	/** Number of foci on this window */
+	unsigned nfocus;
+} wndmgt_window_info_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/wndmgt/include/wndmgt.h
===================================================================
--- uspace/lib/wndmgt/include/wndmgt.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/wndmgt/include/wndmgt.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libwndmgt
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBWNDMGT_WNDMGT_H_
+#define _LIBWNDMGT_WNDMGT_H_
+
+#include <errno.h>
+#include <types/common.h>
+#include "types/wndmgt.h"
+
+extern errno_t wndmgt_open(const char *, wndmgt_cb_t *, void *, wndmgt_t **);
+extern void wndmgt_close(wndmgt_t *);
+extern errno_t wndmgt_get_window_list(wndmgt_t *, wndmgt_window_list_t **);
+extern void wndmgt_free_window_list(wndmgt_window_list_t *);
+extern errno_t wndmgt_get_window_info(wndmgt_t *, sysarg_t,
+    wndmgt_window_info_t **);
+extern void wndmgt_free_window_info(wndmgt_window_info_t *);
+extern errno_t wndmgt_activate_window(wndmgt_t *, sysarg_t, sysarg_t);
+extern errno_t wndmgt_close_window(wndmgt_t *, sysarg_t);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/wndmgt/include/wndmgt_srv.h
===================================================================
--- uspace/lib/wndmgt/include/wndmgt_srv.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/wndmgt/include/wndmgt_srv.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libwndmgt
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBWNDMGT_WNDMGT_SRV_H_
+#define _LIBWNDMGT_WNDMGT_SRV_H_
+
+#include <async.h>
+#include <errno.h>
+#include "types/wndmgt.h"
+
+typedef struct wndmgt_ops wndmgt_ops_t;
+
+/** Window management server structure (per client session) */
+typedef struct {
+	async_sess_t *client_sess;
+	wndmgt_ops_t *ops;
+	void *arg;
+} wndmgt_srv_t;
+
+struct wndmgt_ops {
+	errno_t (*get_window_list)(void *, wndmgt_window_list_t **);
+	errno_t (*get_window_info)(void *, sysarg_t, wndmgt_window_info_t **);
+	errno_t (*activate_window)(void *, sysarg_t, sysarg_t);
+	errno_t (*close_window)(void *, sysarg_t);
+	errno_t (*get_event)(void *, wndmgt_ev_t *);
+};
+
+extern void wndmgt_conn(ipc_call_t *, wndmgt_srv_t *);
+extern void wndmgt_srv_initialize(wndmgt_srv_t *);
+extern void wndmgt_srv_ev_pending(wndmgt_srv_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/wndmgt/meson.build
===================================================================
--- uspace/lib/wndmgt/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/wndmgt/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2022 Jiri Svoboda
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# - Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+# - The name of the author may not be used to endorse or promote products
+#   derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+src = files(
+	'src/wndmgt.c',
+	'src/wndmgt_srv.c',
+)
+
+test_src = files(
+	'test/main.c',
+	'test/wndmgt.c',
+)
Index: uspace/lib/wndmgt/private/wndmgt.h
===================================================================
--- uspace/lib/wndmgt/private/wndmgt.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/wndmgt/private/wndmgt.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libwndmgt
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBWNDMGT_PRIVATE_WNDMGT_H_
+#define _LIBWNDMGT_PRIVATE_WNDMGT_H_
+
+#include <async.h>
+#include <fibril_synch.h>
+#include <stdbool.h>
+
+/** Window management session structure */
+struct wndmgt {
+	/** Session with window management service */
+	async_sess_t *sess;
+	/** Callbacks */
+	wndmgt_cb_t *cb;
+	/** Calback argument */
+	void *cb_arg;
+	/** Synchronize access to window management object */
+	fibril_mutex_t lock;
+	/** @c true if callback handler terminated */
+	bool cb_done;
+	/** Signalled when cb_done or ev_pending is changed */
+	fibril_condvar_t cv;
+};
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/wndmgt/src/wndmgt.c
===================================================================
--- uspace/lib/wndmgt/src/wndmgt.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/wndmgt/src/wndmgt.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,445 @@
+/*
+ * 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 libwndmgt
+ * @{
+ */
+/**
+ * @file
+ * @brief Window management protocol client
+ */
+
+#include <async.h>
+#include <wndmgt.h>
+#include <errno.h>
+#include <fibril_synch.h>
+#include <ipc/wndmgt.h>
+#include <ipc/services.h>
+#include <loc.h>
+#include <mem.h>
+#include <stdlib.h>
+#include "../private/wndmgt.h"
+
+static errno_t wndmgt_callback_create(wndmgt_t *);
+static void wndmgt_cb_conn(ipc_call_t *, void *);
+
+/** Open window management service.
+ *
+ * @param wmname Window management service name or @c NULL to use default
+ * @param cb Window management callbacks
+ * @param cb_arg Callback argument
+ * @param rwndmgt Place to store pointer to window management session
+ * @return EOK on success or an error code
+ */
+errno_t wndmgt_open(const char *wmname, wndmgt_cb_t *cb, void *cb_arg,
+    wndmgt_t **rwndmgt)
+{
+	service_id_t wndmgt_svc;
+	wndmgt_t *wndmgt;
+	errno_t rc;
+
+	wndmgt = calloc(1, sizeof(wndmgt_t));
+	if (wndmgt == NULL)
+		return ENOMEM;
+
+	wndmgt->cb = cb;
+	wndmgt->cb_arg = cb_arg;
+
+	fibril_mutex_initialize(&wndmgt->lock);
+	fibril_condvar_initialize(&wndmgt->cv);
+
+	if (wmname == NULL)
+		wmname = SERVICE_NAME_WNDMGT;
+
+	rc = loc_service_get_id(wmname, &wndmgt_svc, 0);
+	if (rc != EOK) {
+		free(wndmgt);
+		return ENOENT;
+	}
+
+	wndmgt->sess = loc_service_connect(wndmgt_svc, INTERFACE_WNDMGT,
+	    0);
+	if (wndmgt->sess == NULL) {
+		free(wndmgt);
+		return ENOENT;
+	}
+
+	rc = wndmgt_callback_create(wndmgt);
+	if (rc != EOK) {
+		async_hangup(wndmgt->sess);
+		free(wndmgt);
+		return EIO;
+	}
+
+	*rwndmgt = wndmgt;
+	return EOK;
+}
+
+/** Create callback connection from window management service.
+ *
+ * @param wndmgt Window management
+ * @return EOK on success or an error code
+ */
+static errno_t wndmgt_callback_create(wndmgt_t *wndmgt)
+{
+	async_exch_t *exch = async_exchange_begin(wndmgt->sess);
+
+	aid_t req = async_send_0(exch, WNDMGT_CALLBACK_CREATE, NULL);
+
+	port_id_t port;
+	errno_t rc = async_create_callback_port(exch, INTERFACE_WNDMGT_CB, 0, 0,
+	    wndmgt_cb_conn, wndmgt, &port);
+
+	async_exchange_end(exch);
+
+	if (rc != EOK)
+		return rc;
+
+	errno_t retval;
+	async_wait_for(req, &retval);
+
+	return retval;
+}
+
+/** Close window management service.
+ *
+ * @param wndmgt Window management
+ */
+void wndmgt_close(wndmgt_t *wndmgt)
+{
+	fibril_mutex_lock(&wndmgt->lock);
+	async_hangup(wndmgt->sess);
+	wndmgt->sess = NULL;
+
+	/* Wait for callback handler to terminate */
+
+	while (!wndmgt->cb_done)
+		fibril_condvar_wait(&wndmgt->cv, &wndmgt->lock);
+	fibril_mutex_unlock(&wndmgt->lock);
+
+	free(wndmgt);
+}
+
+/** Get window list.
+ *
+ * @param wndmgt Window management
+ * @param rlist Place to store pointer to new window list structure
+ * @return EOK on success or an error code
+ */
+errno_t wndmgt_get_window_list(wndmgt_t *wndmgt, wndmgt_window_list_t **rlist)
+{
+	async_exch_t *exch;
+	aid_t req;
+	ipc_call_t answer;
+	wndmgt_window_list_t *list;
+	sysarg_t nwindows;
+	sysarg_t *windows;
+	errno_t rc;
+
+	exch = async_exchange_begin(wndmgt->sess);
+	req = async_send_0(exch, WNDMGT_GET_WINDOW_LIST, &answer);
+
+	/* Receive window list length */
+	rc = async_data_read_start(exch, &nwindows, sizeof (nwindows));
+	if (rc != EOK) {
+		async_exchange_end(exch);
+		async_wait_for(req, &rc);
+		return rc;
+	}
+
+	windows = calloc(nwindows, sizeof(sysarg_t));
+	if (windows == NULL) {
+		async_exchange_end(exch);
+		async_forget(req);
+		return ENOMEM;
+	}
+
+	/* Receive window list */
+	rc = async_data_read_start(exch, windows, nwindows * sizeof (sysarg_t));
+	async_exchange_end(exch);
+
+	if (rc != EOK) {
+		async_forget(req);
+		return rc;
+	}
+
+	async_wait_for(req, &rc);
+	if (rc != EOK)
+		return rc;
+
+	list = calloc(1, sizeof(wndmgt_window_list_t));
+	if (list == NULL)
+		return ENOMEM;
+
+	list->nwindows = nwindows;
+	list->windows = windows;
+	*rlist = list;
+	return EOK;
+}
+
+/** Free window list.
+ *
+ * @param list Window management list
+ */
+void wndmgt_free_window_list(wndmgt_window_list_t *list)
+{
+	free(list->windows);
+	free(list);
+}
+
+/** Get window information.
+ *
+ * @param wndmgt Window management
+ * @param wnd_id Window ID
+ * @param rinfo Place to store pointer to new window information structure
+ * @return EOK on success or an error code
+ */
+errno_t wndmgt_get_window_info(wndmgt_t *wndmgt, sysarg_t wnd_id,
+    wndmgt_window_info_t **rinfo)
+{
+	async_exch_t *exch;
+	aid_t req;
+	ipc_call_t answer;
+	wndmgt_window_info_t *info;
+	sysarg_t capsize;
+	char *caption;
+	errno_t rc;
+
+	exch = async_exchange_begin(wndmgt->sess);
+	req = async_send_1(exch, WNDMGT_GET_WINDOW_INFO, wnd_id, &answer);
+
+	/* Receive caption size */
+	rc = async_data_read_start(exch, &capsize, sizeof (capsize));
+	if (rc != EOK) {
+		async_exchange_end(exch);
+		async_wait_for(req, &rc);
+		return rc;
+	}
+
+	caption = calloc(capsize + 1, sizeof(char));
+	if (caption == NULL) {
+		async_exchange_end(exch);
+		async_forget(req);
+		return ENOMEM;
+	}
+
+	/* Receive caption */
+	rc = async_data_read_start(exch, caption, capsize);
+	async_exchange_end(exch);
+
+	if (rc != EOK) {
+		async_forget(req);
+		return rc;
+	}
+
+	async_wait_for(req, &rc);
+	if (rc != EOK)
+		return rc;
+
+	/* Null-terminate the string */
+	caption[capsize] = '\0';
+
+	info = calloc(1, sizeof(wndmgt_window_info_t));
+	if (info == NULL)
+		return ENOMEM;
+
+	info->caption = caption;
+	info->flags = ipc_get_arg1(&answer);
+	info->nfocus = ipc_get_arg2(&answer);
+	*rinfo = info;
+	return EOK;
+}
+
+/** Free window information.
+ *
+ * @param info Window management information
+ */
+void wndmgt_free_window_info(wndmgt_window_info_t *info)
+{
+	free(info->caption);
+	free(info);
+}
+
+/** Activate window.
+ *
+ * @param wndmgt Window management session
+ * @param dev_id ID of input device belonging to seat whose
+ *               focus is to be switched
+ * @param wnd_id Window ID
+ * @return EOK on success or an error code
+ */
+errno_t wndmgt_activate_window(wndmgt_t *wndmgt, sysarg_t dev_id,
+    sysarg_t wnd_id)
+{
+	async_exch_t *exch;
+	errno_t rc;
+
+	exch = async_exchange_begin(wndmgt->sess);
+	rc = async_req_2_0(exch, WNDMGT_ACTIVATE_WINDOW, dev_id,
+	    wnd_id);
+
+	async_exchange_end(exch);
+	return rc;
+}
+
+/** Close window.
+ *
+ * @param wndmgt Window management
+ * @param wnd_id Window ID
+ * @return EOK on success or an error code
+ */
+errno_t wndmgt_close_window(wndmgt_t *wndmgt, sysarg_t wnd_id)
+{
+	async_exch_t *exch;
+	errno_t rc;
+
+	exch = async_exchange_begin(wndmgt->sess);
+	rc = async_req_1_0(exch, WNDMGT_CLOSE_WINDOW, wnd_id);
+
+	async_exchange_end(exch);
+	return rc;
+}
+
+/** Get window management event.
+ *
+ * @param wndmgt Window management
+ * @param event Place to store event
+ * @return EOK on success or an error code
+ */
+static errno_t wndmgt_get_event(wndmgt_t *wndmgt, wndmgt_ev_t *event)
+{
+	async_exch_t *exch;
+	ipc_call_t answer;
+	aid_t req;
+	errno_t rc;
+
+	exch = async_exchange_begin(wndmgt->sess);
+	req = async_send_0(exch, WNDMGT_GET_EVENT, &answer);
+	rc = async_data_read_start(exch, event, sizeof(*event));
+	async_exchange_end(exch);
+	if (rc != EOK) {
+		async_forget(req);
+		return rc;
+	}
+
+	async_wait_for(req, &rc);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Window management events are pending.
+ *
+ * @param wndmgt Window management
+ * @param icall Call data
+ */
+static void wndmgt_ev_pending(wndmgt_t *wndmgt, ipc_call_t *icall)
+{
+	errno_t rc;
+	wndmgt_ev_t event;
+
+	while (true) {
+		fibril_mutex_lock(&wndmgt->lock);
+
+		if (wndmgt->sess != NULL)
+			rc = wndmgt_get_event(wndmgt, &event);
+		else
+			rc = ENOENT;
+
+		fibril_mutex_unlock(&wndmgt->lock);
+
+		if (rc != EOK)
+			break;
+
+		switch (event.etype) {
+		case wmev_window_added:
+			if (wndmgt->cb != NULL &&
+			    wndmgt->cb->window_added != NULL) {
+				wndmgt->cb->window_added(wndmgt->cb_arg,
+				    event.wnd_id);
+			}
+			break;
+		case wmev_window_removed:
+			if (wndmgt->cb != NULL &&
+			    wndmgt->cb->window_removed != NULL) {
+				wndmgt->cb->window_removed(wndmgt->cb_arg,
+				    event.wnd_id);
+			}
+			break;
+		case wmev_window_changed:
+			if (wndmgt->cb != NULL &&
+			    wndmgt->cb->window_changed != NULL) {
+				wndmgt->cb->window_changed(wndmgt->cb_arg,
+				    event.wnd_id);
+			}
+			break;
+		}
+	}
+
+	async_answer_0(icall, EOK);
+}
+
+/** Callback connection handler.
+ *
+ * @param icall Connect call data
+ * @param arg   Argument, wndmgt_t *
+ */
+static void wndmgt_cb_conn(ipc_call_t *icall, void *arg)
+{
+	wndmgt_t *wndmgt = (wndmgt_t *) arg;
+
+	while (true) {
+		ipc_call_t call;
+		async_get_call(&call);
+
+		if (!ipc_get_imethod(&call)) {
+			/* Hangup */
+			async_answer_0(&call, EOK);
+			goto out;
+		}
+
+		switch (ipc_get_imethod(&call)) {
+		case WNDMGT_EV_PENDING:
+			wndmgt_ev_pending(wndmgt, &call);
+			break;
+		default:
+			async_answer_0(&call, ENOTSUP);
+			break;
+		}
+	}
+
+out:
+	fibril_mutex_lock(&wndmgt->lock);
+	wndmgt->cb_done = true;
+	fibril_mutex_unlock(&wndmgt->lock);
+	fibril_condvar_broadcast(&wndmgt->cv);
+}
+
+/** @}
+ */
Index: uspace/lib/wndmgt/src/wndmgt_srv.c
===================================================================
--- uspace/lib/wndmgt/src/wndmgt_srv.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/wndmgt/src/wndmgt_srv.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,352 @@
+/*
+ * 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 libwndmgt
+ * @{
+ */
+/**
+ * @file
+ * @brief Window management protocol server stub
+ */
+
+#include <wndmgt_srv.h>
+#include <errno.h>
+#include <io/log.h>
+#include <ipc/wndmgt.h>
+#include <mem.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <str.h>
+#include <wndmgt.h>
+#include "../private/wndmgt.h"
+
+static void wndmgt_callback_create_srv(wndmgt_srv_t *srv, ipc_call_t *call)
+{
+	async_sess_t *sess = async_callback_receive(EXCHANGE_SERIALIZE);
+	if (sess == NULL) {
+		async_answer_0(call, ENOMEM);
+		return;
+	}
+
+	srv->client_sess = sess;
+	async_answer_0(call, EOK);
+}
+
+static void wndmgt_get_window_list_srv(wndmgt_srv_t *srv, ipc_call_t *icall)
+{
+	ipc_call_t call;
+	wndmgt_window_list_t *list = NULL;
+	size_t size;
+	errno_t rc;
+
+	if (srv->ops->get_window_list == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->get_window_list(srv->arg, &list);
+	if (rc != EOK) {
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	/* Send list size */
+
+	if (!async_data_read_receive(&call, &size)) {
+		wndmgt_free_window_list(list);
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != sizeof(list->nwindows)) {
+		wndmgt_free_window_list(list);
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	rc = async_data_read_finalize(&call, &list->nwindows, size);
+	if (rc != EOK) {
+		wndmgt_free_window_list(list);
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	/* Send window list */
+
+	if (!async_data_read_receive(&call, &size)) {
+		wndmgt_free_window_list(list);
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != list->nwindows * sizeof(sysarg_t)) {
+		wndmgt_free_window_list(list);
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	rc = async_data_read_finalize(&call, list->windows, size);
+	if (rc != EOK) {
+		wndmgt_free_window_list(list);
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	async_answer_0(icall, EOK);
+	wndmgt_free_window_list(list);
+}
+
+static void wndmgt_get_window_info_srv(wndmgt_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t wnd_id;
+	ipc_call_t call;
+	wndmgt_window_info_t *info = NULL;
+	size_t capsize;
+	size_t size;
+	errno_t rc;
+
+	wnd_id = ipc_get_arg1(icall);
+
+	if (srv->ops->get_window_info == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->get_window_info(srv->arg, wnd_id, &info);
+	if (rc != EOK) {
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	/* Send caption size */
+
+	if (!async_data_read_receive(&call, &size)) {
+		wndmgt_free_window_info(info);
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != sizeof(size_t)) {
+		wndmgt_free_window_info(info);
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	capsize = str_size(info->caption);
+
+	rc = async_data_read_finalize(&call, &capsize, size);
+	if (rc != EOK) {
+		wndmgt_free_window_info(info);
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	/* Send caption */
+
+	if (!async_data_read_receive(&call, &size)) {
+		wndmgt_free_window_info(info);
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != capsize) {
+		wndmgt_free_window_info(info);
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	rc = async_data_read_finalize(&call, info->caption, size);
+	if (rc != EOK) {
+		wndmgt_free_window_info(info);
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	async_answer_2(icall, EOK, info->flags, info->nfocus);
+	wndmgt_free_window_info(info);
+}
+
+static void wndmgt_activate_window_srv(wndmgt_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t dev_id;
+	sysarg_t wnd_id;
+	errno_t rc;
+
+	dev_id = ipc_get_arg1(icall);
+	wnd_id = ipc_get_arg2(icall);
+
+	if (srv->ops->activate_window == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->activate_window(srv->arg, dev_id, wnd_id);
+	async_answer_0(icall, rc);
+}
+
+static void wndmgt_close_window_srv(wndmgt_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t wnd_id;
+	errno_t rc;
+
+	wnd_id = ipc_get_arg1(icall);
+
+	if (srv->ops->close_window == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->close_window(srv->arg, wnd_id);
+	async_answer_0(icall, rc);
+}
+
+static void wndmgt_get_event_srv(wndmgt_srv_t *srv, ipc_call_t *icall)
+{
+	wndmgt_ev_t event;
+	ipc_call_t call;
+	size_t size;
+	errno_t rc;
+
+	if (srv->ops->get_event == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->get_event(srv->arg, &event);
+	if (rc != EOK) {
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	/* Transfer event data */
+	if (!async_data_read_receive(&call, &size)) {
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != sizeof(event)) {
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	rc = async_data_read_finalize(&call, &event, sizeof(event));
+	if (rc != EOK) {
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	async_answer_0(icall, EOK);
+}
+
+void wndmgt_conn(ipc_call_t *icall, wndmgt_srv_t *srv)
+{
+	/* Accept the connection */
+	async_accept_0(icall);
+
+	while (true) {
+		ipc_call_t call;
+
+		async_get_call(&call);
+		sysarg_t method = ipc_get_imethod(&call);
+
+		if (!method) {
+			/* The other side has hung up */
+			async_answer_0(&call, EOK);
+			break;
+		}
+
+		switch (method) {
+		case WNDMGT_CALLBACK_CREATE:
+			wndmgt_callback_create_srv(srv, &call);
+			break;
+		case WNDMGT_GET_WINDOW_LIST:
+			wndmgt_get_window_list_srv(srv, &call);
+			break;
+		case WNDMGT_GET_WINDOW_INFO:
+			wndmgt_get_window_info_srv(srv, &call);
+			break;
+		case WNDMGT_ACTIVATE_WINDOW:
+			wndmgt_activate_window_srv(srv, &call);
+			break;
+		case WNDMGT_CLOSE_WINDOW:
+			wndmgt_close_window_srv(srv, &call);
+			break;
+		case WNDMGT_GET_EVENT:
+			wndmgt_get_event_srv(srv, &call);
+			break;
+		default:
+			async_answer_0(&call, ENOTSUP);
+		}
+	}
+
+	/* Hang up callback session */
+	if (srv->client_sess != NULL) {
+		async_hangup(srv->client_sess);
+		srv->client_sess = NULL;
+	}
+}
+
+/** Initialize window management server structure
+ *
+ * @param srv Window management server structure to initialize
+ */
+void wndmgt_srv_initialize(wndmgt_srv_t *srv)
+{
+	memset(srv, 0, sizeof(*srv));
+}
+
+/** Send 'pending' event to client.
+ *
+ * @param srv Window management server structure
+ */
+void wndmgt_srv_ev_pending(wndmgt_srv_t *srv)
+{
+	async_exch_t *exch;
+
+	exch = async_exchange_begin(srv->client_sess);
+	async_msg_0(exch, WNDMGT_EV_PENDING);
+	async_exchange_end(exch);
+}
+
+/** @}
+ */
Index: uspace/lib/wndmgt/test/main.c
===================================================================
--- uspace/lib/wndmgt/test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/wndmgt/test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <pcut/pcut.h>
+
+PCUT_INIT;
+
+PCUT_IMPORT(wndmgt);
+
+PCUT_MAIN();
Index: uspace/lib/wndmgt/test/wndmgt.c
===================================================================
--- uspace/lib/wndmgt/test/wndmgt.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/lib/wndmgt/test/wndmgt.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,737 @@
+/*
+ * 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.
+ */
+
+#include <async.h>
+#include <errno.h>
+#include <wndmgt.h>
+#include <wndmgt_srv.h>
+#include <fibril_synch.h>
+#include <loc.h>
+#include <pcut/pcut.h>
+#include <str.h>
+#include "../private/wndmgt.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(wndmgt);
+
+static const char *test_wndmgt_server = "test-wndmgt";
+static const char *test_wndmgt_svc = "test/wndmgt";
+
+static void test_wndmgt_conn(ipc_call_t *, void *);
+
+static errno_t test_get_window_list(void *, wndmgt_window_list_t **);
+static errno_t test_get_window_info(void *, sysarg_t, wndmgt_window_info_t **);
+static errno_t test_activate_window(void *, sysarg_t, sysarg_t);
+static errno_t test_close_window(void *, sysarg_t);
+static errno_t test_get_event(void *, wndmgt_ev_t *);
+
+static void test_window_added(void *, sysarg_t);
+static void test_window_removed(void *, sysarg_t);
+static void test_window_changed(void *, sysarg_t);
+
+static wndmgt_ops_t test_wndmgt_srv_ops = {
+	.get_window_list = test_get_window_list,
+	.get_window_info = test_get_window_info,
+	.activate_window = test_activate_window,
+	.close_window = test_close_window,
+	.get_event = test_get_event
+};
+
+static wndmgt_cb_t test_wndmgt_cb = {
+	.window_added = test_window_added,
+	.window_removed = test_window_removed,
+	.window_changed = test_window_changed
+};
+
+/** Describes to the server how to respond to our request and pass tracking
+ * data back to the client.
+ */
+typedef struct {
+	errno_t rc;
+	sysarg_t wnd_id;
+	wndmgt_ev_t event;
+	wndmgt_ev_t revent;
+	int event_cnt;
+
+	bool get_window_list_called;
+	wndmgt_window_list_t *get_window_list_rlist;
+
+	bool get_window_info_called;
+	sysarg_t get_window_info_wnd_id;
+	wndmgt_window_info_t *get_window_info_rinfo;
+
+	bool activate_window_called;
+	sysarg_t activate_window_seat_id;
+	sysarg_t activate_window_wnd_id;
+
+	bool close_window_called;
+	sysarg_t close_window_wnd_id;
+
+	bool get_event_called;
+
+	bool window_added_called;
+	sysarg_t window_added_wnd_id;
+
+	bool window_removed_called;
+	sysarg_t window_removed_wnd_id;
+
+	bool window_changed_called;
+	sysarg_t window_changed_wnd_id;
+
+	fibril_condvar_t event_cv;
+	fibril_mutex_t event_lock;
+	wndmgt_srv_t *srv;
+} test_response_t;
+
+/** wndmgt_open(), wndmgt_close() work for valid window management service */
+PCUT_TEST(open_close)
+{
+	errno_t rc;
+	service_id_t sid;
+	wndmgt_t *wndmgt = NULL;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_wndmgt_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_wndmgt_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_wndmgt_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndmgt_open(test_wndmgt_svc, NULL, NULL, &wndmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wndmgt);
+
+	wndmgt_close(wndmgt);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** wndmgt_get_window_list() with server returning error response works */
+PCUT_TEST(get_window_list_failure)
+{
+	errno_t rc;
+	service_id_t sid;
+	wndmgt_t *wndmgt = NULL;
+	wndmgt_window_list_t *list;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_wndmgt_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_wndmgt_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_wndmgt_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndmgt_open(test_wndmgt_svc, NULL, NULL, &wndmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wndmgt);
+
+	resp.rc = ENOMEM;
+	resp.get_window_list_called = false;
+
+	rc = wndmgt_get_window_list(wndmgt, &list);
+	PCUT_ASSERT_TRUE(resp.get_window_list_called);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	wndmgt_close(wndmgt);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** wndmgt_get_window_list() with server returning success response works */
+PCUT_TEST(get_window_list_success)
+{
+	errno_t rc;
+	service_id_t sid;
+	wndmgt_t *wndmgt = NULL;
+	wndmgt_window_list_t *list;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_wndmgt_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_wndmgt_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_wndmgt_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndmgt_open(test_wndmgt_svc, NULL, NULL, &wndmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wndmgt);
+
+	resp.rc = EOK;
+	resp.get_window_list_called = false;
+	resp.get_window_list_rlist = calloc(1, sizeof(wndmgt_window_list_t));
+	PCUT_ASSERT_NOT_NULL(resp.get_window_list_rlist);
+	resp.get_window_list_rlist->nwindows = 2;
+	resp.get_window_list_rlist->windows = calloc(2, sizeof(sysarg_t));
+	PCUT_ASSERT_NOT_NULL(resp.get_window_list_rlist->windows);
+	resp.get_window_list_rlist->windows[0] = 42;
+	resp.get_window_list_rlist->windows[1] = 43;
+
+	rc = wndmgt_get_window_list(wndmgt, &list);
+	PCUT_ASSERT_TRUE(resp.get_window_list_called);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	PCUT_ASSERT_INT_EQUALS(2, list->nwindows);
+	PCUT_ASSERT_INT_EQUALS(42, list->windows[0]);
+	PCUT_ASSERT_INT_EQUALS(43, list->windows[1]);
+
+	wndmgt_free_window_list(list);
+	wndmgt_close(wndmgt);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** wndmgt_get_window_infp() with server returning error response works */
+PCUT_TEST(get_window_info_failure)
+{
+	errno_t rc;
+	service_id_t sid;
+	wndmgt_t *wndmgt = NULL;
+	sysarg_t wnd_id;
+	wndmgt_window_info_t *info;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_wndmgt_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_wndmgt_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_wndmgt_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndmgt_open(test_wndmgt_svc, NULL, NULL, &wndmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wndmgt);
+
+	resp.rc = ENOMEM;
+	resp.get_window_info_called = false;
+	wnd_id = 1;
+
+	rc = wndmgt_get_window_info(wndmgt, wnd_id, &info);
+	PCUT_ASSERT_TRUE(resp.get_window_info_called);
+	PCUT_ASSERT_INT_EQUALS(wnd_id, resp.get_window_info_wnd_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	wndmgt_close(wndmgt);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** wndmgt_get_window_info() with server returning success response works */
+PCUT_TEST(get_window_info_success)
+{
+	errno_t rc;
+	service_id_t sid;
+	wndmgt_t *wndmgt = NULL;
+	sysarg_t wnd_id;
+	wndmgt_window_info_t *info;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_wndmgt_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_wndmgt_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_wndmgt_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndmgt_open(test_wndmgt_svc, NULL, NULL, &wndmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wndmgt);
+
+	resp.rc = EOK;
+	resp.get_window_info_called = false;
+	resp.get_window_info_rinfo = calloc(1, sizeof(wndmgt_window_info_t));
+	PCUT_ASSERT_NOT_NULL(resp.get_window_info_rinfo);
+	resp.get_window_info_rinfo->caption = str_dup("Hello");
+	resp.get_window_info_rinfo->flags = 42;
+	resp.get_window_info_rinfo->nfocus = 123;
+	PCUT_ASSERT_NOT_NULL(resp.get_window_info_rinfo->caption);
+	wnd_id = 1;
+
+	rc = wndmgt_get_window_info(wndmgt, wnd_id, &info);
+	PCUT_ASSERT_TRUE(resp.get_window_info_called);
+	PCUT_ASSERT_INT_EQUALS(wnd_id, resp.get_window_info_wnd_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	PCUT_ASSERT_STR_EQUALS("Hello", info->caption);
+	PCUT_ASSERT_INT_EQUALS(42, info->flags);
+	PCUT_ASSERT_INT_EQUALS(123, info->nfocus);
+
+	wndmgt_free_window_info(info);
+	wndmgt_close(wndmgt);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** wndmgt_activate_window() with server returning error response works */
+PCUT_TEST(activate_window_failure)
+{
+	errno_t rc;
+	service_id_t sid;
+	wndmgt_t *wndmgt = NULL;
+	sysarg_t seat_id;
+	sysarg_t wnd_id;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_wndmgt_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_wndmgt_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_wndmgt_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndmgt_open(test_wndmgt_svc, NULL, NULL, &wndmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wndmgt);
+
+	seat_id = 13;
+	wnd_id = 42;
+	resp.rc = ENOMEM;
+	resp.activate_window_called = false;
+
+	rc = wndmgt_activate_window(wndmgt, seat_id, wnd_id);
+	PCUT_ASSERT_TRUE(resp.activate_window_called);
+	PCUT_ASSERT_INT_EQUALS(seat_id, resp.activate_window_seat_id);
+	PCUT_ASSERT_INT_EQUALS(wnd_id, resp.activate_window_wnd_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	wndmgt_close(wndmgt);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** wndmgt_activate_window() with server returning success response works */
+PCUT_TEST(activate_window_success)
+{
+	errno_t rc;
+	service_id_t sid;
+	wndmgt_t *wndmgt = NULL;
+	sysarg_t seat_id;
+	sysarg_t wnd_id;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_wndmgt_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_wndmgt_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_wndmgt_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndmgt_open(test_wndmgt_svc, NULL, NULL, &wndmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wndmgt);
+
+	seat_id = 13;
+	wnd_id = 42;
+	resp.rc = EOK;
+	resp.activate_window_called = false;
+
+	rc = wndmgt_activate_window(wndmgt, seat_id, wnd_id);
+	PCUT_ASSERT_TRUE(resp.activate_window_called);
+	PCUT_ASSERT_INT_EQUALS(seat_id, resp.activate_window_seat_id);
+	PCUT_ASSERT_INT_EQUALS(wnd_id, resp.activate_window_wnd_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	wndmgt_close(wndmgt);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** wndmgt_close_window() with server returning error response works */
+PCUT_TEST(close_window_failure)
+{
+	errno_t rc;
+	service_id_t sid;
+	wndmgt_t *wndmgt = NULL;
+	sysarg_t wnd_id;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_wndmgt_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_wndmgt_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_wndmgt_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndmgt_open(test_wndmgt_svc, NULL, NULL, &wndmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wndmgt);
+
+	wnd_id = 42;
+	resp.rc = ENOMEM;
+	resp.close_window_called = false;
+
+	rc = wndmgt_close_window(wndmgt, wnd_id);
+	PCUT_ASSERT_TRUE(resp.close_window_called);
+	PCUT_ASSERT_INT_EQUALS(wnd_id, resp.close_window_wnd_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	wndmgt_close(wndmgt);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** wndmgt_close_window() with server returning success response works */
+PCUT_TEST(close_window_success)
+{
+	errno_t rc;
+	service_id_t sid;
+	wndmgt_t *wndmgt = NULL;
+	sysarg_t wnd_id;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_wndmgt_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_wndmgt_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_wndmgt_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndmgt_open(test_wndmgt_svc, NULL, NULL, &wndmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wndmgt);
+
+	wnd_id = 42;
+	resp.rc = EOK;
+	resp.close_window_called = false;
+
+	rc = wndmgt_close_window(wndmgt, wnd_id);
+	PCUT_ASSERT_TRUE(resp.close_window_called);
+	PCUT_ASSERT_INT_EQUALS(wnd_id, resp.close_window_wnd_id);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+
+	wndmgt_close(wndmgt);
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** Window added event can be delivered from server to client callback function */
+PCUT_TEST(window_added_deliver)
+{
+	errno_t rc;
+	service_id_t sid;
+	wndmgt_t *wndmgt = NULL;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_wndmgt_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_wndmgt_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_wndmgt_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndmgt_open(test_wndmgt_svc, &test_wndmgt_cb, &resp, &wndmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wndmgt);
+	PCUT_ASSERT_NOT_NULL(resp.srv);
+
+	resp.event_cnt = 1;
+	resp.event.etype = wmev_window_added;
+	resp.event.wnd_id = 42;
+	resp.window_added_called = false;
+	fibril_mutex_initialize(&resp.event_lock);
+	fibril_condvar_initialize(&resp.event_cv);
+	wndmgt_srv_ev_pending(resp.srv);
+
+	/* Wait for the event handler to be called. */
+	fibril_mutex_lock(&resp.event_lock);
+	while (!resp.window_added_called) {
+		fibril_condvar_wait(&resp.event_cv, &resp.event_lock);
+	}
+	fibril_mutex_unlock(&resp.event_lock);
+
+	/* Verify that the event was delivered correctly */
+	PCUT_ASSERT_INT_EQUALS(resp.event.etype,
+	    resp.revent.etype);
+
+	wndmgt_close(wndmgt);
+
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** Window removed event can be delivered from server to client callback function */
+PCUT_TEST(window_removed_deliver)
+{
+	errno_t rc;
+	service_id_t sid;
+	wndmgt_t *wndmgt = NULL;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_wndmgt_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_wndmgt_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_wndmgt_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndmgt_open(test_wndmgt_svc, &test_wndmgt_cb, &resp, &wndmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wndmgt);
+	PCUT_ASSERT_NOT_NULL(resp.srv);
+
+	resp.event_cnt = 1;
+	resp.event.etype = wmev_window_removed;
+	resp.event.wnd_id = 42;
+	resp.window_removed_called = false;
+	fibril_mutex_initialize(&resp.event_lock);
+	fibril_condvar_initialize(&resp.event_cv);
+	wndmgt_srv_ev_pending(resp.srv);
+
+	/* Wait for the event handler to be called. */
+	fibril_mutex_lock(&resp.event_lock);
+	while (!resp.window_removed_called) {
+		fibril_condvar_wait(&resp.event_cv, &resp.event_lock);
+	}
+	fibril_mutex_unlock(&resp.event_lock);
+
+	/* Verify that the event was delivered correctly */
+	PCUT_ASSERT_INT_EQUALS(resp.event.etype,
+	    resp.revent.etype);
+
+	wndmgt_close(wndmgt);
+
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** Window changed event can be delivered from server to client callback function */
+PCUT_TEST(window_changed_deliver)
+{
+	errno_t rc;
+	service_id_t sid;
+	wndmgt_t *wndmgt = NULL;
+	test_response_t resp;
+	loc_srv_t *srv;
+
+	async_set_fallback_port_handler(test_wndmgt_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_wndmgt_server, &srv);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(srv, test_wndmgt_svc, &sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = wndmgt_open(test_wndmgt_svc, &test_wndmgt_cb, &resp, &wndmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(wndmgt);
+	PCUT_ASSERT_NOT_NULL(resp.srv);
+
+	resp.event_cnt = 1;
+	resp.event.etype = wmev_window_changed;
+	resp.event.wnd_id = 42;
+	resp.window_changed_called = false;
+	fibril_mutex_initialize(&resp.event_lock);
+	fibril_condvar_initialize(&resp.event_cv);
+	wndmgt_srv_ev_pending(resp.srv);
+
+	/* Wait for the event handler to be called. */
+	fibril_mutex_lock(&resp.event_lock);
+	while (!resp.window_changed_called) {
+		fibril_condvar_wait(&resp.event_cv, &resp.event_lock);
+	}
+	fibril_mutex_unlock(&resp.event_lock);
+
+	/* Verify that the event was delivered correctly */
+	PCUT_ASSERT_INT_EQUALS(resp.event.etype,
+	    resp.revent.etype);
+
+	wndmgt_close(wndmgt);
+
+	rc = loc_service_unregister(srv, sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	loc_server_unregister(srv);
+}
+
+/** Test window management service connection. */
+static void test_wndmgt_conn(ipc_call_t *icall, void *arg)
+{
+	test_response_t *resp = (test_response_t *) arg;
+	wndmgt_srv_t srv;
+
+	/* Set up protocol structure */
+	wndmgt_srv_initialize(&srv);
+	srv.ops = &test_wndmgt_srv_ops;
+	srv.arg = arg;
+	resp->srv = &srv;
+
+	/* Handle connection */
+	wndmgt_conn(icall, &srv);
+
+	resp->srv = NULL;
+}
+
+static void test_window_added(void *arg, sysarg_t wnd_id)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->revent.etype = wmev_window_added;
+
+	fibril_mutex_lock(&resp->event_lock);
+	resp->window_added_called = true;
+	resp->window_added_wnd_id = wnd_id;
+	fibril_condvar_broadcast(&resp->event_cv);
+	fibril_mutex_unlock(&resp->event_lock);
+}
+
+static void test_window_removed(void *arg, sysarg_t wnd_id)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->revent.etype = wmev_window_removed;
+
+	fibril_mutex_lock(&resp->event_lock);
+	resp->window_removed_called = true;
+	resp->window_removed_wnd_id = wnd_id;
+	fibril_condvar_broadcast(&resp->event_cv);
+	fibril_mutex_unlock(&resp->event_lock);
+}
+
+static void test_window_changed(void *arg, sysarg_t wnd_id)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->revent.etype = wmev_window_changed;
+
+	fibril_mutex_lock(&resp->event_lock);
+	resp->window_changed_called = true;
+	resp->window_changed_wnd_id = wnd_id;
+	fibril_condvar_broadcast(&resp->event_cv);
+	fibril_mutex_unlock(&resp->event_lock);
+}
+
+static errno_t test_get_window_list(void *arg, wndmgt_window_list_t **rlist)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->get_window_list_called = true;
+
+	if (resp->rc != EOK)
+		return resp->rc;
+
+	*rlist = resp->get_window_list_rlist;
+	return EOK;
+}
+
+static errno_t test_get_window_info(void *arg, sysarg_t wnd_id,
+    wndmgt_window_info_t **rinfo)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->get_window_info_called = true;
+	resp->get_window_info_wnd_id = wnd_id;
+
+	if (resp->rc != EOK)
+		return resp->rc;
+
+	*rinfo = resp->get_window_info_rinfo;
+	return EOK;
+}
+
+static errno_t test_activate_window(void *arg, sysarg_t seat_id,
+    sysarg_t wnd_id)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->activate_window_called = true;
+	resp->activate_window_seat_id = seat_id;
+	resp->activate_window_wnd_id = wnd_id;
+	return resp->rc;
+}
+
+static errno_t test_close_window(void *arg, sysarg_t wnd_id)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->close_window_called = true;
+	resp->close_window_wnd_id = wnd_id;
+	return resp->rc;
+}
+
+static errno_t test_get_event(void *arg, wndmgt_ev_t *event)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->get_event_called = true;
+	if (resp->event_cnt > 0) {
+		--resp->event_cnt;
+		*event = resp->event;
+		return EOK;
+	}
+
+	return ENOENT;
+}
+
+PCUT_EXPORT(wndmgt);
Index: uspace/meson.build
===================================================================
--- uspace/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -58,4 +58,10 @@
 endforeach
 
+if CONFIG_BAREBONE
+	drv_list = rd_essential_drv
+else
+	drv_list = rd_drv
+endif
+
 foreach drv : drvs
 	_basename = run_command(basename, drv, check: true).stdout().strip()
@@ -68,5 +74,5 @@
 
 	# Install driver metadata.
-	if not CONFIG_BAREBONE or rd_essential.contains('drv' / drv)
+	if drv_list.contains('drv' / drv)
 		_src = meson.current_source_dir() / 'drv' / drv / _basename + '.ma'
 		_dstdir = 'drv' / _basename
@@ -87,4 +93,5 @@
 	language = 'c'
 	installed_data = []
+	platform_specific = false
 
 	subdir(appdirs.get('subdir'))
@@ -93,5 +100,18 @@
 	installdir = appdirs.get('installdir')
 
-	install = not CONFIG_BAREBONE or rd_essential.contains(dir)
+	is_drv = (dir.split('/')[0] == 'drv')
+
+	if is_drv
+		# Drivers are installed based on rd_[essential_]drv list
+		install = drv_list.contains(dir)
+	else
+		#
+		# Servers and applications are installed all (unless
+		# platform-specific) or based on rd_essential in case
+		# of barebone build or platform-specific
+		#
+		install = (not CONFIG_BAREBONE and not platform_specific) \
+		    or rd_essential.contains(dir)
+	endif
 
 	if install
@@ -129,5 +149,4 @@
 	# Binaries in the 'drv' subdirectory link libdrv by default.
 
-	is_drv = (dir.split('/')[0] == 'drv')
 
 	if is_drv
Index: uspace/srv/audio/hound/audio_sink.c
===================================================================
--- uspace/srv/audio/hound/audio_sink.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/audio/hound/audio_sink.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -36,4 +36,5 @@
 #include <assert.h>
 #include <errno.h>
+#include <fibril_synch.h>
 #include <stdlib.h>
 #include <str.h>
@@ -64,4 +65,5 @@
 		return EINVAL;
 	link_initialize(&sink->link);
+	fibril_mutex_initialize(&sink->lock);
 	list_initialize(&sink->connections);
 	sink->name = str_dup(name);
@@ -139,4 +141,5 @@
 
 	pcm_format_silence(dest, size, &sink->format);
+	fibril_mutex_lock(&sink->lock);
 	list_foreach(sink->connections, sink_link, connection_t, conn) {
 		const errno_t ret = connection_add_source_data(
@@ -147,4 +150,5 @@
 		}
 	}
+	fibril_mutex_unlock(&sink->lock);
 }
 
Index: uspace/srv/audio/hound/audio_sink.h
===================================================================
--- uspace/srv/audio/hound/audio_sink.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/audio/hound/audio_sink.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -41,4 +41,5 @@
 #include <stdbool.h>
 #include <fibril.h>
+#include <fibril_synch.h>
 #include <pcm/format.h>
 
@@ -51,4 +52,6 @@
 	/** Link in hound's sink list */
 	link_t link;
+	/** Protect sink including list of connections */
+	fibril_mutex_t lock;
 	/** List of all related connections */
 	list_t connections;
Index: uspace/srv/audio/hound/connection.c
===================================================================
--- uspace/srv/audio/hound/connection.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/audio/hound/connection.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -61,5 +61,7 @@
 		conn->source = source;
 		list_append(&conn->source_link, &source->connections);
+		fibril_mutex_lock(&sink->lock);
 		list_append(&conn->sink_link, &sink->connections);
+		fibril_mutex_unlock(&sink->lock);
 		audio_sink_set_format(sink, audio_source_format(source));
 		if (source->connection_change)
@@ -83,5 +85,7 @@
 	assert(!link_in_use(&connection->hound_link));
 	list_remove(&connection->source_link);
+	fibril_mutex_lock(&connection->sink->lock);
 	list_remove(&connection->sink_link);
+	fibril_mutex_unlock(&connection->sink->lock);
 	if (connection->sink && connection->sink->connection_change)
 		connection->sink->connection_change(connection->sink, false);
Index: uspace/srv/audio/hound/hound.c
===================================================================
--- uspace/srv/audio/hound/hound.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/audio/hound/hound.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -126,4 +126,5 @@
 	assert(fibril_mutex_is_locked(&hound->list_guard));
 	log_verbose("Removing sink '%s'.", sink->name);
+	fibril_mutex_lock(&sink->lock);
 	if (!list_empty(&sink->connections))
 		log_warning("Removing sink '%s' while still connected.", sink->name);
@@ -135,4 +136,5 @@
 	}
 	list_remove(&sink->link);
+	fibril_mutex_unlock(&sink->lock);
 }
 
Index: uspace/srv/bd/file_bd/file_bd.c
===================================================================
--- uspace/srv/bd/file_bd/file_bd.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/bd/file_bd/file_bd.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2009 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -61,4 +61,5 @@
 static aoff64_t num_blocks;
 static FILE *img;
+static loc_srv_t *srv;
 
 static service_id_t service_id;
@@ -137,5 +138,5 @@
 		return -1;
 
-	rc = loc_service_register(device_name, &service_id);
+	rc = loc_service_register(srv, device_name, &service_id);
 	if (rc != EOK) {
 		printf("%s: Unable to register device '%s': %s.\n",
@@ -150,5 +151,5 @@
 	}
 
-	rc = loc_service_add_to_cat(service_id, disk_cat);
+	rc = loc_service_add_to_cat(srv, service_id, disk_cat);
 	if (rc != EOK) {
 		printf("%s: Failed adding %s to category: %s",
@@ -176,5 +177,5 @@
 
 	async_set_fallback_port_handler(file_bd_connection, NULL);
-	errno_t rc = loc_server_register(NAME);
+	errno_t rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		printf("%s: Unable to register driver.\n", NAME);
@@ -183,23 +184,37 @@
 
 	img = fopen(fname, "rb+");
-	if (img == NULL)
-		return EINVAL;
+	if (img == NULL) {
+		rc = EINVAL;
+		goto error;
+	}
 
 	if (fseek(img, 0, SEEK_END) != 0) {
-		fclose(img);
-		return EIO;
+		rc = EIO;
+		goto error;
 	}
 
 	off64_t img_size = ftell(img);
 	if (img_size < 0) {
+		rc = EIO;
+		goto error;
+	}
+
+	num_blocks = img_size / block_size;
+
+	fibril_mutex_initialize(&dev_lock);
+
+	return EOK;
+error:
+	if (img != NULL) {
 		fclose(img);
-		return EIO;
-	}
-
-	num_blocks = img_size / block_size;
-
-	fibril_mutex_initialize(&dev_lock);
-
-	return EOK;
+		img = NULL;
+	}
+
+	if (srv != NULL) {
+		loc_server_unregister(srv);
+		srv = NULL;
+	}
+
+	return rc;
 }
 
Index: uspace/srv/bd/rd/rd.c
===================================================================
--- uspace/srv/bd/rd/rd.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/bd/rd/rd.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2007 Michal Konopa
  * Copyright (c) 2007 Martin Jelen
@@ -93,4 +94,5 @@
 
 static bd_srvs_t bd_srvs;
+static loc_srv_t *srv;
 
 static void rd_client_conn(ipc_call_t *icall, void *arg)
@@ -178,5 +180,5 @@
 
 	async_set_fallback_port_handler(rd_client_conn, NULL);
-	ret = loc_server_register(NAME);
+	ret = loc_server_register(NAME, &srv);
 	if (ret != EOK) {
 		printf("%s: Unable to register driver: %s\n", NAME, str_error(ret));
@@ -185,5 +187,5 @@
 
 	service_id_t service_id;
-	ret = loc_service_register("bd/initrd", &service_id);
+	ret = loc_service_register(srv, "bd/initrd", &service_id);
 	if (ret != EOK) {
 		printf("%s: Unable to register device service\n", NAME);
Index: uspace/srv/bd/sata_bd/sata_bd.c
===================================================================
--- uspace/srv/bd/sata_bd/sata_bd.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/bd/sata_bd/sata_bd.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2012 Petr Jerman
  * All rights reserved.
@@ -57,4 +58,5 @@
 #define MAXDISKS  256
 
+static loc_srv_t *srv;
 static sata_bd_dev_t disk[MAXDISKS];
 static int disk_count;
@@ -249,5 +251,5 @@
 
 	async_set_fallback_port_handler(sata_bd_connection, NULL);
-	rc = loc_server_register(NAME);
+	rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		printf(NAME ": Unable to register driver: %s.\n", str_error(rc));
@@ -270,5 +272,5 @@
 		char name[1024];
 		snprintf(name, 1024, "%s/%s", NAMESPACE, disk[i].dev_name);
-		rc = loc_service_register(name, &disk[i].service_id);
+		rc = loc_service_register(srv, name, &disk[i].service_id);
 		if (rc != EOK) {
 			printf(NAME ": Unable to register device %s: %s\n", name, str_error(rc));
@@ -276,5 +278,5 @@
 		}
 
-		rc = loc_service_add_to_cat(disk[i].service_id, disk_cat);
+		rc = loc_service_add_to_cat(srv, disk[i].service_id, disk_cat);
 		if (rc != EOK) {
 			printf("%s: Failed adding %s to category: %s.",
Index: uspace/srv/bd/vbd/disk.c
===================================================================
--- uspace/srv/bd/vbd/disk.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/bd/vbd/disk.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2016 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -51,4 +51,6 @@
 #include "types/vbd.h"
 
+loc_srv_t *vbds_srv;
+
 static fibril_mutex_t vbds_disks_lock;
 static list_t vbds_disks; /* of vbds_disk_t */
@@ -497,5 +499,5 @@
 
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "block_init(%zu)", sid);
-	rc = block_init(sid, 2048);
+	rc = block_init(sid);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed opening block device %s.",
@@ -1122,5 +1124,5 @@
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "loc_service_register('%s')",
 	    name);
-	rc = loc_service_register(name, &psid);
+	rc = loc_service_register(vbds_srv, name, &psid);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering "
@@ -1131,5 +1133,5 @@
 	}
 
-	rc = loc_service_add_to_cat(psid, part_cid);
+	rc = loc_service_add_to_cat(vbds_srv, psid, part_cid);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failled adding partition "
@@ -1138,5 +1140,5 @@
 		free(part);
 
-		rc = loc_service_unregister(psid);
+		rc = loc_service_unregister(vbds_srv, psid);
 		if (rc != EOK) {
 			log_msg(LOG_DEFAULT, LVL_ERROR, "Error unregistering "
@@ -1160,5 +1162,5 @@
 	    "disk->svc_name='%s', id=%zu)", part->disk->svc_name, part->svc_id);
 
-	rc = loc_service_unregister(part->svc_id);
+	rc = loc_service_unregister(vbds_srv, part->svc_id);
 	if (rc != EOK)
 		return EIO;
Index: uspace/srv/bd/vbd/disk.h
===================================================================
--- uspace/srv/bd/vbd/disk.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/bd/vbd/disk.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2015 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -57,4 +57,6 @@
 extern void vbds_bd_conn(ipc_call_t *, void *);
 
+extern loc_srv_t *vbds_srv;
+
 #endif
 
Index: uspace/srv/bd/vbd/vbd.c
===================================================================
--- uspace/srv/bd/vbd/vbd.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/bd/vbd/vbd.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2015 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -68,5 +68,5 @@
 	async_set_fallback_port_handler(vbds_client_conn, NULL);
 
-	rc = loc_server_register(NAME);
+	rc = loc_server_register(NAME, &vbds_srv);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering server: %s.", str_error(rc));
@@ -74,5 +74,5 @@
 	}
 
-	rc = loc_service_register(SERVICE_NAME_VBD, &ctl_sid);
+	rc = loc_service_register(vbds_srv, SERVICE_NAME_VBD, &ctl_sid);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering service: %s.", str_error(rc));
Index: uspace/srv/clipboard/clipboard.c
===================================================================
--- uspace/srv/clipboard/clipboard.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/clipboard/clipboard.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2009 Martin Decky
  * All rights reserved.
@@ -185,9 +186,10 @@
 {
 	errno_t rc;
+	loc_srv_t *srv;
 
 	printf("%s: HelenOS clipboard service\n", NAME);
 	async_set_fallback_port_handler(clip_connection, NULL);
 
-	rc = loc_server_register(NAME);
+	rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		printf("%s: Failed registering server: %s\n", NAME, str_error(rc));
@@ -195,6 +197,8 @@
 	}
 
-	rc = loc_service_register(SERVICE_NAME_CLIPBOARD, &svc_id);
+	rc = loc_service_register(srv, SERVICE_NAME_CLIPBOARD, &svc_id);
 	if (rc != EOK) {
+		loc_server_unregister(srv);
+
 		printf("%s: Failed registering service : %s\n", NAME, str_error(rc));
 		return rc;
Index: uspace/srv/clipboard/meson.build
===================================================================
--- uspace/srv/clipboard/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/clipboard/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 #
+# Copyright (c) 2023 Jiri Svoboda
 # Copyright (c) 2005 Martin Decky
 # Copyright (c) 2007 Jakub Jermar
@@ -28,3 +29,4 @@
 #
 
+deps = [ 'clipboard' ]
 src = files('clipboard.c')
Index: uspace/srv/devman/devtree.c
===================================================================
--- uspace/srv/devman/devtree.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/devman/devtree.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -93,5 +93,5 @@
 }
 
-static hash_table_ops_t devman_devices_ops = {
+static const hash_table_ops_t devman_devices_ops = {
 	.hash = devman_devices_hash,
 	.key_hash = handle_key_hash,
@@ -101,5 +101,5 @@
 };
 
-static hash_table_ops_t devman_functions_ops = {
+static const hash_table_ops_t devman_functions_ops = {
 	.hash = devman_functions_hash,
 	.key_hash = handle_key_hash,
@@ -109,5 +109,5 @@
 };
 
-static hash_table_ops_t loc_devices_ops = {
+static const hash_table_ops_t loc_devices_ops = {
 	.hash = loc_functions_hash,
 	.key_hash = service_id_key_hash,
Index: uspace/srv/devman/drv_conn.c
===================================================================
--- uspace/srv/devman/drv_conn.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/devman/drv_conn.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2010 Lenka Trochtova
  * All rights reserved.
@@ -360,5 +361,5 @@
 	rc = loc_category_get_id(cat_name, &cat_id, IPC_FLAG_BLOCKING);
 	if (rc == EOK)
-		rc = loc_service_add_to_cat(fun->service_id, cat_id);
+		rc = loc_service_add_to_cat(devman_srv, fun->service_id, cat_id);
 	if (rc == EOK) {
 		log_msg(LOG_DEFAULT, LVL_NOTE, "Function `%s' added to category `%s'.",
Index: uspace/srv/devman/loc.c
===================================================================
--- uspace/srv/devman/loc.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/devman/loc.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2010 Lenka Trochtova
  * All rights reserved.
@@ -37,4 +38,5 @@
 #include "fun.h"
 #include "loc.h"
+#include "main.h"
 
 /** Create loc path and name for the function. */
@@ -59,5 +61,5 @@
 	}
 
-	loc_service_register(loc_pathname, &fun->service_id);
+	loc_service_register(devman_srv, loc_pathname, &fun->service_id);
 
 	tree_add_loc_function(tree, fun);
@@ -69,5 +71,5 @@
 errno_t loc_unregister_tree_function(fun_node_t *fun, dev_tree_t *tree)
 {
-	errno_t rc = loc_service_unregister(fun->service_id);
+	errno_t rc = loc_service_unregister(devman_srv, fun->service_id);
 	tree_rem_loc_function(tree, fun);
 	return rc;
Index: uspace/srv/devman/main.c
===================================================================
--- uspace/srv/devman/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/devman/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2010 Lenka Trochtova
  * All rights reserved.
@@ -64,4 +65,5 @@
 driver_list_t drivers_list;
 dev_tree_t device_tree;
+loc_srv_t *devman_srv;
 
 static void devman_connection_device(ipc_call_t *icall, void *arg)
@@ -280,4 +282,6 @@
 static bool devman_init(void)
 {
+	errno_t rc;
+
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "devman_init - looking for available drivers.");
 
@@ -305,5 +309,9 @@
 	 * not work.
 	 */
-	loc_server_register(NAME);
+	rc = loc_server_register(NAME, &devman_srv);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_FATAL, "Error registering devman server.");
+		return false;
+	}
 
 	return true;
Index: uspace/srv/devman/main.h
===================================================================
--- uspace/srv/devman/main.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/devman/main.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2013 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -34,8 +34,10 @@
 #define MAIN_H_
 
+#include <loc.h>
 #include "devman.h"
 
 extern driver_list_t drivers_list;
 extern dev_tree_t device_tree;
+extern loc_srv_t *devman_srv;
 
 #endif
Index: uspace/srv/fs/cdfs/cdfs_ops.c
===================================================================
--- uspace/srv/fs/cdfs/cdfs_ops.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/fs/cdfs/cdfs_ops.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2011 Martin Decky
  * All rights reserved.
@@ -323,5 +324,5 @@
 
 /** Nodes hash table operations */
-static hash_table_ops_t nodes_ops = {
+static const hash_table_ops_t nodes_ops = {
 	.hash = nodes_hash,
 	.key_hash = nodes_key_hash,
@@ -1093,5 +1094,5 @@
 
 	/* Initialize the block layer */
-	errno_t rc = block_init(service_id, BLOCK_SIZE);
+	errno_t rc = block_init(service_id);
 	if (rc != EOK)
 		return rc;
@@ -1150,5 +1151,5 @@
 {
 	/* Initialize the block layer */
-	errno_t rc = block_init(service_id, BLOCK_SIZE);
+	errno_t rc = block_init(service_id);
 	if (rc != EOK)
 		return rc;
Index: uspace/srv/fs/exfat/exfat_idx.c
===================================================================
--- uspace/srv/fs/exfat/exfat_idx.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/fs/exfat/exfat_idx.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -149,5 +149,5 @@
 }
 
-static hash_table_ops_t uph_ops = {
+static const hash_table_ops_t uph_ops = {
 	.hash = pos_hash,
 	.key_hash = pos_key_hash,
@@ -195,5 +195,5 @@
 }
 
-static hash_table_ops_t uih_ops = {
+static const hash_table_ops_t uih_ops = {
 	.hash = idx_hash,
 	.key_hash = idx_key_hash,
Index: uspace/srv/fs/exfat/exfat_ops.c
===================================================================
--- uspace/srv/fs/exfat/exfat_ops.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/fs/exfat/exfat_ops.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2008 Jakub Jermar
  * Copyright (c) 2011 Oleg Romanenko
@@ -1020,5 +1021,5 @@
 
 	/* initialize libblock */
-	rc = block_init(service_id, BS_SIZE);
+	rc = block_init(service_id);
 	if (rc != EOK)
 		return rc;
Index: uspace/srv/fs/fat/fat_idx.c
===================================================================
--- uspace/srv/fs/fat/fat_idx.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/fs/fat/fat_idx.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -149,5 +149,5 @@
 }
 
-static hash_table_ops_t uph_ops = {
+static const hash_table_ops_t uph_ops = {
 	.hash = pos_hash,
 	.key_hash = pos_key_hash,
@@ -195,5 +195,5 @@
 }
 
-static hash_table_ops_t uih_ops = {
+static const hash_table_ops_t uih_ops = {
 	.hash = idx_hash,
 	.key_hash = idx_key_hash,
Index: uspace/srv/fs/fat/fat_ops.c
===================================================================
--- uspace/srv/fs/fat/fat_ops.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/fs/fat/fat_ops.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2008 Jakub Jermar
  * Copyright (c) 2011 Oleg Romanenko
@@ -839,5 +840,5 @@
 service_id_t fat_service_get(fs_node_t *fn)
 {
-	return FAT_NODE(fn)->idx->service_id;
+	return 0;
 }
 
@@ -916,5 +917,5 @@
 
 	/* initialize libblock */
-	rc = block_init(service_id, BS_SIZE);
+	rc = block_init(service_id);
 	if (rc != EOK)
 		return rc;
Index: uspace/srv/fs/locfs/locfs_ops.c
===================================================================
--- uspace/srv/fs/locfs/locfs_ops.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/fs/locfs/locfs_ops.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -95,5 +95,5 @@
 }
 
-static hash_table_ops_t services_ops = {
+static const hash_table_ops_t services_ops = {
 	.hash = services_hash,
 	.key_hash = services_key_hash,
Index: uspace/srv/fs/mfs/mfs_ops.c
===================================================================
--- uspace/srv/fs/mfs/mfs_ops.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/fs/mfs/mfs_ops.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2011 Maurizio Lombardi
  * All rights reserved.
@@ -123,5 +124,5 @@
 }
 
-static hash_table_ops_t open_nodes_ops = {
+static const hash_table_ops_t open_nodes_ops = {
 	.hash = open_nodes_hash,
 	.key_hash = open_nodes_key_hash,
@@ -283,5 +284,5 @@
 
 	/* Initialize libblock */
-	rc = block_init(service_id, 4096);
+	rc = block_init(service_id);
 	if (rc != EOK)
 		return rc;
@@ -310,5 +311,5 @@
 
 	/* Initialize libblock */
-	rc = block_init(service_id, 4096);
+	rc = block_init(service_id);
 	if (rc != EOK)
 		return rc;
@@ -392,6 +393,5 @@
 mfs_service_get(fs_node_t *fsnode)
 {
-	struct mfs_node *node = fsnode->data;
-	return node->instance->service_id;
+	return 0;
 }
 
Index: uspace/srv/fs/tmpfs/tmpfs_ops.c
===================================================================
--- uspace/srv/fs/tmpfs/tmpfs_ops.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/fs/tmpfs/tmpfs_ops.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -189,5 +189,5 @@
 
 /** TMPFS nodes hash table operations. */
-hash_table_ops_t nodes_ops = {
+const hash_table_ops_t nodes_ops = {
 	.hash = nodes_hash,
 	.key_hash = nodes_key_hash,
Index: uspace/srv/fs/udf/udf.h
===================================================================
--- uspace/srv/fs/udf/udf.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/fs/udf/udf.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2008 Jakub Jermar
  * Copyright (c) 2012 Julia Medvedeva
@@ -51,6 +52,4 @@
 
 #define BS_BLOCK     0
-#define MIN_SIZE     512
-#define MAX_SIZE     8192
 #define DEFAULT_VOL  0
 
Index: uspace/srv/fs/udf/udf_idx.c
===================================================================
--- uspace/srv/fs/udf/udf_idx.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/fs/udf/udf_idx.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -78,5 +78,5 @@
 }
 
-static hash_table_ops_t udf_idx_ops = {
+static const hash_table_ops_t udf_idx_ops = {
 	.hash = udf_idx_hash,
 	.key_hash = udf_idx_key_hash,
Index: uspace/srv/fs/udf/udf_ops.c
===================================================================
--- uspace/srv/fs/udf/udf_ops.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/fs/udf/udf_ops.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2008 Jakub Jermar
  * Copyright (c) 2012 Julia Medvedeva
@@ -104,8 +105,4 @@
 static service_id_t udf_service_get(fs_node_t *node)
 {
-	udf_node_t *udfn = UDF_NODE(node);
-	if (udfn)
-		return udfn->instance->service_id;
-
 	return 0;
 }
@@ -327,5 +324,5 @@
 
 	/* initialize block cache */
-	errno_t rc = block_init(service_id, MAX_SIZE);
+	errno_t rc = block_init(service_id);
 	if (rc != EOK) {
 		free(instance);
Index: uspace/srv/hid/console/console.c
===================================================================
--- uspace/srv/hid/console/console.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/console/console.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2011 Martin Decky
  * All rights reserved.
@@ -37,4 +37,5 @@
 #include <stdio.h>
 #include <adt/prodcons.h>
+#include <io/console.h>
 #include <io/input.h>
 #include <ipc/vfs.h>
@@ -89,4 +90,6 @@
 } console_t;
 
+static loc_srv_t *console_srv;
+
 /** Input server proxy */
 static input_t *input;
@@ -120,8 +123,11 @@
 static errno_t input_ev_active(input_t *);
 static errno_t input_ev_deactive(input_t *);
-static errno_t input_ev_key(input_t *, kbd_event_type_t, keycode_t, keymod_t, char32_t);
-static errno_t input_ev_move(input_t *, int, int);
-static errno_t input_ev_abs_move(input_t *, unsigned, unsigned, unsigned, unsigned);
-static errno_t input_ev_button(input_t *, int, int);
+static errno_t input_ev_key(input_t *, unsigned, kbd_event_type_t, keycode_t,
+    keymod_t, char32_t);
+static errno_t input_ev_move(input_t *, unsigned, int, int);
+static errno_t input_ev_abs_move(input_t *, unsigned, unsigned, unsigned,
+    unsigned, unsigned);
+static errno_t input_ev_button(input_t *, unsigned, int, int);
+static errno_t input_ev_dclick(input_t *, unsigned, int);
 
 static input_ev_ops_t input_ev_ops = {
@@ -131,5 +137,6 @@
 	.move = input_ev_move,
 	.abs_move = input_ev_abs_move,
-	.button = input_ev_button
+	.button = input_ev_button,
+	.dclick = input_ev_dclick
 };
 
@@ -149,4 +156,5 @@
 static void cons_set_rgb_color(con_srv_t *, pixel_t, pixel_t);
 static void cons_set_cursor_visibility(con_srv_t *, bool);
+static errno_t cons_set_caption(con_srv_t *, const char *);
 static errno_t cons_get_event(con_srv_t *, cons_event_t *);
 static errno_t cons_map(con_srv_t *, sysarg_t, sysarg_t, charfield_t **);
@@ -170,4 +178,5 @@
 	.set_rgb_color = cons_set_rgb_color,
 	.set_cursor_visibility = cons_set_cursor_visibility,
+	.set_caption = cons_set_caption,
 	.get_event = cons_get_event,
 	.map = cons_map,
@@ -353,11 +362,17 @@
 }
 
-static errno_t input_ev_key(input_t *input, kbd_event_type_t type, keycode_t key,
-    keymod_t mods, char32_t c)
+static errno_t input_ev_key(input_t *input, unsigned kbd_id,
+    kbd_event_type_t type, keycode_t key, keymod_t mods, char32_t c)
 {
 	cons_event_t event;
-
+	bool alt;
+	bool shift;
+
+	alt = (mods & KM_ALT) != 0 && (mods & (KM_CTRL | KM_SHIFT)) == 0;
+	shift = (mods & KM_SHIFT) != 0 && (mods & (KM_CTRL | KM_ALT)) == 0;
+
+	/* Switch console on Alt+Fn or Shift+Fn */
 	if ((key >= KC_F1) && (key <= KC_F1 + CONSOLE_COUNT) &&
-	    ((mods & KM_CTRL) == 0)) {
+	    (alt || shift)) {
 		cons_switch(key - KC_F1);
 	} else {
@@ -365,4 +380,5 @@
 		event.type = CEV_KEY;
 
+		(void)kbd_id;
 		event.ev.key.type = type;
 		event.ev.key.key = key;
@@ -413,23 +429,44 @@
 }
 
-static errno_t input_ev_move(input_t *input, int dx, int dy)
-{
+static errno_t input_ev_move(input_t *input, unsigned pos_id, int dx, int dy)
+{
+	(void) pos_id;
 	pointer_update(pointer_x + dx, pointer_y + dy);
 	return EOK;
 }
 
-static errno_t input_ev_abs_move(input_t *input, unsigned x, unsigned y,
-    unsigned max_x, unsigned max_y)
-{
+static errno_t input_ev_abs_move(input_t *input, unsigned pos_id, unsigned x,
+    unsigned y, unsigned max_x, unsigned max_y)
+{
+	(void)pos_id;
 	pointer_update(mouse_scale_x * cols * x / max_x, mouse_scale_y * rows * y / max_y);
 	return EOK;
 }
 
-static errno_t input_ev_button(input_t *input, int bnum, int bpress)
+static errno_t input_ev_button(input_t *input, unsigned pos_id, int bnum,
+    int bpress)
 {
 	cons_event_t event;
+
+	(void)pos_id;
 
 	event.type = CEV_POS;
 	event.ev.pos.type = bpress ? POS_PRESS : POS_RELEASE;
+	event.ev.pos.btn_num = bnum;
+	event.ev.pos.hpos = pointer_x / mouse_scale_x;
+	event.ev.pos.vpos = pointer_y / mouse_scale_y;
+
+	console_queue_cons_event(active_console, &event);
+	return EOK;
+}
+
+static errno_t input_ev_dclick(input_t *input, unsigned pos_id, int bnum)
+{
+	cons_event_t event;
+
+	(void)pos_id;
+
+	event.type = CEV_POS;
+	event.ev.pos.type = POS_DCLICK;
 	event.ev.pos.btn_num = bnum;
 	event.ev.pos.hpos = pointer_x / mouse_scale_x;
@@ -653,4 +690,13 @@
 }
 
+static errno_t cons_set_caption(con_srv_t *srv, const char *caption)
+{
+	console_t *cons = srv_to_console(srv);
+
+	(void) cons;
+	(void) caption;
+	return EOK;
+}
+
 static errno_t cons_get_event(con_srv_t *srv, cons_event_t *event)
 {
@@ -865,5 +911,5 @@
 	/* Register server */
 	async_set_fallback_port_handler(client_connection, NULL);
-	rc = loc_server_register(NAME);
+	rc = loc_server_register(NAME, &console_srv);
 	if (rc != EOK) {
 		printf("%s: Unable to register server (%s)\n", NAME,
@@ -915,5 +961,6 @@
 			snprintf(vc, LOC_NAME_MAXLEN, "%s/vc%zu", NAMESPACE, i);
 
-			if (loc_service_register(vc, &consoles[i].dsid) != EOK) {
+			if (loc_service_register(console_srv, vc,
+			    &consoles[i].dsid) != EOK) {
 				printf("%s: Unable to register device %s\n", NAME, vc);
 				return false;
Index: uspace/srv/hid/console/meson.build
===================================================================
--- uspace/srv/hid/console/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/console/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -28,3 +28,4 @@
 #
 
+deps = [ 'console', 'input', 'output' ]
 src = files('console.c')
Index: uspace/srv/hid/display/cfgclient.c
===================================================================
--- uspace/srv/hid/display/cfgclient.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/srv/hid/display/cfgclient.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,191 @@
+/*
+ * 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 display
+ * @{
+ */
+/**
+ * @file Display server CFG client
+ */
+
+#include <adt/list.h>
+#include <errno.h>
+#include <io/log.h>
+#include <stdlib.h>
+#include "cfgclient.h"
+#include "display.h"
+#include "window.h"
+
+/** Create CFG client.
+ *
+ * @param display Parent display
+ * @param cb CFG client callbacks
+ * @param cb_arg Callback argument
+ * @param rcfgclient Place to store pointer to new CFG client.
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ds_cfgclient_create(ds_display_t *display, ds_cfgclient_cb_t *cb,
+    void *cb_arg, ds_cfgclient_t **rcfgclient)
+{
+	ds_cfgclient_t *cfgclient;
+
+	cfgclient = calloc(1, sizeof(ds_cfgclient_t));
+	if (cfgclient == NULL)
+		return ENOMEM;
+
+	list_initialize(&cfgclient->events);
+	cfgclient->cb = cb;
+	cfgclient->cb_arg = cb_arg;
+
+	ds_display_add_cfgclient(display, cfgclient);
+
+	*rcfgclient = cfgclient;
+	return EOK;
+}
+
+/** Destroy CFG client.
+ *
+ * @param cfgclient CFG client
+ */
+void ds_cfgclient_destroy(ds_cfgclient_t *cfgclient)
+{
+	ds_cfgclient_purge_events(cfgclient);
+	ds_display_remove_cfgclient(cfgclient);
+	free(cfgclient);
+}
+
+/** Get next event from CFG client event queue.
+ *
+ * @param cfgclient CFG client
+ * @param event Place to store event
+ * @return EOK on success, ENOENT if event queue is empty
+ */
+errno_t ds_cfgclient_get_event(ds_cfgclient_t *cfgclient, dispcfg_ev_t *event)
+{
+	link_t *link;
+	ds_cfgclient_ev_t *wevent;
+
+	link = list_first(&cfgclient->events);
+	if (link == NULL)
+		return ENOENT;
+
+	wevent = list_get_instance(link, ds_cfgclient_ev_t, levents);
+	list_remove(link);
+
+	*event = wevent->event;
+	free(wevent);
+	return EOK;
+}
+
+/** Purge events from CFG client event queue.
+ *
+ * @param client Client
+ */
+void ds_cfgclient_purge_events(ds_cfgclient_t *cfgclient)
+{
+	link_t *cur;
+	link_t *next;
+	ds_cfgclient_ev_t *wevent;
+
+	cur = list_first(&cfgclient->events);
+	while (cur != NULL) {
+		next = list_next(cur, &cfgclient->events);
+		wevent = list_get_instance(cur, ds_cfgclient_ev_t, levents);
+
+		list_remove(cur);
+		free(wevent);
+		cur = next;
+	}
+}
+
+/** Post seat added event to the CFG client's message queue.
+ *
+ * @param cfgclient CFG client
+ * @param seat_id Seat ID of the added seat
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ds_cfgclient_post_seat_added_event(ds_cfgclient_t *cfgclient,
+    sysarg_t seat_id)
+{
+	ds_cfgclient_ev_t *sevent;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "cfgclient_pos_seat_added_event "
+	    "cfgclient=%p seat_id=%zu\n", (void *)cfgclient, seat_id);
+
+	sevent = calloc(1, sizeof(ds_cfgclient_ev_t));
+	if (sevent == NULL)
+		return ENOMEM;
+
+	sevent->cfgclient = cfgclient;
+	sevent->event.etype = dcev_seat_added;
+	sevent->event.seat_id = seat_id;
+	list_append(&sevent->levents, &cfgclient->events);
+
+	/* Notify the client */
+	// TODO Do not send more than once until client drains the queue
+	if (cfgclient->cb != NULL && cfgclient->cb->ev_pending != NULL)
+		cfgclient->cb->ev_pending(cfgclient->cb_arg);
+
+	return EOK;
+}
+
+/** Post seat removed event to the CFG client's message queue.
+ *
+ * @param cfgclient CFG client
+ * @param seat_id Seat ID of the added seat
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ds_cfgclient_post_seat_removed_event(ds_cfgclient_t *cfgclient,
+    sysarg_t seat_id)
+{
+	ds_cfgclient_ev_t *sevent;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "cfgclient_pos_seat_removed_event "
+	    "cfgclient=%p seat_id=%zu\n", (void *)cfgclient, seat_id);
+
+	sevent = calloc(1, sizeof(ds_cfgclient_ev_t));
+	if (sevent == NULL)
+		return ENOMEM;
+
+	sevent->cfgclient = cfgclient;
+	sevent->event.etype = dcev_seat_removed;
+	sevent->event.seat_id = seat_id;
+	list_append(&sevent->levents, &cfgclient->events);
+
+	/* Notify the client */
+	// TODO Do not send more than once until client drains the queue
+	if (cfgclient->cb != NULL && cfgclient->cb->ev_pending != NULL)
+		cfgclient->cb->ev_pending(cfgclient->cb_arg);
+
+	return EOK;
+}
+
+/** @}
+ */
Index: uspace/srv/hid/display/cfgclient.h
===================================================================
--- uspace/srv/hid/display/cfgclient.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/srv/hid/display/cfgclient.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,55 @@
+/*
+ * 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 display
+ * @{
+ */
+/**
+ * @file Display server CFG client
+ */
+
+#ifndef CFGCLIENT_H
+#define CFGCLIENT_H
+
+#include <errno.h>
+#include <dispcfg.h>
+#include "types/display/display.h"
+#include "types/display/cfgclient.h"
+
+extern errno_t ds_cfgclient_create(ds_display_t *, ds_cfgclient_cb_t *, void *,
+    ds_cfgclient_t **);
+extern void ds_cfgclient_destroy(ds_cfgclient_t *);
+extern errno_t ds_cfgclient_get_event(ds_cfgclient_t *, dispcfg_ev_t *);
+extern errno_t ds_cfgclient_post_seat_added_event(ds_cfgclient_t *, sysarg_t);
+extern errno_t ds_cfgclient_post_seat_removed_event(ds_cfgclient_t *, sysarg_t);
+extern void ds_cfgclient_purge_events(ds_cfgclient_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/hid/display/cfgops.c
===================================================================
--- uspace/srv/hid/display/cfgops.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/srv/hid/display/cfgops.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,373 @@
+/*
+ * 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 display
+ * @{
+ */
+/**
+ * @file Display configuration ops implementation
+ */
+
+#include <errno.h>
+#include <io/log.h>
+#include <stdlib.h>
+#include <str.h>
+#include <dispcfg_srv.h>
+#include "display.h"
+#include "idevcfg.h"
+#include "seat.h"
+#include "cfgclient.h"
+
+static errno_t dispc_get_seat_list(void *, dispcfg_seat_list_t **);
+static errno_t dispc_get_seat_info(void *, sysarg_t, dispcfg_seat_info_t **);
+static errno_t dispc_seat_create(void *, const char *, sysarg_t *);
+static errno_t dispc_seat_delete(void *, sysarg_t);
+static errno_t dispc_dev_assign(void *, sysarg_t, sysarg_t);
+static errno_t dispc_dev_unassign(void *, sysarg_t);
+static errno_t dispc_get_asgn_dev_list(void *, sysarg_t, dispcfg_dev_list_t **);
+static errno_t dispc_get_event(void *, dispcfg_ev_t *);
+
+dispcfg_ops_t dispcfg_srv_ops = {
+	.get_seat_list = dispc_get_seat_list,
+	.get_seat_info = dispc_get_seat_info,
+	.seat_create = dispc_seat_create,
+	.seat_delete = dispc_seat_delete,
+	.dev_assign = dispc_dev_assign,
+	.dev_unassign = dispc_dev_unassign,
+	.get_asgn_dev_list = dispc_get_asgn_dev_list,
+	.get_event = dispc_get_event,
+};
+
+/** Get seat list.
+ *
+ * @param arg Argument (CFG client)
+ * @param rlist Place to store pointer to new list
+ * @return EOK on success or an error code
+ */
+static errno_t dispc_get_seat_list(void *arg, dispcfg_seat_list_t **rlist)
+{
+	ds_cfgclient_t *cfgclient = (ds_cfgclient_t *)arg;
+	dispcfg_seat_list_t *list;
+	ds_seat_t *seat;
+	unsigned i;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dispcfg_get_seat_list()");
+
+	list = calloc(1, sizeof(dispcfg_seat_list_t));
+	if (list == NULL)
+		return ENOMEM;
+
+	ds_display_lock(cfgclient->display);
+
+	/* Count the number of seats */
+	list->nseats = 0;
+	seat = ds_display_first_seat(cfgclient->display);
+	while (seat != NULL) {
+		++list->nseats;
+		seat = ds_display_next_seat(seat);
+	}
+
+	/* Allocate array for seat IDs */
+	list->seats = calloc(list->nseats, sizeof(sysarg_t));
+	if (list->seats == NULL) {
+		ds_display_unlock(cfgclient->display);
+		free(list);
+		return ENOMEM;
+	}
+
+	/* Fill in seat IDs */
+	i = 0;
+	seat = ds_display_first_seat(cfgclient->display);
+	while (seat != NULL) {
+		list->seats[i++] = seat->id;
+		seat = ds_display_next_seat(seat);
+	}
+
+	ds_display_unlock(cfgclient->display);
+	*rlist = list;
+	return EOK;
+}
+
+/** Get seat information.
+ *
+ * @param arg Argument (CFG client)
+ * @param seat_id Seat ID
+ * @param rinfo Place to store pointer to new seat information structure
+ * @return EOK on success or an error code
+ */
+static errno_t dispc_get_seat_info(void *arg, sysarg_t seat_id,
+    dispcfg_seat_info_t **rinfo)
+{
+	ds_cfgclient_t *cfgclient = (ds_cfgclient_t *)arg;
+	ds_seat_t *seat;
+	dispcfg_seat_info_t *info;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dispcfg_get_seat_info()");
+
+	ds_display_lock(cfgclient->display);
+	seat = ds_display_find_seat(cfgclient->display, seat_id);
+	if (seat == NULL) {
+		ds_display_unlock(cfgclient->display);
+		return ENOENT;
+	}
+
+	info = calloc(1, sizeof(dispcfg_seat_info_t));
+	if (info == NULL) {
+		ds_display_unlock(cfgclient->display);
+		return ENOMEM;
+	}
+
+	(void)seat;
+	info->name = str_dup(seat->name);
+	if (info->name == NULL) {
+		ds_display_unlock(cfgclient->display);
+		free(info);
+		return ENOMEM;
+	}
+
+	ds_display_unlock(cfgclient->display);
+	*rinfo = info;
+	return EOK;
+}
+
+/** Create seat.
+ *
+ * @param arg Argument (CFG client)
+ * @param name Seat name
+ * @param rseat_id Place to store ID of the new seat
+ * @return EOK on success or an error code
+ */
+static errno_t dispc_seat_create(void *arg, const char *name,
+    sysarg_t *rseat_id)
+{
+	ds_cfgclient_t *cfgclient = (ds_cfgclient_t *)arg;
+	ds_seat_t *seat;
+	errno_t rc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dispcfg_seat_create()");
+
+	ds_display_lock(cfgclient->display);
+
+	rc = ds_seat_create(cfgclient->display, name, &seat);
+	if (rc != EOK) {
+		ds_display_unlock(cfgclient->display);
+		return rc;
+	}
+
+	(void) ds_display_paint(cfgclient->display, NULL);
+	ds_display_unlock(cfgclient->display);
+
+	*rseat_id = seat->id;
+	return EOK;
+}
+
+/** Delete seat.
+ *
+ * @param arg Argument (CFG client)
+ * @param seat_id Seat ID
+ * @return EOK on success or an error code
+ */
+static errno_t dispc_seat_delete(void *arg, sysarg_t seat_id)
+{
+	ds_cfgclient_t *cfgclient = (ds_cfgclient_t *)arg;
+	ds_seat_t *seat;
+	ds_seat_t *s;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dispcfg_seat_delete()");
+
+	ds_display_lock(cfgclient->display);
+	seat = ds_display_find_seat(cfgclient->display, seat_id);
+	if (seat == NULL) {
+		ds_display_unlock(cfgclient->display);
+		return ENOENT;
+	}
+
+	/* Verify we are not deleting the last seat */
+	s = ds_display_first_seat(cfgclient->display);
+	if (s == seat && ds_display_next_seat(s) == NULL) {
+		ds_display_unlock(cfgclient->display);
+		return EBUSY;
+	}
+
+	ds_seat_destroy(seat);
+
+	(void) ds_display_paint(cfgclient->display, NULL);
+	ds_display_unlock(cfgclient->display);
+	return EOK;
+}
+
+/** Assign device to seat.
+ *
+ * @param arg Argument (CFG client)
+ * @param svc_id Device service ID
+ * @param seat_id Seat ID
+ * @return EOK on success or an error code
+ */
+static errno_t dispc_dev_assign(void *arg, sysarg_t svc_id, sysarg_t seat_id)
+{
+	ds_cfgclient_t *cfgclient = (ds_cfgclient_t *)arg;
+	ds_seat_t *seat;
+	ds_idevcfg_t *idevcfg;
+	errno_t rc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dispcfg_dev_assign()");
+
+	ds_display_lock(cfgclient->display);
+	seat = ds_display_find_seat(cfgclient->display, seat_id);
+	if (seat == NULL) {
+		ds_display_unlock(cfgclient->display);
+		return ENOENT;
+	}
+
+	rc = ds_idevcfg_create(cfgclient->display, svc_id, seat, &idevcfg);
+	if (rc != EOK) {
+		assert(rc == ENOMEM);
+		ds_display_unlock(cfgclient->display);
+		return ENOMEM;
+	}
+
+	(void)idevcfg;
+
+	ds_display_unlock(cfgclient->display);
+	return EOK;
+}
+
+/** Unassign device from any seat.
+ *
+ * @param arg Argument (CFG client)
+ * @param svc_id Device service ID
+ * @return EOK on success or an error code
+ */
+static errno_t dispc_dev_unassign(void *arg, sysarg_t svc_id)
+{
+	ds_cfgclient_t *cfgclient = (ds_cfgclient_t *)arg;
+	ds_idevcfg_t *idevcfg;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dispcfg_dev_unassign()");
+
+	ds_display_lock(cfgclient->display);
+
+	idevcfg = ds_display_first_idevcfg(cfgclient->display);
+	while (idevcfg != NULL) {
+		if (idevcfg->svc_id == svc_id)
+			break;
+
+		idevcfg = ds_display_next_idevcfg(idevcfg);
+	}
+
+	if (idevcfg == NULL) {
+		ds_display_unlock(cfgclient->display);
+		return ENOENT;
+	}
+
+	ds_idevcfg_destroy(idevcfg);
+	ds_display_unlock(cfgclient->display);
+	return EOK;
+}
+
+/** Get assigned device list.
+ *
+ * @param arg Argument (CFG client)
+ * @param seat_id Seat ID
+ * @param rlist Place to store pointer to new list
+ * @return EOK on success or an error code
+ */
+static errno_t dispc_get_asgn_dev_list(void *arg, sysarg_t seat_id,
+    dispcfg_dev_list_t **rlist)
+{
+	ds_cfgclient_t *cfgclient = (ds_cfgclient_t *)arg;
+	dispcfg_dev_list_t *list;
+	ds_seat_t *seat;
+	ds_idevcfg_t *idevcfg;
+	unsigned i;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dispcfg_get_asgn_dev_list()");
+
+	list = calloc(1, sizeof(dispcfg_dev_list_t));
+	if (list == NULL)
+		return ENOMEM;
+
+	ds_display_lock(cfgclient->display);
+
+	seat = ds_display_find_seat(cfgclient->display, seat_id);
+	if (seat == NULL) {
+		ds_display_unlock(cfgclient->display);
+		free(list);
+		return ENOENT;
+	}
+
+	/* Count the number of devices */
+	list->ndevs = 0;
+	idevcfg = ds_seat_first_idevcfg(seat);
+	while (idevcfg != NULL) {
+		++list->ndevs;
+		idevcfg = ds_display_next_idevcfg(idevcfg);
+	}
+
+	/* Allocate array for device IDs */
+	list->devs = calloc(list->ndevs, sizeof(sysarg_t));
+	if (list->devs == NULL) {
+		ds_display_unlock(cfgclient->display);
+		free(list);
+		return ENOMEM;
+	}
+
+	/* Fill in device IDs */
+	i = 0;
+	idevcfg = ds_seat_first_idevcfg(seat);
+	while (idevcfg != NULL) {
+		list->devs[i++] = idevcfg->svc_id;
+		idevcfg = ds_display_next_idevcfg(idevcfg);
+	}
+
+	ds_display_unlock(cfgclient->display);
+	*rlist = list;
+	return EOK;
+}
+
+/** Get display configuration event.
+ *
+ * @param arg Argument (CFG client)
+ * @param ev Place to store event
+ * @return EOK on success, ENOENT if there are no events
+ */
+static errno_t dispc_get_event(void *arg, dispcfg_ev_t *ev)
+{
+	ds_cfgclient_t *cfgclient = (ds_cfgclient_t *)arg;
+	errno_t rc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dispcfg_get_event()");
+
+	ds_display_lock(cfgclient->display);
+	rc = ds_cfgclient_get_event(cfgclient, ev);
+	ds_display_unlock(cfgclient->display);
+	return rc;
+}
+
+/** @}
+ */
Index: uspace/srv/hid/display/cfgops.h
===================================================================
--- uspace/srv/hid/display/cfgops.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/srv/hid/display/cfgops.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,46 @@
+/*
+ * 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 display
+ * @{
+ */
+/**
+ * @file Display configuration ops implementation
+ */
+
+#ifndef CFGOPS_H
+#define CFGOPS_H
+
+#include <dispcfg_srv.h>
+
+extern dispcfg_ops_t dispcfg_srv_ops;
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/hid/display/client.c
===================================================================
--- uspace/srv/hid/display/client.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/client.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -181,5 +181,5 @@
  * @param ewindow Place to store pointer to window receiving the event
  * @param event Place to store event
- * @return Graphic context
+ * @return EOK on success, ENOENT if event queue is empty
  */
 errno_t ds_client_get_event(ds_client_t *client, ds_window_t **ewindow,
@@ -257,8 +257,10 @@
  * @param client Client
  * @param ewindow Window that the message is targetted to
- *
- * @return EOK on success or an error code
- */
-errno_t ds_client_post_focus_event(ds_client_t *client, ds_window_t *ewindow)
+ * @param event Focus event data
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ds_client_post_focus_event(ds_client_t *client, ds_window_t *ewindow,
+    display_wnd_focus_ev_t *event)
 {
 	ds_window_ev_t *wevent;
@@ -270,4 +272,5 @@
 	wevent->window = ewindow;
 	wevent->event.etype = wev_focus;
+	wevent->event.ev.focus = *event;
 	list_append(&wevent->levents, &client->events);
 
@@ -373,8 +376,10 @@
  * @param client Client
  * @param ewindow Window that the message is targetted to
- *
- * @return EOK on success or an error code
- */
-errno_t ds_client_post_unfocus_event(ds_client_t *client, ds_window_t *ewindow)
+ * @param event Unfocus event data
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ds_client_post_unfocus_event(ds_client_t *client, ds_window_t *ewindow,
+    display_wnd_unfocus_ev_t *event)
 {
 	ds_window_ev_t *wevent;
@@ -386,4 +391,5 @@
 	wevent->window = ewindow;
 	wevent->event.etype = wev_unfocus;
+	wevent->event.ev.unfocus = *event;
 	list_append(&wevent->levents, &client->events);
 
Index: uspace/srv/hid/display/client.h
===================================================================
--- uspace/srv/hid/display/client.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/client.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -55,5 +55,6 @@
 extern void ds_client_purge_window_events(ds_client_t *, ds_window_t *);
 extern errno_t ds_client_post_close_event(ds_client_t *, ds_window_t *);
-extern errno_t ds_client_post_focus_event(ds_client_t *, ds_window_t *);
+extern errno_t ds_client_post_focus_event(ds_client_t *, ds_window_t *,
+    display_wnd_focus_ev_t *);
 extern errno_t ds_client_post_kbd_event(ds_client_t *, ds_window_t *,
     kbd_event_t *);
@@ -62,5 +63,6 @@
 extern errno_t ds_client_post_resize_event(ds_client_t *, ds_window_t *,
     gfx_rect_t *);
-extern errno_t ds_client_post_unfocus_event(ds_client_t *, ds_window_t *);
+extern errno_t ds_client_post_unfocus_event(ds_client_t *, ds_window_t *,
+    display_wnd_unfocus_ev_t *);
 
 #endif
Index: uspace/srv/hid/display/display.c
===================================================================
--- uspace/srv/hid/display/display.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/display.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -45,7 +45,8 @@
 #include "cursimg.h"
 #include "cursor.h"
+#include "display.h"
 #include "seat.h"
 #include "window.h"
-#include "display.h"
+#include "wmclient.h"
 
 static gfx_context_t *ds_display_get_unbuf_gc(ds_display_t *);
@@ -96,6 +97,9 @@
 	fibril_mutex_initialize(&disp->lock);
 	list_initialize(&disp->clients);
+	list_initialize(&disp->wmclients);
+	list_initialize(&disp->cfgclients);
 	disp->next_wnd_id = 1;
 	list_initialize(&disp->ddevs);
+	list_initialize(&disp->idevcfgs);
 	list_initialize(&disp->seats);
 	list_initialize(&disp->windows);
@@ -114,7 +118,21 @@
 void ds_display_destroy(ds_display_t *disp)
 {
+	int i;
+
 	assert(list_empty(&disp->clients));
+	assert(list_empty(&disp->wmclients));
+	assert(list_empty(&disp->cfgclients));
 	assert(list_empty(&disp->seats));
-	/* XXX destroy cursors */
+	assert(list_empty(&disp->ddevs));
+	assert(list_empty(&disp->idevcfgs));
+	assert(list_empty(&disp->seats));
+	assert(list_empty(&disp->windows));
+
+	/* Destroy cursors */
+	for (i = 0; i < dcurs_limit; i++) {
+		ds_cursor_destroy(disp->cursor[i]);
+		disp->cursor[i] = NULL;
+	}
+
 	gfx_color_delete(disp->bg_color);
 	free(disp);
@@ -203,4 +221,83 @@
 
 	return list_get_instance(link, ds_client_t, lclients);
+}
+
+/** Add WM client to display.
+ *
+ * @param disp Display
+ * @param wmclient WM client
+ */
+void ds_display_add_wmclient(ds_display_t *disp, ds_wmclient_t *wmclient)
+{
+	assert(wmclient->display == NULL);
+	assert(!link_used(&wmclient->lwmclients));
+
+	wmclient->display = disp;
+	list_append(&wmclient->lwmclients, &disp->wmclients);
+}
+
+/** Remove WM client from display.
+ *
+ * @param wmclient WM client
+ */
+void ds_display_remove_wmclient(ds_wmclient_t *wmclient)
+{
+	list_remove(&wmclient->lwmclients);
+	wmclient->display = NULL;
+}
+
+/** Add CFG client to display.
+ *
+ * @param disp Display
+ * @param cfgclient CFG client
+ */
+void ds_display_add_cfgclient(ds_display_t *disp, ds_cfgclient_t *cfgclient)
+{
+	assert(cfgclient->display == NULL);
+	assert(!link_used(&cfgclient->lcfgclients));
+
+	cfgclient->display = disp;
+	list_append(&cfgclient->lcfgclients, &disp->cfgclients);
+}
+
+/** Remove CFG client from display.
+ *
+ * @param cfgclient CFG client
+ */
+void ds_display_remove_cfgclient(ds_cfgclient_t *cfgclient)
+{
+	list_remove(&cfgclient->lcfgclients);
+	cfgclient->display = NULL;
+}
+
+/** Get first WM client in display.
+ *
+ * @param disp Display
+ * @return First WM client or @c NULL if there is none
+ */
+ds_wmclient_t *ds_display_first_wmclient(ds_display_t *disp)
+{
+	link_t *link = list_first(&disp->wmclients);
+
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ds_wmclient_t, lwmclients);
+}
+
+/** Get next WM client in display.
+ *
+ * @param wmclient Current WM client
+ * @return Next WM client or @c NULL if there is none
+ */
+ds_wmclient_t *ds_display_next_wmclient(ds_wmclient_t *wmclient)
+{
+	link_t *link = list_next(&wmclient->lwmclients,
+	    &wmclient->display->wmclients);
+
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ds_wmclient_t, lwmclients);
 }
 
@@ -246,5 +343,6 @@
 		gfx_rect_translate(&wnd->dpos, &wnd->rect, &drect);
 
-		if (gfx_pix_inside_rect(pos, &drect))
+		if (gfx_pix_inside_rect(pos, &drect) &&
+		    ds_window_is_visible(wnd))
 			return wnd;
 
@@ -255,4 +353,36 @@
 }
 
+/** Add window to window list.
+ *
+ * Topmost windows are enlisted before any other window. Non-topmost
+ * windows are enlisted before any other non-topmost window.
+ *
+ * @param display Display
+ * @param wnd Window
+ */
+void ds_display_enlist_window(ds_display_t *display, ds_window_t *wnd)
+{
+	ds_window_t *w;
+
+	assert(wnd->display == display);
+	assert(!link_used(&wnd->ldwindows));
+
+	if ((wnd->flags & wndf_topmost) == 0) {
+		/* Find the first non-topmost window */
+		w = ds_display_first_window(display);
+		while (w != NULL && (w->flags & wndf_topmost) != 0)
+			w = ds_display_next_window(w);
+
+		if (w != NULL)
+			list_insert_before(&wnd->ldwindows, &w->ldwindows);
+		else
+			list_append(&wnd->ldwindows, &display->windows);
+	} else {
+		/* Insert at the beginning */
+		list_prepend(&wnd->ldwindows, &display->windows);
+	}
+
+}
+
 /** Add window to display.
  *
@@ -262,9 +392,18 @@
 void ds_display_add_window(ds_display_t *display, ds_window_t *wnd)
 {
+	ds_wmclient_t *wmclient;
+
 	assert(wnd->display == NULL);
 	assert(!link_used(&wnd->ldwindows));
 
 	wnd->display = display;
-	list_prepend(&wnd->ldwindows, &display->windows);
+	ds_display_enlist_window(display, wnd);
+
+	/* Notify window managers about the new window */
+	wmclient = ds_display_first_wmclient(display);
+	while (wmclient != NULL) {
+		ds_wmclient_post_wnd_added_event(wmclient, wnd->id);
+		wmclient = ds_display_next_wmclient(wmclient);
+	}
 }
 
@@ -275,6 +414,32 @@
 void ds_display_remove_window(ds_window_t *wnd)
 {
+	ds_wmclient_t *wmclient;
+	ds_display_t *display;
+
+	display = wnd->display;
+
 	list_remove(&wnd->ldwindows);
 	wnd->display = NULL;
+
+	/* Notify window managers about the removed window */
+	wmclient = ds_display_first_wmclient(display);
+	while (wmclient != NULL) {
+		ds_wmclient_post_wnd_removed_event(wmclient, wnd->id);
+		wmclient = ds_display_next_wmclient(wmclient);
+	}
+}
+
+/** Move window to top.
+ *
+ * @param display Display
+ * @param wnd Window
+ */
+void ds_display_window_to_top(ds_window_t *wnd)
+{
+	assert(wnd->display != NULL);
+	assert(link_used(&wnd->ldwindows));
+
+	list_remove(&wnd->ldwindows);
+	ds_display_enlist_window(wnd->display, wnd);
 }
 
@@ -352,6 +517,6 @@
 	ds_seat_t *seat;
 
-	// TODO Determine which seat the event belongs to
-	seat = ds_display_first_seat(display);
+	/* Determine which seat the event belongs to */
+	seat = ds_display_seat_by_idev(display, event->kbd_id);
 	if (seat == NULL)
 		return EOK;
@@ -369,6 +534,6 @@
 	ds_seat_t *seat;
 
-	// TODO Determine which seat the event belongs to
-	seat = ds_display_first_seat(display);
+	/* Determine which seat the event belongs to */
+	seat = ds_display_seat_by_idev(display, event->pos_id);
 	if (seat == NULL)
 		return EOK;
@@ -388,4 +553,5 @@
 
 	seat->display = disp;
+	seat->id = disp->next_seat_id++;
 	list_append(&seat->lseats, &disp->seats);
 }
@@ -429,4 +595,61 @@
 
 	return list_get_instance(link, ds_seat_t, lseats);
+}
+
+/** Get default seat in display.
+ *
+ * @param disp Display
+ * @return First seat or @c NULL if there is none
+ */
+ds_seat_t *ds_display_default_seat(ds_display_t *disp)
+{
+	/* XXX Probably not the best solution */
+	return ds_display_first_seat(disp);
+}
+
+/** Find seat by ID.
+ *
+ * @param display Display
+ * @param id Seat ID
+ */
+ds_seat_t *ds_display_find_seat(ds_display_t *display, ds_seat_id_t id)
+{
+	ds_seat_t *seat;
+
+	seat = ds_display_first_seat(display);
+	while (seat != NULL) {
+		if (seat->id == id)
+			return seat;
+
+		seat = ds_display_next_seat(seat);
+	}
+
+	return NULL;
+}
+
+/** Get seat which owns the specified input device.
+ *
+ * @param disp Display
+ * @param idev_id Input device ID
+ * @return Seat which owns device with ID @a idev_id or @c NULL if not found
+ */
+ds_seat_t *ds_display_seat_by_idev(ds_display_t *disp, ds_idev_id_t idev_id)
+{
+	ds_idevcfg_t *idevcfg;
+
+	/*
+	 * Find input device configuration entry that maps this input device
+	 * to a seat.
+	 */
+	idevcfg = ds_display_first_idevcfg(disp);
+	while (idevcfg != NULL) {
+		if (idevcfg->svc_id == idev_id)
+			return idevcfg->seat;
+
+		idevcfg = ds_display_next_idevcfg(idevcfg);
+	}
+
+	/* If none was found, return the default seat */
+	return ds_display_default_seat(disp);
 }
 
@@ -493,7 +716,10 @@
 {
 	errno_t rc;
+	gfx_rect_t old_disp_rect;
 
 	assert(ddev->display == NULL);
 	assert(!link_used(&ddev->lddevs));
+
+	old_disp_rect = disp->rect;
 
 	ddev->display = disp;
@@ -507,14 +733,12 @@
 		/* Create cloning GC */
 		rc = ds_clonegc_create(ddev->gc, &disp->fbgc);
-		if (rc != EOK) {
-			// XXX Remove output
-			return ENOMEM;
-		}
+		if (rc != EOK)
+			goto error;
 
 		/* Allocate backbuffer */
 		rc = ds_display_alloc_backbuf(disp);
 		if (rc != EOK) {
-			// XXX Remove output
-			// XXX Delete clone GC
+			ds_clonegc_delete(disp->fbgc);
+			disp->fbgc = NULL;
 			goto error;
 		}
@@ -526,10 +750,9 @@
 	}
 
+	ds_display_update_max_rect(disp);
+
 	return EOK;
 error:
-	disp->rect.p0.x = 0;
-	disp->rect.p0.y = 0;
-	disp->rect.p1.x = 0;
-	disp->rect.p1.y = 0;
+	disp->rect = old_disp_rect;
 	list_remove(&ddev->lddevs);
 	return rc;
@@ -576,4 +799,58 @@
 }
 
+/** Add input device configuration entry to display.
+ *
+ * @param disp Display
+ * @param idevcfg Input device configuration
+ */
+void ds_display_add_idevcfg(ds_display_t *disp, ds_idevcfg_t *idevcfg)
+{
+	assert(idevcfg->display == NULL);
+	assert(!link_used(&idevcfg->ldispidcfgs));
+
+	idevcfg->display = disp;
+	list_append(&idevcfg->ldispidcfgs, &disp->idevcfgs);
+}
+
+/** Remove input device configuration entry from display.
+ *
+ * @param idevcfg Input device configuration entry
+ */
+void ds_display_remove_idevcfg(ds_idevcfg_t *idevcfg)
+{
+	list_remove(&idevcfg->ldispidcfgs);
+	idevcfg->display = NULL;
+}
+
+/** Get first input device configuration entry in display.
+ *
+ * @param disp Display
+ * @return First input device configuration entry or @c NULL if there is none
+ */
+ds_idevcfg_t *ds_display_first_idevcfg(ds_display_t *disp)
+{
+	link_t *link = list_first(&disp->idevcfgs);
+
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ds_idevcfg_t, ldispidcfgs);
+}
+
+/** Get next input device configuration entry in display.
+ *
+ * @param idevcfg Current input device configuration entry
+ * @return Next input device configuration entry or @c NULL if there is none
+ */
+ds_idevcfg_t *ds_display_next_idevcfg(ds_idevcfg_t *idevcfg)
+{
+	link_t *link = list_next(&idevcfg->ldispidcfgs, &idevcfg->display->idevcfgs);
+
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ds_idevcfg_t, ldispidcfgs);
+}
+
 /** Add cursor to display.
  *
@@ -598,4 +875,69 @@
 	list_remove(&cursor->ldisplay);
 	cursor->display = NULL;
+}
+
+/** Update display maximize rectangle.
+ *
+ * Recalculate the maximize rectangle (the rectangle used for maximized
+ * windows).
+ *
+ * @param display Display
+ */
+void ds_display_update_max_rect(ds_display_t *display)
+{
+	ds_window_t *wnd;
+	gfx_rect_t max_rect;
+	gfx_rect_t drect;
+
+	/* Start with the entire display */
+	max_rect = display->rect;
+
+	wnd = ds_display_first_window(display);
+	while (wnd != NULL) {
+		/* Should maximized windows avoid this window? */
+		if ((wnd->flags & wndf_avoid) != 0) {
+			/* Window bounding rectangle on display */
+			gfx_rect_translate(&wnd->dpos, &wnd->rect, &drect);
+
+			/* Crop maximized rectangle */
+			ds_display_crop_max_rect(&drect, &max_rect);
+		}
+
+		wnd = ds_display_next_window(wnd);
+	}
+
+	/* Update the maximize rectangle */
+	display->max_rect = max_rect;
+}
+
+/** Crop maximize rectangle.
+ *
+ * Use the avoid rectangle @a arect to crop off maximization rectangle
+ * @a mrect. If @a arect covers the top, bottom, left or right part
+ * of @a mrect, it will be cropped off. Otherwise there will be
+ * no effect.
+ *
+ * @param arect Avoid rectangle
+ * @param mrect Maximize rectangle to be modified
+ */
+void ds_display_crop_max_rect(gfx_rect_t *arect, gfx_rect_t *mrect)
+{
+	if (arect->p0.x == mrect->p0.x && arect->p0.y == mrect->p0.y &&
+	    arect->p1.x == mrect->p1.x) {
+		/* Cropp off top part */
+		mrect->p0.y = arect->p1.y;
+	} else if (arect->p0.x == mrect->p0.x && arect->p1.x == mrect->p1.x &&
+	    arect->p1.y == mrect->p1.y) {
+		/* Cropp off bottom part */
+		mrect->p1.y = arect->p0.y;
+	} else if (arect->p0.x == mrect->p0.x && arect->p0.y == mrect->p0.y &&
+	    arect->p1.y == mrect->p1.y) {
+		/* Cropp off left part */
+		mrect->p0.x = arect->p1.x;
+	} else if (arect->p0.y == mrect->p0.y && arect->p1.x == mrect->p1.x &&
+	    arect->p1.y == mrect->p1.y) {
+		/* Cropp off right part */
+		mrect->p1.x = arect->p0.x;
+	}
 }
 
Index: uspace/srv/hid/display/display.h
===================================================================
--- uspace/srv/hid/display/display.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/display.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -42,10 +42,14 @@
 #include <gfx/coord.h>
 #include <io/kbd_event.h>
+#include "types/display/cfgclient.h"
 #include "types/display/client.h"
 #include "types/display/cursor.h"
 #include "types/display/ddev.h"
 #include "types/display/display.h"
+#include "types/display/idev.h"
+#include "types/display/idevcfg.h"
 #include "types/display/ptd_event.h"
 #include "types/display/seat.h"
+#include "types/display/wmclient.h"
 
 extern errno_t ds_display_create(gfx_context_t *, ds_display_flags_t,
@@ -59,8 +63,16 @@
 extern ds_client_t *ds_display_first_client(ds_display_t *);
 extern ds_client_t *ds_display_next_client(ds_client_t *);
+extern void ds_display_add_wmclient(ds_display_t *, ds_wmclient_t *);
+extern void ds_display_remove_wmclient(ds_wmclient_t *);
+extern void ds_display_add_cfgclient(ds_display_t *, ds_cfgclient_t *);
+extern void ds_display_remove_cfgclient(ds_cfgclient_t *);
+extern ds_wmclient_t *ds_display_first_wmclient(ds_display_t *);
+extern ds_wmclient_t *ds_display_next_wmclient(ds_wmclient_t *);
 extern ds_window_t *ds_display_find_window(ds_display_t *, ds_wnd_id_t);
 extern ds_window_t *ds_display_window_by_pos(ds_display_t *, gfx_coord2_t *);
+extern void ds_display_enlist_window(ds_display_t *, ds_window_t *);
 extern void ds_display_add_window(ds_display_t *, ds_window_t *);
 extern void ds_display_remove_window(ds_window_t *);
+extern void ds_display_window_to_top(ds_window_t *);
 extern ds_window_t *ds_display_first_window(ds_display_t *);
 extern ds_window_t *ds_display_last_window(ds_display_t *);
@@ -73,10 +85,19 @@
 extern ds_seat_t *ds_display_first_seat(ds_display_t *);
 extern ds_seat_t *ds_display_next_seat(ds_seat_t *);
+extern ds_seat_t *ds_display_default_seat(ds_display_t *);
+extern ds_seat_t *ds_display_find_seat(ds_display_t *, ds_seat_id_t);
+extern ds_seat_t *ds_display_seat_by_idev(ds_display_t *, ds_idev_id_t);
 extern errno_t ds_display_add_ddev(ds_display_t *, ds_ddev_t *);
 extern void ds_display_remove_ddev(ds_ddev_t *);
 extern ds_ddev_t *ds_display_first_ddev(ds_display_t *);
 extern ds_ddev_t *ds_display_next_ddev(ds_ddev_t *);
+extern void ds_display_add_idevcfg(ds_display_t *, ds_idevcfg_t *);
+extern void ds_display_remove_idevcfg(ds_idevcfg_t *);
+extern ds_idevcfg_t *ds_display_first_idevcfg(ds_display_t *);
+extern ds_idevcfg_t *ds_display_next_idevcfg(ds_idevcfg_t *);
 extern void ds_display_add_cursor(ds_display_t *, ds_cursor_t *);
 extern void ds_display_remove_cursor(ds_cursor_t *);
+extern void ds_display_update_max_rect(ds_display_t *);
+extern void ds_display_crop_max_rect(gfx_rect_t *, gfx_rect_t *);
 extern gfx_context_t *ds_display_get_gc(ds_display_t *);
 extern errno_t ds_display_paint_bg(ds_display_t *, gfx_rect_t *);
Index: uspace/srv/hid/display/dsops.c
===================================================================
--- uspace/srv/hid/display/dsops.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/dsops.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -46,12 +46,18 @@
 static errno_t disp_window_create(void *, display_wnd_params_t *, sysarg_t *);
 static errno_t disp_window_destroy(void *, sysarg_t);
-static errno_t disp_window_move_req(void *, sysarg_t, gfx_coord2_t *);
+static errno_t disp_window_move_req(void *, sysarg_t, gfx_coord2_t *,
+    sysarg_t);
 static errno_t disp_window_move(void *, sysarg_t, gfx_coord2_t *);
 static errno_t disp_window_get_pos(void *, sysarg_t, gfx_coord2_t *);
+static errno_t disp_window_get_max_rect(void *, sysarg_t, gfx_rect_t *);
 static errno_t disp_window_resize_req(void *, sysarg_t,
-    display_wnd_rsztype_t, gfx_coord2_t *);
+    display_wnd_rsztype_t, gfx_coord2_t *, sysarg_t);
 static errno_t disp_window_resize(void *, sysarg_t, gfx_coord2_t *,
     gfx_rect_t *);
+static errno_t disp_window_minimize(void *, sysarg_t);
+static errno_t disp_window_maximize(void *, sysarg_t);
+static errno_t disp_window_unmaximize(void *, sysarg_t);
 static errno_t disp_window_set_cursor(void *, sysarg_t, display_stock_cursor_t);
+static errno_t disp_window_set_caption(void *, sysarg_t, const char *);
 static errno_t disp_get_event(void *, sysarg_t *, display_wnd_ev_t *);
 static errno_t disp_get_info(void *, display_info_t *);
@@ -63,7 +69,12 @@
 	.window_move = disp_window_move,
 	.window_get_pos = disp_window_get_pos,
+	.window_get_max_rect = disp_window_get_max_rect,
 	.window_resize_req = disp_window_resize_req,
 	.window_resize = disp_window_resize,
+	.window_minimize = disp_window_minimize,
+	.window_maximize = disp_window_maximize,
+	.window_unmaximize = disp_window_unmaximize,
 	.window_set_cursor = disp_window_set_cursor,
+	.window_set_caption = disp_window_set_caption,
 	.get_event = disp_get_event,
 	.get_info = disp_get_info
@@ -117,5 +128,5 @@
 
 static errno_t disp_window_move_req(void *arg, sysarg_t wnd_id,
-    gfx_coord2_t *pos)
+    gfx_coord2_t *pos, sysarg_t pos_id)
 {
 	ds_client_t *client = (ds_client_t *) arg;
@@ -131,5 +142,5 @@
 
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "disp_window_move_req()");
-	ds_window_move_req(wnd, pos);
+	ds_window_move_req(wnd, pos, pos_id);
 	ds_display_unlock(client->display);
 	return EOK;
@@ -175,6 +186,26 @@
 }
 
+static errno_t disp_window_get_max_rect(void *arg, sysarg_t wnd_id,
+    gfx_rect_t *rect)
+{
+	ds_client_t *client = (ds_client_t *) arg;
+	ds_window_t *wnd;
+
+	ds_display_lock(client->display);
+
+	wnd = ds_client_find_window(client, wnd_id);
+	if (wnd == NULL) {
+		ds_display_unlock(client->display);
+		return ENOENT;
+	}
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "disp_window_get_max_rect()");
+	ds_window_get_max_rect(wnd, rect);
+	ds_display_unlock(client->display);
+	return EOK;
+}
+
 static errno_t disp_window_resize_req(void *arg, sysarg_t wnd_id,
-    display_wnd_rsztype_t rsztype, gfx_coord2_t *pos)
+    display_wnd_rsztype_t rsztype, gfx_coord2_t *pos, sysarg_t pos_id)
 {
 	ds_client_t *client = (ds_client_t *) arg;
@@ -193,5 +224,5 @@
 
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "disp_window_resize_req()");
-	ds_window_resize_req(wnd, rsztype, pos);
+	ds_window_resize_req(wnd, rsztype, pos, pos_id);
 	ds_display_unlock(client->display);
 	return EOK;
@@ -219,4 +250,64 @@
 }
 
+static errno_t disp_window_minimize(void *arg, sysarg_t wnd_id)
+{
+	ds_client_t *client = (ds_client_t *) arg;
+	ds_window_t *wnd;
+	errno_t rc;
+
+	ds_display_lock(client->display);
+
+	wnd = ds_client_find_window(client, wnd_id);
+	if (wnd == NULL) {
+		ds_display_unlock(client->display);
+		return ENOENT;
+	}
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "disp_window_maximize()");
+	rc = ds_window_minimize(wnd);
+	ds_display_unlock(client->display);
+	return rc;
+}
+
+static errno_t disp_window_maximize(void *arg, sysarg_t wnd_id)
+{
+	ds_client_t *client = (ds_client_t *) arg;
+	ds_window_t *wnd;
+	errno_t rc;
+
+	ds_display_lock(client->display);
+
+	wnd = ds_client_find_window(client, wnd_id);
+	if (wnd == NULL) {
+		ds_display_unlock(client->display);
+		return ENOENT;
+	}
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "disp_window_maximize()");
+	rc = ds_window_maximize(wnd);
+	ds_display_unlock(client->display);
+	return rc;
+}
+
+static errno_t disp_window_unmaximize(void *arg, sysarg_t wnd_id)
+{
+	ds_client_t *client = (ds_client_t *) arg;
+	ds_window_t *wnd;
+	errno_t rc;
+
+	ds_display_lock(client->display);
+
+	wnd = ds_client_find_window(client, wnd_id);
+	if (wnd == NULL) {
+		ds_display_unlock(client->display);
+		return ENOENT;
+	}
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "disp_window_unmaximize()");
+	rc = ds_window_unmaximize(wnd);
+	ds_display_unlock(client->display);
+	return rc;
+}
+
 static errno_t disp_window_set_cursor(void *arg, sysarg_t wnd_id,
     display_stock_cursor_t cursor)
@@ -240,4 +331,25 @@
 }
 
+static errno_t disp_window_set_caption(void *arg, sysarg_t wnd_id,
+    const char *caption)
+{
+	ds_client_t *client = (ds_client_t *) arg;
+	ds_window_t *wnd;
+	errno_t rc;
+
+	ds_display_lock(client->display);
+
+	wnd = ds_client_find_window(client, wnd_id);
+	if (wnd == NULL) {
+		ds_display_unlock(client->display);
+		return ENOENT;
+	}
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "disp_window_set_caption()");
+	rc = ds_window_set_caption(wnd, caption);
+	ds_display_unlock(client->display);
+	return rc;
+}
+
 static errno_t disp_get_event(void *arg, sysarg_t *wnd_id,
     display_wnd_ev_t *event)
@@ -247,5 +359,5 @@
 	errno_t rc;
 
-	log_msg(LOG_DEFAULT, LVL_DEBUG, "disp_window_get_event()");
+	log_msg(LOG_DEFAULT, LVL_DEBUG2, "disp_window_get_event()");
 
 	ds_display_lock(client->display);
Index: uspace/srv/hid/display/idevcfg.c
===================================================================
--- uspace/srv/hid/display/idevcfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/srv/hid/display/idevcfg.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,83 @@
+/*
+ * 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 display
+ * @{
+ */
+/**
+ * @file Display server input device configuration
+ */
+
+#include <adt/list.h>
+#include <errno.h>
+#include <io/log.h>
+#include <loc.h>
+#include <stdlib.h>
+#include "display.h"
+#include "idevcfg.h"
+#include "seat.h"
+
+/** Create input device configuration entry.
+ *
+ * @param display Parent display
+ * @param svc_id Device service ID
+ * @param seat Seat to which device is assigned
+ * @param ridevcfg Place to store pointer to new input device configuration
+ *                 entry
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ds_idevcfg_create(ds_display_t *display, service_id_t svc_id,
+    ds_seat_t *seat, ds_idevcfg_t **ridevcfg)
+{
+	ds_idevcfg_t *idevcfg;
+
+	idevcfg = calloc(1, sizeof(ds_idevcfg_t));
+	if (idevcfg == NULL)
+		return ENOMEM;
+
+	idevcfg->svc_id = svc_id;
+	ds_seat_add_idevcfg(seat, idevcfg);
+
+	ds_display_add_idevcfg(display, idevcfg);
+	*ridevcfg = idevcfg;
+	return EOK;
+}
+
+/** Destroy input device configuration entry.
+ *
+ * @param ddev Display device
+ */
+void ds_idevcfg_destroy(ds_idevcfg_t *idevcfg)
+{
+	ds_display_remove_idevcfg(idevcfg);
+	ds_seat_remove_idevcfg(idevcfg);
+	free(idevcfg);
+}
+
+/** @}
+ */
Index: uspace/srv/hid/display/idevcfg.h
===================================================================
--- uspace/srv/hid/display/idevcfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/srv/hid/display/idevcfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,52 @@
+/*
+ * 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 display
+ * @{
+ */
+/**
+ * @file Display server input device configuration
+ */
+
+#ifndef IDEVCFG_H
+#define IDEVCFG_H
+
+#include <errno.h>
+#include <loc.h>
+#include "types/display/display.h"
+#include "types/display/idevcfg.h"
+#include "types/display/seat.h"
+
+extern errno_t ds_idevcfg_create(ds_display_t *, service_id_t, ds_seat_t *,
+    ds_idevcfg_t **);
+extern void ds_idevcfg_destroy(ds_idevcfg_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/hid/display/input.c
===================================================================
--- uspace/srv/hid/display/input.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/input.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -45,8 +45,11 @@
 static errno_t ds_input_ev_active(input_t *);
 static errno_t ds_input_ev_deactive(input_t *);
-static errno_t ds_input_ev_key(input_t *, kbd_event_type_t, keycode_t, keymod_t, char32_t);
-static errno_t ds_input_ev_move(input_t *, int, int);
-static errno_t ds_input_ev_abs_move(input_t *, unsigned, unsigned, unsigned, unsigned);
-static errno_t ds_input_ev_button(input_t *, int, int);
+static errno_t ds_input_ev_key(input_t *, unsigned, kbd_event_type_t, keycode_t,
+    keymod_t, char32_t);
+static errno_t ds_input_ev_move(input_t *, unsigned, int, int);
+static errno_t ds_input_ev_abs_move(input_t *, unsigned, unsigned, unsigned,
+    unsigned, unsigned);
+static errno_t ds_input_ev_button(input_t *, unsigned, int, int);
+static errno_t ds_input_ev_dclick(input_t *, unsigned, int);
 
 static input_ev_ops_t ds_input_ev_ops = {
@@ -56,5 +59,6 @@
 	.move = ds_input_ev_move,
 	.abs_move = ds_input_ev_abs_move,
-	.button = ds_input_ev_button
+	.button = ds_input_ev_button,
+	.dclick = ds_input_ev_dclick
 };
 
@@ -69,6 +73,6 @@
 }
 
-static errno_t ds_input_ev_key(input_t *input, kbd_event_type_t type,
-    keycode_t key, keymod_t mods, char32_t c)
+static errno_t ds_input_ev_key(input_t *input, unsigned kbd_id,
+    kbd_event_type_t type, keycode_t key, keymod_t mods, char32_t c)
 {
 	ds_display_t *disp = (ds_display_t *) input->user;
@@ -76,4 +80,5 @@
 	errno_t rc;
 
+	event.kbd_id = kbd_id;
 	event.type = type;
 	event.key = key;
@@ -87,10 +92,11 @@
 }
 
-static errno_t ds_input_ev_move(input_t *input, int dx, int dy)
-{
-	ds_display_t *disp = (ds_display_t *) input->user;
-	ptd_event_t event;
-	errno_t rc;
-
+static errno_t ds_input_ev_move(input_t *input, unsigned pos_id, int dx, int dy)
+{
+	ds_display_t *disp = (ds_display_t *) input->user;
+	ptd_event_t event;
+	errno_t rc;
+
+	event.pos_id = pos_id;
 	event.type = PTD_MOVE;
 	event.dmove.x = dx;
@@ -103,11 +109,12 @@
 }
 
-static errno_t ds_input_ev_abs_move(input_t *input, unsigned x, unsigned y,
-    unsigned max_x, unsigned max_y)
-{
-	ds_display_t *disp = (ds_display_t *) input->user;
-	ptd_event_t event;
-	errno_t rc;
-
+static errno_t ds_input_ev_abs_move(input_t *input, unsigned pos_id, unsigned x,
+    unsigned y, unsigned max_x, unsigned max_y)
+{
+	ds_display_t *disp = (ds_display_t *) input->user;
+	ptd_event_t event;
+	errno_t rc;
+
+	event.pos_id = pos_id;
 	event.type = PTD_ABS_MOVE;
 	event.apos.x = x;
@@ -124,11 +131,31 @@
 }
 
-static errno_t ds_input_ev_button(input_t *input, int bnum, int bpress)
-{
-	ds_display_t *disp = (ds_display_t *) input->user;
-	ptd_event_t event;
-	errno_t rc;
-
+static errno_t ds_input_ev_button(input_t *input, unsigned pos_id, int bnum,
+    int bpress)
+{
+	ds_display_t *disp = (ds_display_t *) input->user;
+	ptd_event_t event;
+	errno_t rc;
+
+	event.pos_id = pos_id;
 	event.type = bpress ? PTD_PRESS : PTD_RELEASE;
+	event.btn_num = bnum;
+	event.dmove.x = 0;
+	event.dmove.y = 0;
+
+	ds_display_lock(disp);
+	rc = ds_display_post_ptd_event(disp, &event);
+	ds_display_unlock(disp);
+	return rc;
+}
+
+static errno_t ds_input_ev_dclick(input_t *input, unsigned pos_id, int bnum)
+{
+	ds_display_t *disp = (ds_display_t *) input->user;
+	ptd_event_t event;
+	errno_t rc;
+
+	event.pos_id = pos_id;
+	event.type = PTD_DCLICK;
 	event.btn_num = bnum;
 	event.dmove.x = 0;
Index: uspace/srv/hid/display/main.c
===================================================================
--- uspace/srv/hid/display/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -36,4 +36,5 @@
 #include <async.h>
 #include <disp_srv.h>
+#include <dispcfg_srv.h>
 #include <errno.h>
 #include <gfx/context.h>
@@ -47,4 +48,7 @@
 #include <stdio.h>
 #include <task.h>
+#include <wndmgt_srv.h>
+#include "cfgclient.h"
+#include "cfgops.h"
 #include "client.h"
 #include "display.h"
@@ -55,7 +59,14 @@
 #include "seat.h"
 #include "window.h"
+#include "wmclient.h"
+#include "wmops.h"
 
 static void display_client_conn(ipc_call_t *, void *);
 static void display_client_ev_pending(void *);
+static void display_wmclient_ev_pending(void *);
+static void display_cfgclient_ev_pending(void *);
+static void display_gc_conn(ipc_call_t *, void *);
+static void display_wndmgt_conn(ipc_call_t *, void *);
+static void display_dispcfg_conn(ipc_call_t *, void *);
 
 #ifdef CONFIG_DISP_DOUBLE_BUF
@@ -76,4 +87,12 @@
 };
 
+static ds_wmclient_cb_t display_wmclient_cb = {
+	.ev_pending = display_wmclient_ev_pending
+};
+
+static ds_cfgclient_cb_t display_cfgclient_cb = {
+	.ev_pending = display_cfgclient_ev_pending
+};
+
 static void display_client_ev_pending(void *arg)
 {
@@ -81,4 +100,18 @@
 
 	display_srv_ev_pending(srv);
+}
+
+static void display_wmclient_ev_pending(void *arg)
+{
+	wndmgt_srv_t *srv = (wndmgt_srv_t *) arg;
+
+	wndmgt_srv_ev_pending(srv);
+}
+
+static void display_cfgclient_ev_pending(void *arg)
+{
+	dispcfg_srv_t *srv = (dispcfg_srv_t *) arg;
+
+	dispcfg_srv_ev_pending(srv);
 }
 
@@ -90,4 +123,10 @@
 	ds_output_t *output = NULL;
 	gfx_context_t *gc = NULL;
+	port_id_t disp_port;
+	port_id_t gc_port;
+	port_id_t wm_port;
+	port_id_t dc_port;
+	loc_srv_t *srv = NULL;
+	service_id_t sid = 0;
 	errno_t rc;
 
@@ -98,5 +137,5 @@
 		goto error;
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	if (rc != EOK)
 		goto error;
@@ -115,7 +154,24 @@
 		goto error;
 
-	async_set_fallback_port_handler(display_client_conn, disp);
-
-	rc = loc_server_register(NAME);
+	rc = async_create_port(INTERFACE_DISPLAY, display_client_conn, disp,
+	    &disp_port);
+	if (rc != EOK)
+		goto error;
+
+	rc = async_create_port(INTERFACE_GC, display_gc_conn, disp, &gc_port);
+	if (rc != EOK)
+		goto error;
+
+	rc = async_create_port(INTERFACE_WNDMGT, display_wndmgt_conn, disp,
+	    &wm_port);
+	if (rc != EOK)
+		goto error;
+
+	rc = async_create_port(INTERFACE_DISPCFG, display_dispcfg_conn, disp,
+	    &dc_port);
+	if (rc != EOK)
+		goto error;
+
+	rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering server: %s.", str_error(rc));
@@ -124,6 +180,5 @@
 	}
 
-	service_id_t sid;
-	rc = loc_service_register(SERVICE_NAME_DISPLAY, &sid);
+	rc = loc_service_register(srv, SERVICE_NAME_DISPLAY, &sid);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering service: %s.", str_error(rc));
@@ -135,4 +190,12 @@
 	return EOK;
 error:
+	if (sid != 0)
+		loc_service_unregister(srv, sid);
+	if (srv != NULL)
+		loc_server_unregister(srv);
+	// XXX destroy disp_port
+	// XXX destroy gc_port
+	// XXX destroy wm_port
+	// XXX destroy dc_port
 #if 0
 	if (disp->input != NULL)
@@ -154,10 +217,7 @@
 {
 	display_srv_t srv;
-	sysarg_t wnd_id;
 	sysarg_t svc_id;
 	ds_client_t *client = NULL;
-	ds_window_t *wnd;
 	ds_display_t *disp = (ds_display_t *) arg;
-	gfx_context_t *gc;
 	errno_t rc;
 
@@ -166,13 +226,11 @@
 	    ipc_get_arg4(icall));
 
-	(void) icall;
-	(void) arg;
-
 	svc_id = ipc_get_arg2(icall);
-	wnd_id = ipc_get_arg3(icall);
 
 	if (svc_id != 0) {
 		/* Create client object */
+		ds_display_lock(disp);
 		rc = ds_client_create(disp, &display_client_cb, &srv, &client);
+		ds_display_unlock(disp);
 		if (rc != EOK) {
 			async_answer_0(icall, ENOMEM);
@@ -191,20 +249,95 @@
 		ds_client_destroy(client);
 		ds_display_unlock(disp);
-	} else {
-		/* Window GC connection */
-
-		wnd = ds_display_find_window(disp, wnd_id);
-		if (wnd == NULL) {
-			async_answer_0(icall, ENOENT);
-			return;
-		}
-
-		/*
-		 * XXX We need a way to make sure that the connection does
-		 * not stay active after the window had been destroyed
-		 */
-		gc = ds_window_get_ctx(wnd);
-		gc_conn(icall, gc);
-	}
+	}
+}
+
+/** Handle GC connection to display server */
+static void display_gc_conn(ipc_call_t *icall, void *arg)
+{
+	sysarg_t wnd_id;
+	ds_window_t *wnd;
+	ds_display_t *disp = (ds_display_t *) arg;
+	gfx_context_t *gc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "display_gc_conn arg1=%zu arg2=%zu arg3=%zu arg4=%zu.",
+	    ipc_get_arg1(icall), ipc_get_arg2(icall), ipc_get_arg3(icall),
+	    ipc_get_arg4(icall));
+
+	wnd_id = ipc_get_arg3(icall);
+
+	/* Window GC connection */
+
+	wnd = ds_display_find_window(disp, wnd_id);
+	if (wnd == NULL) {
+		async_answer_0(icall, ENOENT);
+		return;
+	}
+
+	/*
+	 * XXX We need a way to make sure that the connection does
+	 * not stay active after the window had been destroyed
+	 */
+	gc = ds_window_get_ctx(wnd);
+	gc_conn(icall, gc);
+}
+
+/** Handle window management connection to display server */
+static void display_wndmgt_conn(ipc_call_t *icall, void *arg)
+{
+	ds_display_t *disp = (ds_display_t *) arg;
+	errno_t rc;
+	wndmgt_srv_t srv;
+	ds_wmclient_t *wmclient = NULL;
+
+	/* Create WM client object */
+	ds_display_lock(disp);
+	rc = ds_wmclient_create(disp, &display_wmclient_cb, &srv, &wmclient);
+	ds_display_unlock(disp);
+	if (rc != EOK) {
+		async_answer_0(icall, ENOMEM);
+		return;
+	}
+
+	/* Set up protocol structure */
+	wndmgt_srv_initialize(&srv);
+	srv.ops = &wndmgt_srv_ops;
+	srv.arg = wmclient;
+
+	/* Handle connection */
+	wndmgt_conn(icall, &srv);
+
+	ds_display_lock(disp);
+	ds_wmclient_destroy(wmclient);
+	ds_display_unlock(disp);
+}
+
+/** Handle configuration connection to display server */
+static void display_dispcfg_conn(ipc_call_t *icall, void *arg)
+{
+	ds_display_t *disp = (ds_display_t *) arg;
+	errno_t rc;
+	dispcfg_srv_t srv;
+	ds_cfgclient_t *cfgclient = NULL;
+
+	/* Create CFG client object */
+	ds_display_lock(disp);
+	rc = ds_cfgclient_create(disp, &display_cfgclient_cb, &srv, &cfgclient);
+	ds_display_unlock(disp);
+	if (rc != EOK) {
+		async_answer_0(icall, ENOMEM);
+		return;
+	}
+
+	/* Set up protocol structure */
+	dispcfg_srv_initialize(&srv);
+	srv.ops = &dispcfg_srv_ops;
+	srv.arg = cfgclient;
+
+	/* Handle connection */
+	dispcfg_conn(icall, &srv);
+
+	ds_display_lock(disp);
+	ds_cfgclient_destroy(cfgclient);
+	ds_display_unlock(disp);
 }
 
Index: uspace/srv/hid/display/meson.build
===================================================================
--- uspace/srv/hid/display/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 #
-# Copyright (c) 2019 Jiri Svoboda
+# Copyright (c) 2023 Jiri Svoboda
 # All rights reserved.
 #
@@ -27,7 +27,9 @@
 #
 
-deps = [ 'ipcgfx', 'memgfx', 'display', 'ddev' ]
+deps = [ 'ipcgfx', 'memgfx', 'display', 'ddev', 'dispcfg', 'wndmgt', 'input' ]
 
 src = files(
+	'cfgclient.c',
+	'cfgops.c',
 	'client.c',
 	'clonegc.c',
@@ -37,4 +39,5 @@
 	'display.c',
 	'dsops.c',
+	'idevcfg.c',
 	'input.c',
 	'main.c',
@@ -42,7 +45,10 @@
 	'seat.c',
 	'window.c',
+	'wmclient.c',
+	'wmops.c',
 )
 
 test_src = files(
+	'cfgclient.c',
 	'client.c',
 	'clonegc.c',
@@ -51,6 +57,9 @@
 	'ddev.c',
 	'display.c',
+	'idevcfg.c',
 	'seat.c',
 	'window.c',
+	'wmclient.c',
+	'test/cfgclient.c',
 	'test/client.c',
 	'test/clonegc.c',
@@ -60,3 +69,4 @@
 	'test/seat.c',
 	'test/window.c',
+	'test/wmclient.c',
 )
Index: uspace/srv/hid/display/seat.c
===================================================================
--- uspace/srv/hid/display/seat.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/seat.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,7 +39,9 @@
 #include <gfx/render.h>
 #include <stdlib.h>
+#include <str.h>
 #include "client.h"
 #include "cursor.h"
 #include "display.h"
+#include "idevcfg.h"
 #include "seat.h"
 #include "window.h"
@@ -51,10 +53,20 @@
  *
  * @param display Parent display
+ * @param name Seat name
  * @param rseat Place to store pointer to new seat.
  * @return EOK on success, ENOMEM if out of memory
  */
-errno_t ds_seat_create(ds_display_t *display, ds_seat_t **rseat)
+errno_t ds_seat_create(ds_display_t *display, const char *name,
+    ds_seat_t **rseat)
 {
 	ds_seat_t *seat;
+	ds_seat_t *s0;
+
+	s0 = ds_display_first_seat(display);
+	while (s0 != NULL) {
+		if (str_cmp(s0->name, name) == 0)
+			return EEXIST;
+		s0 = ds_display_next_seat(s0);
+	}
 
 	seat = calloc(1, sizeof(ds_seat_t));
@@ -62,4 +74,12 @@
 		return ENOMEM;
 
+	seat->name = str_dup(name);
+	if (seat->name == NULL) {
+		free(seat);
+		return ENOMEM;
+	}
+
+	list_initialize(&seat->idevcfgs);
+
 	ds_display_add_seat(display, seat);
 	seat->pntpos.x = 0;
@@ -68,4 +88,5 @@
 	seat->client_cursor = display->cursor[dcurs_arrow];
 	seat->wm_cursor = NULL;
+	seat->focus = ds_display_first_window(display);
 
 	*rseat = seat;
@@ -79,5 +100,20 @@
 void ds_seat_destroy(ds_seat_t *seat)
 {
+	ds_idevcfg_t *idevcfg;
+
+	/* Remove all input device configuration entries pointing to this seat */
+	idevcfg = ds_seat_first_idevcfg(seat);
+	while (idevcfg != NULL) {
+		ds_idevcfg_destroy(idevcfg);
+		idevcfg = ds_seat_first_idevcfg(seat);
+	}
+
+	/* Remove this seat's focus */
+	if (seat->focus != NULL)
+		ds_window_post_unfocus_event(seat->focus);
+
 	ds_display_remove_seat(seat);
+
+	free(seat->name);
 	free(seat);
 }
@@ -90,7 +126,15 @@
 void ds_seat_set_focus(ds_seat_t *seat, ds_window_t *wnd)
 {
+	errno_t rc;
+
 	if (wnd == seat->focus) {
 		/* Focus is not changing */
 		return;
+	}
+
+	if (wnd != NULL) {
+		rc = ds_window_unminimize(wnd);
+		if (rc != EOK)
+			return;
 	}
 
@@ -130,23 +174,14 @@
 /** Evacuate seat references to window.
  *
- * If seat's focus is @a wnd, it will be set to a different window.
+ * If seat's focus is @a wnd, it will be set to NULL.
  * If seat's popup window is @a wnd, it will be set to NULL.
  *
  * @param seat Seat
- * @param wnd Window to evacuate focus from
+ * @param wnd Window to evacuate references from
  */
 void ds_seat_evac_wnd_refs(ds_seat_t *seat, ds_window_t *wnd)
 {
-	ds_window_t *nwnd;
-
-	if (seat->focus == wnd) {
-		nwnd = ds_display_prev_window(wnd);
-		if (nwnd == NULL)
-			nwnd = ds_display_last_window(wnd->display);
-		if (nwnd == wnd)
-			nwnd = NULL;
-
-		ds_seat_set_focus(seat, nwnd);
-	}
+	if (seat->focus == wnd)
+		ds_seat_set_focus(seat, NULL);
 
 	if (seat->popup == wnd)
@@ -154,4 +189,30 @@
 }
 
+/** Unfocus window.
+ *
+ * If seat's focus is @a wnd, it will be set to a different window
+ * that is not minimized, preferably not a system window.
+ *
+ * @param seat Seat
+ * @param wnd Window to remove focus from
+ */
+void ds_seat_unfocus_wnd(ds_seat_t *seat, ds_window_t *wnd)
+{
+	ds_window_t *nwnd;
+
+	if (seat->focus != wnd)
+		return;
+
+	/* Find alternate window that is neither system nor minimized */
+	nwnd = ds_window_find_prev(wnd, ~(wndf_minimized | wndf_system));
+
+	if (nwnd == NULL) {
+		/* Find alternate window that is not minimized */
+		nwnd = ds_window_find_prev(wnd, ~wndf_minimized);
+	}
+
+	ds_seat_set_focus(seat, nwnd);
+}
+
 /** Switch focus to another window.
  *
@@ -163,12 +224,13 @@
 	ds_window_t *nwnd;
 
-	if (seat->focus != NULL)
-		nwnd = ds_display_prev_window(seat->focus);
-	else
-		nwnd = NULL;
-
-	if (nwnd == NULL)
-		nwnd = ds_display_last_window(seat->display);
-
+	if (seat->focus != NULL) {
+		/* Find alternate window that is not a system window */
+		nwnd = ds_window_find_next(seat->focus, ~wndf_system);
+	} else {
+		/* Currently no focus. Focus topmost window. */
+		nwnd = ds_display_first_window(seat->display);
+	}
+
+	/* Only switch focus if there is another window */
 	if (nwnd != NULL)
 		ds_seat_set_focus(seat, nwnd);
@@ -362,8 +424,21 @@
 	}
 
-	if (event->type == PTD_PRESS || event->type == PTD_RELEASE) {
-		pevent.pos_id = 0;
-		pevent.type = (event->type == PTD_PRESS) ?
-		    POS_PRESS : POS_RELEASE;
+	if (event->type == PTD_PRESS || event->type == PTD_RELEASE ||
+	    event->type == PTD_DCLICK) {
+		pevent.pos_id = event->pos_id;
+		switch (event->type) {
+		case PTD_PRESS:
+			pevent.type = POS_PRESS;
+			break;
+		case PTD_RELEASE:
+			pevent.type = POS_RELEASE;
+			break;
+		case PTD_DCLICK:
+			pevent.type = POS_DCLICK;
+			break;
+		default:
+			assert(false);
+		}
+
 		pevent.btn_num = event->btn_num;
 		pevent.hpos = seat->pntpos.x;
@@ -382,5 +457,5 @@
 		seat->pntpos = npos;
 
-		pevent.pos_id = 0;
+		pevent.pos_id = event->pos_id;
 		pevent.type = POS_UPDATE;
 		pevent.btn_num = 0;
@@ -410,5 +485,5 @@
 		seat->pntpos = npos;
 
-		pevent.pos_id = 0;
+		pevent.pos_id = event->pos_id;
 		pevent.type = POS_UPDATE;
 		pevent.btn_num = 0;
@@ -440,12 +515,6 @@
 	wnd = ds_display_window_by_pos(seat->display, &seat->pntpos);
 
-	/* Click outside popup window */
-	if (event->type == POS_PRESS && wnd != seat->popup) {
-		/* Close popup window */
-		ds_seat_set_popup(seat, NULL);
-	}
-
 	/* Deliver event to popup window. */
-	if (seat->popup != NULL) {
+	if (seat->popup != NULL && event->type != POS_PRESS) {
 		rc = ds_window_post_pos_event(seat->popup, event);
 		if (rc != EOK)
@@ -471,5 +540,5 @@
 		 * to the same window above.
 		 */
-		if (wnd != seat->popup) {
+		if (wnd != seat->popup || event->type == POS_PRESS) {
 			rc = ds_window_post_pos_event(wnd, event);
 			if (rc != EOK)
@@ -481,4 +550,10 @@
 	}
 
+	/* Click outside popup window */
+	if (event->type == POS_PRESS && wnd != seat->popup) {
+		/* Close popup window */
+		ds_seat_set_popup(seat, NULL);
+	}
+
 	return EOK;
 }
@@ -497,4 +572,58 @@
 }
 
+/** Add input device configuration entry to seat.
+ *
+ * @param seat Seat
+ * @param idevcfg Input device configuration
+ */
+void ds_seat_add_idevcfg(ds_seat_t *seat, ds_idevcfg_t *idevcfg)
+{
+	assert(idevcfg->seat == NULL);
+	assert(!link_used(&idevcfg->lseatidcfgs));
+
+	idevcfg->seat = seat;
+	list_append(&idevcfg->lseatidcfgs, &seat->idevcfgs);
+}
+
+/** Remove input device configuration entry from seat.
+ *
+ * @param idevcfg Input device configuration entry
+ */
+void ds_seat_remove_idevcfg(ds_idevcfg_t *idevcfg)
+{
+	list_remove(&idevcfg->lseatidcfgs);
+	idevcfg->seat = NULL;
+}
+
+/** Get first input device configuration entry in seat.
+ *
+ * @param disp Display
+ * @return First input device configuration entry or @c NULL if there is none
+ */
+ds_idevcfg_t *ds_seat_first_idevcfg(ds_seat_t *seat)
+{
+	link_t *link = list_first(&seat->idevcfgs);
+
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ds_idevcfg_t, lseatidcfgs);
+}
+
+/** Get next input device configuration entry in seat.
+ *
+ * @param idevcfg Current input device configuration entry
+ * @return Next input device configuration entry or @c NULL if there is none
+ */
+ds_idevcfg_t *ds_seat_next_idevcfg(ds_idevcfg_t *idevcfg)
+{
+	link_t *link = list_next(&idevcfg->lseatidcfgs, &idevcfg->seat->idevcfgs);
+
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ds_idevcfg_t, lseatidcfgs);
+}
+
 /** @}
  */
Index: uspace/srv/hid/display/seat.h
===================================================================
--- uspace/srv/hid/display/seat.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/seat.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -46,9 +46,10 @@
 #include "types/display/window.h"
 
-extern errno_t ds_seat_create(ds_display_t *, ds_seat_t **);
+extern errno_t ds_seat_create(ds_display_t *, const char *, ds_seat_t **);
 extern void ds_seat_destroy(ds_seat_t *);
 extern void ds_seat_set_focus(ds_seat_t *, ds_window_t *);
 extern void ds_seat_set_popup(ds_seat_t *, ds_window_t *);
 extern void ds_seat_evac_wnd_refs(ds_seat_t *, ds_window_t *);
+extern void ds_seat_unfocus_wnd(ds_seat_t *, ds_window_t *);
 extern void ds_seat_switch_focus(ds_seat_t *);
 extern errno_t ds_seat_post_kbd_event(ds_seat_t *, kbd_event_t *);
@@ -57,4 +58,8 @@
 extern void ds_seat_set_wm_cursor(ds_seat_t *, ds_cursor_t *);
 extern errno_t ds_seat_paint_pointer(ds_seat_t *, gfx_rect_t *);
+extern void ds_seat_add_idevcfg(ds_seat_t *, ds_idevcfg_t *);
+extern void ds_seat_remove_idevcfg(ds_idevcfg_t *);
+extern ds_idevcfg_t *ds_seat_first_idevcfg(ds_seat_t *);
+extern ds_idevcfg_t *ds_seat_next_idevcfg(ds_idevcfg_t *);
 
 #endif
Index: uspace/srv/hid/display/test/cfgclient.c
===================================================================
--- uspace/srv/hid/display/test/cfgclient.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/srv/hid/display/test/cfgclient.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,181 @@
+/*
+ * 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.
+ */
+
+#include <disp_srv.h>
+#include <errno.h>
+#include <pcut/pcut.h>
+#include <str.h>
+
+#include "../display.h"
+#include "../cfgclient.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(cfgclient);
+
+static void test_ds_dcev_pending(void *);
+
+static ds_cfgclient_cb_t test_ds_cfgclient_cb = {
+	.ev_pending = test_ds_dcev_pending
+};
+
+static void test_ds_dcev_pending(void *arg)
+{
+	bool *called_cb = (bool *) arg;
+	*called_cb = true;
+}
+
+/** WM client creation and destruction. */
+PCUT_TEST(create_destroy)
+{
+	ds_display_t *disp;
+	ds_cfgclient_t *cfgclient;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_cfgclient_create(disp, &test_ds_cfgclient_cb, NULL, &cfgclient);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ds_cfgclient_destroy(cfgclient);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_cfgclient_get_event(), ds_cfgclient_post_seat_added_event(). */
+PCUT_TEST(client_get_post_seat_added_event)
+{
+	ds_display_t *disp;
+	ds_cfgclient_t *cfgclient;
+	dispcfg_ev_t revent;
+	bool called_cb = NULL;
+	sysarg_t seat_id;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_cfgclient_create(disp, &test_ds_cfgclient_cb, &called_cb,
+	    &cfgclient);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	called_cb = false;
+	seat_id = 42;
+
+	rc = ds_cfgclient_get_event(cfgclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
+
+	rc = ds_cfgclient_post_seat_added_event(cfgclient, seat_id);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_TRUE(called_cb);
+
+	rc = ds_cfgclient_get_event(cfgclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS(seat_id, revent.seat_id);
+	PCUT_ASSERT_EQUALS(dcev_seat_added, revent.etype);
+
+	rc = ds_cfgclient_get_event(cfgclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
+
+	ds_cfgclient_destroy(cfgclient);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_cfgclient_get_event(), ds_cfgclient_post_seat_removed_event(). */
+PCUT_TEST(client_get_post_seat_removed_event)
+{
+	ds_display_t *disp;
+	ds_cfgclient_t *cfgclient;
+	dispcfg_ev_t revent;
+	bool called_cb = NULL;
+	sysarg_t seat_id;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_cfgclient_create(disp, &test_ds_cfgclient_cb, &called_cb,
+	    &cfgclient);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	called_cb = false;
+	seat_id = 42;
+
+	rc = ds_cfgclient_get_event(cfgclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
+
+	rc = ds_cfgclient_post_seat_removed_event(cfgclient, seat_id);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_TRUE(called_cb);
+
+	rc = ds_cfgclient_get_event(cfgclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS(seat_id, revent.seat_id);
+	PCUT_ASSERT_EQUALS(dcev_seat_removed, revent.etype);
+
+	rc = ds_cfgclient_get_event(cfgclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
+
+	ds_cfgclient_destroy(cfgclient);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_cfgclient_purge_events() */
+PCUT_TEST(purge_events)
+{
+	ds_display_t *disp;
+	ds_cfgclient_t *cfgclient;
+	dispcfg_ev_t revent;
+	bool called_cb = NULL;
+	sysarg_t seat_id;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_cfgclient_create(disp, &test_ds_cfgclient_cb, &called_cb,
+	    &cfgclient);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Post window added event */
+	seat_id = 42;
+	rc = ds_cfgclient_post_seat_added_event(cfgclient, seat_id);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Purge it */
+	ds_cfgclient_purge_events(cfgclient);
+
+	/* The queue should be empty now */
+	rc = ds_cfgclient_get_event(cfgclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
+
+	ds_cfgclient_destroy(cfgclient);
+	ds_display_destroy(disp);
+}
+
+PCUT_EXPORT(cfgclient);
Index: uspace/srv/hid/display/test/client.c
===================================================================
--- uspace/srv/hid/display/test/client.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/test/client.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -93,5 +93,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -144,5 +144,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -192,5 +192,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -240,4 +240,5 @@
 	display_wnd_params_t params;
 	ds_window_t *rwindow;
+	display_wnd_focus_ev_t efocus;
 	display_wnd_ev_t revent;
 	bool called_cb = NULL;
@@ -250,5 +251,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -271,5 +272,7 @@
 	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
 
-	rc = ds_client_post_focus_event(client, wnd);
+	efocus.nfocus = 42;
+
+	rc = ds_client_post_focus_event(client, wnd, &efocus);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_TRUE(called_cb);
@@ -279,4 +282,5 @@
 	PCUT_ASSERT_EQUALS(wnd, rwindow);
 	PCUT_ASSERT_EQUALS(wev_focus, revent.etype);
+	PCUT_ASSERT_INT_EQUALS(efocus.nfocus, revent.ev.focus.nfocus);
 
 	rc = ds_client_get_event(client, &rwindow, &revent);
@@ -309,5 +313,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -377,5 +381,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -445,5 +449,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -504,4 +508,5 @@
 	display_wnd_params_t params;
 	ds_window_t *rwindow;
+	display_wnd_unfocus_ev_t eunfocus;
 	display_wnd_ev_t revent;
 	bool called_cb = NULL;
@@ -514,5 +519,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -535,5 +540,7 @@
 	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
 
-	rc = ds_client_post_unfocus_event(client, wnd);
+	eunfocus.nfocus = 42;
+
+	rc = ds_client_post_unfocus_event(client, wnd, &eunfocus);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_TRUE(called_cb);
@@ -543,4 +550,5 @@
 	PCUT_ASSERT_EQUALS(wnd, rwindow);
 	PCUT_ASSERT_EQUALS(wev_unfocus, revent.etype);
+	PCUT_ASSERT_INT_EQUALS(eunfocus.nfocus, revent.ev.unfocus.nfocus);
 
 	rc = ds_client_get_event(client, &rwindow, &revent);
@@ -572,5 +580,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -618,5 +626,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
Index: uspace/srv/hid/display/test/cursor.c
===================================================================
--- uspace/srv/hid/display/test/cursor.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/test/cursor.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -117,4 +117,5 @@
 	PCUT_ASSERT_TRUE(resp.render_called);
 
+	ds_ddev_close(ddev);
 	ds_cursor_destroy(cursor);
 	ds_display_destroy(disp);
@@ -167,4 +168,5 @@
 	PCUT_ASSERT_FALSE(resp.render_called);
 
+	ds_ddev_close(ddev);
 	ds_cursor_destroy(cursor);
 	ds_display_destroy(disp);
Index: uspace/srv/hid/display/test/display.c
===================================================================
--- uspace/srv/hid/display/test/display.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/test/display.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -32,8 +32,11 @@
 #include <str.h>
 
+#include "../cfgclient.h"
 #include "../client.h"
 #include "../display.h"
+#include "../idevcfg.h"
 #include "../seat.h"
 #include "../window.h"
+#include "../wmclient.h"
 
 PCUT_INIT;
@@ -47,4 +50,16 @@
 };
 
+static void test_ds_wmev_pending(void *);
+
+static ds_wmclient_cb_t test_ds_wmclient_cb = {
+	.ev_pending = test_ds_wmev_pending
+};
+
+static void test_ds_cfgev_pending(void *);
+
+static ds_cfgclient_cb_t test_ds_cfgclient_cb = {
+	.ev_pending = test_ds_cfgev_pending
+};
+
 static void test_ds_ev_pending(void *arg)
 {
@@ -53,4 +68,16 @@
 }
 
+static void test_ds_wmev_pending(void *arg)
+{
+	bool *called_cb = (bool *) arg;
+	*called_cb = true;
+}
+
+static void test_ds_cfgev_pending(void *arg)
+{
+	bool *called_cb = (bool *) arg;
+	*called_cb = true;
+}
+
 /** Display creation and destruction. */
 PCUT_TEST(display_create_destroy)
@@ -86,4 +113,45 @@
 
 	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** Basic WM client operation. */
+PCUT_TEST(display_wmclient)
+{
+	ds_display_t *disp;
+	ds_wmclient_t *wmclient;
+	ds_wmclient_t *c0, *c1;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_wmclient_create(disp, &test_ds_wmclient_cb, NULL, &wmclient);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	c0 = ds_display_first_wmclient(disp);
+	PCUT_ASSERT_EQUALS(c0, wmclient);
+
+	c1 = ds_display_next_wmclient(c0);
+	PCUT_ASSERT_NULL(c1);
+
+	ds_wmclient_destroy(wmclient);
+	ds_display_destroy(disp);
+}
+
+/** Basic CFG client operation. */
+PCUT_TEST(display_cfgclient)
+{
+	ds_display_t *disp;
+	ds_cfgclient_t *cfgclient;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_cfgclient_create(disp, &test_ds_cfgclient_cb, NULL, &cfgclient);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ds_cfgclient_destroy(cfgclient);
 	ds_display_destroy(disp);
 }
@@ -108,5 +176,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -150,4 +218,120 @@
 	wnd = ds_display_find_window(disp, w0->id + 1);
 	PCUT_ASSERT_NULL(wnd);
+
+	ds_window_destroy(w0);
+	ds_window_destroy(w1);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_display_enlist_window() */
+PCUT_TEST(display_enlist_window)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *w0;
+	ds_window_t *w1;
+	ds_window_t *w2;
+	ds_window_t *w3;
+	ds_window_t *w;
+	display_wnd_params_t params;
+	bool called_cb = false;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, &test_ds_client_cb, &called_cb, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 100;
+
+	/* Regular windows */
+
+	rc = ds_window_create(client, &params, &w0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_window_create(client, &params, &w1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Topmost windows */
+
+	params.flags |= wndf_topmost;
+
+	rc = ds_window_create(client, &params, &w2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_window_create(client, &params, &w3);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Delist w1 and w2 */
+	list_remove(&w1->ldwindows);
+	list_remove(&w2->ldwindows);
+
+	/* Enlist the windows back and check their order */
+	ds_display_enlist_window(disp, w1);
+	ds_display_enlist_window(disp, w2);
+
+	w = ds_display_first_window(disp);
+	PCUT_ASSERT_EQUALS(w2, w);
+	w = ds_display_next_window(w);
+	PCUT_ASSERT_EQUALS(w3, w);
+	w = ds_display_next_window(w);
+	PCUT_ASSERT_EQUALS(w1, w);
+	w = ds_display_next_window(w);
+	PCUT_ASSERT_EQUALS(w0, w);
+	w = ds_display_next_window(w);
+	PCUT_ASSERT_EQUALS(NULL, w);
+
+	ds_window_destroy(w0);
+	ds_window_destroy(w1);
+	ds_window_destroy(w2);
+	ds_window_destroy(w3);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_display_window_to_top() */
+PCUT_TEST(display_window_to_top)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *w0;
+	ds_window_t *w1;
+	display_wnd_params_t params;
+	bool called_cb = false;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, &test_ds_client_cb, &called_cb, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 100;
+
+	rc = ds_window_create(client, &params, &w0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_window_create(client, &params, &w1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_EQUALS(w1, ds_display_first_window(disp));
+	ds_display_window_to_top(w0);
+	PCUT_ASSERT_EQUALS(w0, ds_display_first_window(disp));
 
 	ds_window_destroy(w0);
@@ -178,5 +362,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -214,4 +398,27 @@
 }
 
+/** Basic idevcfg operation */
+PCUT_TEST(display_idevcfg)
+{
+	ds_display_t *disp;
+	ds_seat_t *seat;
+	ds_idevcfg_t *idevcfg;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_idevcfg_create(disp, 42, seat, &idevcfg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ds_idevcfg_destroy(idevcfg);
+
+	ds_seat_destroy(seat);
+	ds_display_destroy(disp);
+}
+
 /** Basic seat operation. */
 PCUT_TEST(display_seat)
@@ -219,11 +426,11 @@
 	ds_display_t *disp;
 	ds_seat_t *seat;
-	ds_seat_t *s0, *s1;
-	errno_t rc;
-
-	rc = ds_display_create(NULL, df_none, &disp);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ds_seat_create(disp, &seat);
+	ds_seat_t *s0, *s1, *s2;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -234,6 +441,15 @@
 	PCUT_ASSERT_NULL(s1);
 
-	ds_seat_destroy(seat);
-	ds_display_destroy(disp);
+	s2 = ds_display_find_seat(disp, seat->id);
+	PCUT_ASSERT_EQUALS(s2, seat);
+
+	ds_seat_destroy(seat);
+	ds_display_destroy(disp);
+}
+
+/** ds_display_seat_by_idev() returns the correct seat. */
+PCUT_TEST(display_seat_by_idev)
+{
+	// XXX TODO
 }
 
@@ -254,5 +470,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -302,5 +518,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -370,5 +586,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -442,3 +658,169 @@
 }
 
+/** ds_display_update_max_rect() updates maximization rectangle */
+PCUT_TEST(display_update_max_rect)
+{
+	ds_display_t *disp;
+	ds_seat_t *seat;
+	ds_client_t *client;
+	ds_window_t *wnd;
+	display_wnd_params_t params;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/*
+	 * We need to set display dimensions Here we do it directly
+	 * instead of adding a display device.
+	 */
+	disp->rect.p0.x = 0;
+	disp->rect.p0.y = 0;
+	disp->rect.p1.x = 500;
+	disp->rect.p1.y = 500;
+
+	/* Set maximize rectangle as well */
+	disp->max_rect = disp->rect;
+
+	/* A panel-like window at the bottom */
+	display_wnd_params_init(&params);
+	params.flags |= wndf_setpos;
+	params.pos.x = 0;
+	params.pos.y = 450;
+	params.rect.p0.x = 0;
+	params.rect.p0.y = 0;
+	params.rect.p1.x = 500;
+	params.rect.p1.y = 50;
+
+	rc = ds_window_create(client, &params, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/*
+	 * At this point the maximize rect should be unaltered because
+	 * the avoid flag has not been set.
+	 */
+	PCUT_ASSERT_INT_EQUALS(0, disp->max_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(0, disp->max_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(500, disp->max_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(500, disp->max_rect.p1.y);
+
+	wnd->flags |= wndf_avoid;
+
+	/* Update maximize rectangle */
+	ds_display_update_max_rect(disp);
+
+	/* Verify maximize rectangle */
+	PCUT_ASSERT_INT_EQUALS(0, disp->max_rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(0, disp->max_rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(500, disp->max_rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(450, disp->max_rect.p1.y);
+
+	ds_window_destroy(wnd);
+	ds_client_destroy(client);
+	ds_seat_destroy(seat);
+	ds_display_destroy(disp);
+}
+
+/** Cropping maximization rectangle from the top */
+PCUT_TEST(display_crop_max_rect_top)
+{
+	gfx_rect_t arect;
+	gfx_rect_t mrect;
+
+	arect.p0.x = 10;
+	arect.p0.y = 20;
+	arect.p1.x = 30;
+	arect.p1.y = 5;
+
+	mrect.p0.x = 10;
+	mrect.p0.y = 20;
+	mrect.p1.x = 30;
+	mrect.p1.y = 40;
+
+	ds_display_crop_max_rect(&arect, &mrect);
+
+	PCUT_ASSERT_INT_EQUALS(10, mrect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(5, mrect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(30, mrect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(40, mrect.p1.y);
+}
+
+/** Cropping maximization rectangle from the bottom */
+PCUT_TEST(display_crop_max_rect_bottom)
+{
+	gfx_rect_t arect;
+	gfx_rect_t mrect;
+
+	arect.p0.x = 10;
+	arect.p0.y = 35;
+	arect.p1.x = 30;
+	arect.p1.y = 40;
+
+	mrect.p0.x = 10;
+	mrect.p0.y = 20;
+	mrect.p1.x = 30;
+	mrect.p1.y = 40;
+
+	ds_display_crop_max_rect(&arect, &mrect);
+
+	PCUT_ASSERT_INT_EQUALS(10, mrect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(20, mrect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(30, mrect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(35, mrect.p1.y);
+}
+
+/** Cropping maximization rectangle from the left */
+PCUT_TEST(display_crop_max_rect_left)
+{
+	gfx_rect_t arect;
+	gfx_rect_t mrect;
+
+	arect.p0.x = 10;
+	arect.p0.y = 20;
+	arect.p1.x = 15;
+	arect.p1.y = 40;
+
+	mrect.p0.x = 10;
+	mrect.p0.y = 20;
+	mrect.p1.x = 30;
+	mrect.p1.y = 40;
+
+	ds_display_crop_max_rect(&arect, &mrect);
+
+	PCUT_ASSERT_INT_EQUALS(15, mrect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(20, mrect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(30, mrect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(40, mrect.p1.y);
+}
+
+/** Cropping maximization rectangle from the right */
+PCUT_TEST(display_crop_max_rect_right)
+{
+	gfx_rect_t arect;
+	gfx_rect_t mrect;
+
+	arect.p0.x = 25;
+	arect.p0.y = 20;
+	arect.p1.x = 30;
+	arect.p1.y = 40;
+
+	mrect.p0.x = 10;
+	mrect.p0.y = 20;
+	mrect.p1.x = 30;
+	mrect.p1.y = 40;
+
+	ds_display_crop_max_rect(&arect, &mrect);
+
+	PCUT_ASSERT_INT_EQUALS(10, mrect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(20, mrect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(25, mrect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(40, mrect.p1.y);
+}
+
 PCUT_EXPORT(display);
Index: uspace/srv/hid/display/test/main.c
===================================================================
--- uspace/srv/hid/display/test/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -31,4 +31,5 @@
 PCUT_INIT;
 
+PCUT_IMPORT(cfgclient);
 PCUT_IMPORT(client);
 PCUT_IMPORT(clonegc);
@@ -37,4 +38,5 @@
 PCUT_IMPORT(seat);
 PCUT_IMPORT(window);
+PCUT_IMPORT(wmclient);
 
 PCUT_MAIN();
Index: uspace/srv/hid/display/test/seat.c
===================================================================
--- uspace/srv/hid/display/test/seat.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/test/seat.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -70,5 +70,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -90,52 +90,5 @@
 }
 
-/** Evacuate focus when another window is available. */
-PCUT_TEST(evac_focus_two_windows)
-{
-	ds_display_t *disp;
-	ds_client_t *client;
-	ds_seat_t *seat;
-	ds_window_t *w0;
-	ds_window_t *w1;
-	display_wnd_params_t params;
-	bool called_cb = false;
-	errno_t rc;
-
-	rc = ds_display_create(NULL, df_none, &disp);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ds_client_create(disp, &test_ds_client_cb, &called_cb, &client);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ds_seat_create(disp, &seat);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	display_wnd_params_init(&params);
-	params.rect.p0.x = params.rect.p0.y = 0;
-	params.rect.p1.x = params.rect.p1.y = 1;
-
-	rc = ds_window_create(client, &params, &w1);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ds_window_create(client, &params, &w0);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ds_seat_set_focus(seat, w1);
-	PCUT_ASSERT_EQUALS(w1, seat->focus);
-	PCUT_ASSERT_TRUE(called_cb);
-	called_cb = false;
-
-	ds_seat_evac_wnd_refs(seat, w1);
-	PCUT_ASSERT_EQUALS(w0, seat->focus);
-	PCUT_ASSERT_TRUE(called_cb);
-
-	ds_window_destroy(w0);
-	ds_window_destroy(w1);
-	ds_seat_destroy(seat);
-	ds_client_destroy(client);
-	ds_display_destroy(disp);
-}
-
-/** Evacuate focus from the only existing window.
+/** Evacuate focus from window.
  *
  * After evacuating no window should be focused
@@ -157,5 +110,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -202,5 +155,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -224,4 +177,193 @@
 }
 
+/** Unfocus window when three windows are available */
+PCUT_TEST(unfocus_wnd_three_windows)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *w0;
+	ds_window_t *w1;
+	ds_window_t *w2;
+	display_wnd_params_t params;
+	bool called_cb = false;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, &test_ds_client_cb, &called_cb, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 1;
+
+	rc = ds_window_create(client, &params, &w2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_window_create(client, &params, &w1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_window_create(client, &params, &w0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* w0 is at the top, then w1, then w2 */
+
+	PCUT_ASSERT_EQUALS(w0, seat->focus);
+
+	ds_window_unfocus(w0);
+
+	/* The previous window, w1, should be focused now */
+	PCUT_ASSERT_EQUALS(w1, seat->focus);
+
+	ds_window_destroy(w0);
+	ds_window_destroy(w1);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** Unfocus window when two windows and one system window are available */
+PCUT_TEST(unfocus_wnd_two_windows_one_sys)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *w0;
+	ds_window_t *w1;
+	ds_window_t *w2;
+	display_wnd_params_t params;
+	bool called_cb = false;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, &test_ds_client_cb, &called_cb, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 1;
+
+	params.flags |= wndf_system;
+	rc = ds_window_create(client, &params, &w2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	params.flags &= ~wndf_system;
+	rc = ds_window_create(client, &params, &w1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_window_create(client, &params, &w0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_EQUALS(w0, seat->focus);
+
+	ds_window_unfocus(w0);
+
+	/* The previous non-system window is w1, w2 should be skipped */
+	PCUT_ASSERT_EQUALS(w1, seat->focus);
+
+	ds_window_destroy(w0);
+	ds_window_destroy(w1);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** Unfocus window when one window and one system window is available */
+PCUT_TEST(unfocus_wnd_one_window_one_sys)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *w0;
+	ds_window_t *w1;
+	display_wnd_params_t params;
+	bool called_cb = false;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, &test_ds_client_cb, &called_cb, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 1;
+
+	params.flags |= wndf_system;
+	rc = ds_window_create(client, &params, &w1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	params.flags &= ~wndf_system;
+	rc = ds_window_create(client, &params, &w0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_EQUALS(w0, seat->focus);
+
+	ds_window_unfocus(w0);
+
+	/* Since no non-system window is available, w1 should be focused */
+	PCUT_ASSERT_EQUALS(w1, seat->focus);
+
+	ds_window_destroy(w0);
+	ds_window_destroy(w1);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** Unfocus window when one window is available */
+PCUT_TEST(unfocus_wnd_one_window)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *w0;
+	display_wnd_params_t params;
+	bool called_cb = false;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, &test_ds_client_cb, &called_cb, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 1;
+
+	rc = ds_window_create(client, &params, &w0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_EQUALS(w0, seat->focus);
+
+	ds_window_unfocus(w0);
+
+	/* Since no other window is availabe, no window should be focused */
+	PCUT_ASSERT_EQUALS(NULL, seat->focus);
+
+	ds_window_destroy(w0);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
 /** Switch focus when another window is available. */
 PCUT_TEST(switch_focus_two_windows)
@@ -242,5 +384,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -291,5 +433,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -335,5 +477,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -388,5 +530,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -452,5 +594,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -548,5 +690,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
Index: uspace/srv/hid/display/test/window.c
===================================================================
--- uspace/srv/hid/display/test/window.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/test/window.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -36,4 +36,5 @@
 #include "../client.h"
 #include "../display.h"
+#include "../idevcfg.h"
 #include "../seat.h"
 #include "../window.h"
@@ -51,4 +52,82 @@
 };
 
+/** Test creating and destroying window */
+PCUT_TEST(create_destroy)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *wnd;
+	display_wnd_params_t params;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 10;
+
+	rc = ds_window_create(client, &params, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ds_window_destroy(wnd);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_window_bring_to_top() brings window to top */
+PCUT_TEST(bring_to_top)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *w1;
+	ds_window_t *w2;
+	display_wnd_params_t params;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 10;
+
+	rc = ds_window_create(client, &params, &w1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_window_create(client, &params, &w2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* w2 should be on the top */
+	PCUT_ASSERT_EQUALS(w2, ds_display_first_window(disp));
+
+	/* Bring w1 to top */
+	ds_window_bring_to_top(w1);
+
+	/* Now w1 should be on the top */
+	PCUT_ASSERT_EQUALS(w1, ds_display_first_window(disp));
+
+	ds_window_destroy(w1);
+	ds_window_destroy(w2);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
 /** Test ds_window_resize(). */
 PCUT_TEST(window_resize)
@@ -69,5 +148,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -99,4 +178,246 @@
 }
 
+/** Test ds_window_get_pos(). */
+PCUT_TEST(get_pos)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *wnd;
+	display_wnd_params_t params;
+	gfx_coord2_t pos;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 10;
+
+	rc = ds_window_create(client, &params, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	wnd->dpos.x = 100;
+	wnd->dpos.y = 100;
+
+	ds_window_get_pos(wnd, &pos);
+
+	PCUT_ASSERT_INT_EQUALS(100, pos.x);
+	PCUT_ASSERT_INT_EQUALS(100, pos.y);
+
+	ds_window_destroy(wnd);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_window_get_max_rect(). */
+PCUT_TEST(get_max_rect)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *wnd;
+	display_wnd_params_t params;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 10;
+
+	rc = ds_window_create(client, &params, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	wnd->dpos.x = 100;
+	wnd->dpos.y = 100;
+
+	ds_window_get_max_rect(wnd, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(disp->rect.p0.x, rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(disp->rect.p0.y, rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(disp->rect.p1.x, rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(disp->rect.p1.y, rect.p1.y);
+
+	ds_window_destroy(wnd);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_window_minimize(). */
+PCUT_TEST(window_minimize)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *wnd;
+	display_wnd_params_t params;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 10;
+
+	rc = ds_window_create(client, &params, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_window_minimize(wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(wndf_minimized, wnd->flags & wndf_minimized);
+
+	ds_window_destroy(wnd);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_window_unminimize(). */
+PCUT_TEST(window_unminimize)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *wnd;
+	display_wnd_params_t params;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.flags |= wndf_minimized;
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 10;
+
+	rc = ds_window_create(client, &params, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_window_unminimize(wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(0, wnd->flags & wndf_minimized);
+
+	ds_window_destroy(wnd);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_window_maximize(). */
+PCUT_TEST(window_maximize)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *wnd;
+	display_wnd_params_t params;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 10;
+
+	rc = ds_window_create(client, &params, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	wnd->dpos.x = 100;
+	wnd->dpos.y = 100;
+
+	rc = ds_window_maximize(wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(wndf_maximized, wnd->flags & wndf_maximized);
+
+	ds_window_destroy(wnd);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_window_unmaximize(). */
+PCUT_TEST(window_unmaximize)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *wnd;
+	display_wnd_params_t params;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 10;
+
+	rc = ds_window_create(client, &params, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	wnd->dpos.x = 100;
+	wnd->dpos.y = 100;
+
+	rc = ds_window_maximize(wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_window_unmaximize(wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(0, wnd->flags & wndf_maximized);
+
+	ds_window_destroy(wnd);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
 /** Test ds_window_get_ctx(). */
 PCUT_TEST(window_get_ctx)
@@ -116,5 +437,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -135,4 +456,42 @@
 }
 
+/** Test ds_window_is_visible() */
+PCUT_TEST(is_visible)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *wnd;
+	display_wnd_params_t params;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 10;
+
+	rc = ds_window_create(client, &params, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_TRUE(ds_window_is_visible(wnd));
+
+	wnd->flags |= wndf_minimized;
+
+	PCUT_ASSERT_FALSE(ds_window_is_visible(wnd));
+
+	ds_window_destroy(wnd);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
 /** Test ds_window_post_kbd_event() with Alt-F4 sends close event */
 PCUT_TEST(window_post_kbd_event_alt_f4)
@@ -158,5 +517,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -213,5 +572,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -265,4 +624,82 @@
 }
 
+/** Test ds_window_post_focus_event() */
+PCUT_TEST(window_post_focus_event)
+{
+	gfx_context_t *gc;
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *wnd;
+	display_wnd_params_t params;
+	errno_t rc;
+
+	rc = gfx_context_new(&dummy_ops, NULL, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_display_create(gc, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 1;
+
+	rc = ds_window_create(client, &params, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_window_post_focus_event(wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ds_window_destroy(wnd);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_window_post_unfocus_event() */
+PCUT_TEST(window_post_unfocus_event)
+{
+	gfx_context_t *gc;
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *wnd;
+	display_wnd_params_t params;
+	errno_t rc;
+
+	rc = gfx_context_new(&dummy_ops, NULL, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_display_create(gc, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 1;
+
+	rc = ds_window_create(client, &params, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_window_post_focus_event(wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ds_window_destroy(wnd);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
 /** Test ds_window_move_req() */
 PCUT_TEST(window_move_req)
@@ -275,4 +712,5 @@
 	display_wnd_params_t params;
 	gfx_coord2_t pos;
+	sysarg_t pos_id;
 	errno_t rc;
 
@@ -286,5 +724,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -300,9 +738,11 @@
 	pos.x = 42;
 	pos.y = 43;
-	ds_window_move_req(wnd, &pos);
+	pos_id = 44;
+	ds_window_move_req(wnd, &pos, pos_id);
 
 	PCUT_ASSERT_INT_EQUALS(dsw_moving, wnd->state);
 	PCUT_ASSERT_INT_EQUALS(pos.x, wnd->orig_pos.x);
 	PCUT_ASSERT_INT_EQUALS(pos.y, wnd->orig_pos.y);
+	PCUT_ASSERT_INT_EQUALS(pos_id, wnd->orig_pos_id);
 
 	ds_window_destroy(wnd);
@@ -322,4 +762,5 @@
 	display_wnd_params_t params;
 	gfx_coord2_t pos;
+	sysarg_t pos_id;
 	errno_t rc;
 
@@ -333,5 +774,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -347,5 +788,6 @@
 	pos.x = 42;
 	pos.y = 43;
-	ds_window_resize_req(wnd, display_wr_top_right, &pos);
+	pos_id = 44;
+	ds_window_resize_req(wnd, display_wr_top_right, &pos, pos_id);
 
 	PCUT_ASSERT_INT_EQUALS(dsw_resizing, wnd->state);
@@ -353,4 +795,5 @@
 	PCUT_ASSERT_INT_EQUALS(pos.x, wnd->orig_pos.x);
 	PCUT_ASSERT_INT_EQUALS(pos.y, wnd->orig_pos.y);
+	PCUT_ASSERT_INT_EQUALS(pos_id, wnd->orig_pos_id);
 
 	ds_window_destroy(wnd);
@@ -380,5 +823,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -650,5 +1093,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	rc = ds_seat_create(disp, &seat);
+	rc = ds_seat_create(disp, "Alice", &seat);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -666,4 +1109,10 @@
 	PCUT_ASSERT_EQUALS(wnd->display->cursor[dcurs_arrow], wnd->cursor);
 
+	// Check that invalid cursors cannot be set: ignore enum conversions
+	// as we are out-of-bounds
+#pragma GCC diagnostic push
+#if defined(__GNUC__) && (__GNUC__ >= 10)
+#pragma GCC diagnostic ignored "-Wenum-conversion"
+#endif
 	rc = ds_window_set_cursor(wnd, dcurs_limit);
 	PCUT_ASSERT_ERRNO_VAL(EINVAL, rc);
@@ -673,4 +1122,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EINVAL, rc);
 	PCUT_ASSERT_EQUALS(wnd->display->cursor[dcurs_arrow], wnd->cursor);
+#pragma GCC diagnostic pop
 
 	rc = ds_window_set_cursor(wnd, dcurs_size_lr);
@@ -684,4 +1134,271 @@
 }
 
+/** Test ds_window_set_caption() */
+PCUT_TEST(window_set_caption)
+{
+	gfx_context_t *gc;
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *wnd;
+	display_wnd_params_t params;
+	errno_t rc;
+
+	rc = gfx_context_new(&dummy_ops, NULL, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_display_create(gc, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 1;
+
+	rc = ds_window_create(client, &params, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_EQUALS(wnd->display->cursor[dcurs_arrow], wnd->cursor);
+
+	rc = ds_window_set_caption(wnd, "Hello");
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_INT_EQUALS(0, str_cmp("Hello", wnd->caption));
+
+	ds_window_destroy(wnd);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** ds_window_find_next() finds next window by flags */
+PCUT_TEST(window_find_next)
+{
+	gfx_context_t *gc;
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *w0;
+	ds_window_t *w1;
+	ds_window_t *w2;
+	ds_window_t *wnd;
+	display_wnd_params_t params;
+	errno_t rc;
+
+	rc = gfx_context_new(&dummy_ops, NULL, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_display_create(gc, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 1;
+
+	rc = ds_window_create(client, &params, &w0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_window_create(client, &params, &w1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	w1->flags |= wndf_minimized;
+
+	rc = ds_window_create(client, &params, &w2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	w2->flags |= wndf_system;
+
+	wnd = ds_window_find_next(w0, wndf_minimized);
+	PCUT_ASSERT_EQUALS(w1, wnd);
+
+	wnd = ds_window_find_next(w0, wndf_system);
+	PCUT_ASSERT_EQUALS(w2, wnd);
+
+	wnd = ds_window_find_next(w0, wndf_maximized);
+	PCUT_ASSERT_NULL(wnd);
+
+	ds_window_destroy(w0);
+	ds_window_destroy(w1);
+	ds_window_destroy(w2);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** ds_window_find_prev() finds previous window by flags */
+PCUT_TEST(window_find_prev)
+{
+	gfx_context_t *gc;
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *w0;
+	ds_window_t *w1;
+	ds_window_t *w2;
+	ds_window_t *wnd;
+	display_wnd_params_t params;
+	errno_t rc;
+
+	rc = gfx_context_new(&dummy_ops, NULL, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_display_create(gc, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 1;
+
+	rc = ds_window_create(client, &params, &w2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	w2->flags |= wndf_system;
+
+	rc = ds_window_create(client, &params, &w1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	w1->flags |= wndf_minimized;
+
+	rc = ds_window_create(client, &params, &w0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	wnd = ds_window_find_prev(w0, wndf_minimized);
+	PCUT_ASSERT_EQUALS(w1, wnd);
+
+	wnd = ds_window_find_prev(w0, wndf_system);
+	PCUT_ASSERT_EQUALS(w2, wnd);
+
+	wnd = ds_window_find_prev(w0, wndf_maximized);
+	PCUT_ASSERT_NULL(wnd);
+
+	ds_window_destroy(w0);
+	ds_window_destroy(w1);
+	ds_window_destroy(w2);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** ds_window_unfocus() switches to another window */
+PCUT_TEST(window_unfocus)
+{
+	gfx_context_t *gc;
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat;
+	ds_window_t *w0;
+	ds_window_t *w1;
+	display_wnd_params_t params;
+	errno_t rc;
+
+	rc = gfx_context_new(&dummy_ops, NULL, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_display_create(gc, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 1;
+
+	rc = ds_window_create(client, &params, &w1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_window_create(client, &params, &w0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_EQUALS(w0, seat->focus);
+
+	ds_window_unfocus(w0);
+
+	PCUT_ASSERT_EQUALS(w1, seat->focus);
+
+	ds_window_destroy(w0);
+	ds_window_destroy(w1);
+	ds_seat_destroy(seat);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** ds_window_orig_seat() correctly compares seats */
+PCUT_TEST(window_orig_seat)
+{
+	gfx_context_t *gc;
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_seat_t *seat0;
+	ds_seat_t *seat1;
+	sysarg_t devid0;
+	sysarg_t devid1;
+	ds_idevcfg_t *cfg0;
+	ds_idevcfg_t *cfg1;
+	ds_window_t *wnd;
+	display_wnd_params_t params;
+	errno_t rc;
+
+	rc = gfx_context_new(&dummy_ops, NULL, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_display_create(gc, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Alice", &seat0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_seat_create(disp, "Bob", &seat1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_wnd_params_init(&params);
+	params.rect.p0.x = params.rect.p0.y = 0;
+	params.rect.p1.x = params.rect.p1.y = 1;
+
+	rc = ds_window_create(client, &params, &wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	devid0 = 42;
+	devid1 = 43;
+
+	rc = ds_idevcfg_create(disp, devid0, seat0, &cfg0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_idevcfg_create(disp, devid1, seat1, &cfg1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	wnd->state = dsw_moving;
+	wnd->orig_pos_id = devid0;
+
+	PCUT_ASSERT_TRUE(ds_window_orig_seat(wnd, devid0));
+	PCUT_ASSERT_FALSE(ds_window_orig_seat(wnd, devid1));
+
+	ds_idevcfg_destroy(cfg0);
+	ds_idevcfg_destroy(cfg1);
+	ds_window_destroy(wnd);
+	ds_seat_destroy(seat0);
+	ds_seat_destroy(seat1);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
 static errno_t dummy_set_color(void *arg, gfx_color_t *color)
 {
Index: uspace/srv/hid/display/test/wmclient.c
===================================================================
--- uspace/srv/hid/display/test/wmclient.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/srv/hid/display/test/wmclient.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <disp_srv.h>
+#include <errno.h>
+#include <pcut/pcut.h>
+#include <str.h>
+
+#include "../display.h"
+#include "../wmclient.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(wmclient);
+
+static void test_ds_wmev_pending(void *);
+
+static ds_wmclient_cb_t test_ds_wmclient_cb = {
+	.ev_pending = test_ds_wmev_pending
+};
+
+static void test_ds_wmev_pending(void *arg)
+{
+	bool *called_cb = (bool *) arg;
+	*called_cb = true;
+}
+
+/** WM client creation and destruction. */
+PCUT_TEST(create_destroy)
+{
+	ds_display_t *disp;
+	ds_wmclient_t *wmclient;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_wmclient_create(disp, &test_ds_wmclient_cb, NULL, &wmclient);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ds_wmclient_destroy(wmclient);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_wmclient_get_event(), ds_wmclient_post_wnd_added_event(). */
+PCUT_TEST(client_get_post_wnd_added_event)
+{
+	ds_display_t *disp;
+	ds_wmclient_t *wmclient;
+	wndmgt_ev_t revent;
+	bool called_cb = NULL;
+	sysarg_t wnd_id;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_wmclient_create(disp, &test_ds_wmclient_cb, &called_cb,
+	    &wmclient);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	called_cb = false;
+	wnd_id = 42;
+
+	rc = ds_wmclient_get_event(wmclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
+
+	rc = ds_wmclient_post_wnd_added_event(wmclient, wnd_id);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_TRUE(called_cb);
+
+	rc = ds_wmclient_get_event(wmclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS(wnd_id, revent.wnd_id);
+	PCUT_ASSERT_EQUALS(wmev_window_added, revent.etype);
+
+	rc = ds_wmclient_get_event(wmclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
+
+	ds_wmclient_destroy(wmclient);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_wmclient_get_event(), ds_wmclient_post_wnd_removed_event(). */
+PCUT_TEST(client_get_post_wnd_removed_event)
+{
+	ds_display_t *disp;
+	ds_wmclient_t *wmclient;
+	wndmgt_ev_t revent;
+	bool called_cb = NULL;
+	sysarg_t wnd_id;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_wmclient_create(disp, &test_ds_wmclient_cb, &called_cb,
+	    &wmclient);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	called_cb = false;
+	wnd_id = 42;
+
+	rc = ds_wmclient_get_event(wmclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
+
+	rc = ds_wmclient_post_wnd_removed_event(wmclient, wnd_id);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_TRUE(called_cb);
+
+	rc = ds_wmclient_get_event(wmclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS(wnd_id, revent.wnd_id);
+	PCUT_ASSERT_EQUALS(wmev_window_removed, revent.etype);
+
+	rc = ds_wmclient_get_event(wmclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
+
+	ds_wmclient_destroy(wmclient);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_wmclient_get_event(), ds_wmclient_post_wnd_changed_event(). */
+PCUT_TEST(client_get_post_wnd_changed_event)
+{
+	ds_display_t *disp;
+	ds_wmclient_t *wmclient;
+	wndmgt_ev_t revent;
+	bool called_cb = NULL;
+	sysarg_t wnd_id;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_wmclient_create(disp, &test_ds_wmclient_cb, &called_cb,
+	    &wmclient);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	called_cb = false;
+	wnd_id = 42;
+
+	rc = ds_wmclient_get_event(wmclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
+
+	rc = ds_wmclient_post_wnd_changed_event(wmclient, wnd_id);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_TRUE(called_cb);
+
+	rc = ds_wmclient_get_event(wmclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS(wnd_id, revent.wnd_id);
+	PCUT_ASSERT_EQUALS(wmev_window_changed, revent.etype);
+
+	rc = ds_wmclient_get_event(wmclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
+
+	ds_wmclient_destroy(wmclient);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_wmclient_purge_events() */
+PCUT_TEST(purge_events)
+{
+	ds_display_t *disp;
+	ds_wmclient_t *wmclient;
+	wndmgt_ev_t revent;
+	bool called_cb = NULL;
+	sysarg_t wnd_id;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, df_none, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_wmclient_create(disp, &test_ds_wmclient_cb, &called_cb,
+	    &wmclient);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Post window added event */
+	wnd_id = 42;
+	rc = ds_wmclient_post_wnd_added_event(wmclient, wnd_id);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Purge it */
+	ds_wmclient_purge_events(wmclient);
+
+	/* The queue should be empty now */
+	rc = ds_wmclient_get_event(wmclient, &revent);
+	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
+
+	ds_wmclient_destroy(wmclient);
+	ds_display_destroy(disp);
+}
+
+PCUT_EXPORT(wmclient);
Index: uspace/srv/hid/display/types/display/cfgclient.h
===================================================================
--- uspace/srv/hid/display/types/display/cfgclient.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/srv/hid/display/types/display/cfgclient.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,74 @@
+/*
+ * 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 display
+ * @{
+ */
+/**
+ * @file Display server CFG client type
+ */
+
+#ifndef TYPES_DISPLAY_CFGCLIENT_H
+#define TYPES_DISPLAY_CFGCLIENT_H
+
+#include <adt/list.h>
+#include <dispcfg.h>
+
+/** Display server CFG client callbacks */
+typedef struct {
+	void (*ev_pending)(void *);
+} ds_cfgclient_cb_t;
+
+/** Display server WM client */
+typedef struct ds_cfgclient {
+	/** Parent display */
+	struct ds_display *display;
+	/** Callbacks */
+	ds_cfgclient_cb_t *cb;
+	/** Callback argument */
+	void *cb_arg;
+	/** Link to @c display->cfgclients */
+	link_t lcfgclients;
+	/** Event queue (of ds_window_ev_t) */
+	list_t events;
+} ds_cfgclient_t;
+
+/** WM client event queue entry */
+typedef struct {
+	/** Link to event queue */
+	link_t levents;
+	/** WM client to which the event is delivered */
+	ds_cfgclient_t *cfgclient;
+	/** Event */
+	dispcfg_ev_t event;
+} ds_cfgclient_ev_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/hid/display/types/display/display.h
===================================================================
--- uspace/srv/hid/display/types/display/display.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/types/display/display.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -46,4 +46,5 @@
 #include "cursor.h"
 #include "clonegc.h"
+#include "seat.h"
 #include "window.h"
 
@@ -62,4 +63,8 @@
 	/** Clients (of ds_client_t) */
 	list_t clients;
+	/** WM clients (of ds_wmclient_t) */
+	list_t wmclients;
+	/** CFG clients (of ds_cfgclient_t) */
+	list_t cfgclients;
 
 	/** Next ID to assign to a window.
@@ -71,4 +76,6 @@
 	 */
 	ds_wnd_id_t next_wnd_id;
+	/** Next ID to assign to a seat */
+	ds_seat_id_t next_seat_id;
 	/** Input service */
 	input_t *input;
@@ -83,4 +90,7 @@
 	list_t ddevs;
 
+	/** Input device configuration entries (of ds_idevcfg_t) */
+	list_t idevcfgs;
+
 	/** Background color */
 	gfx_color_t *bg_color;
@@ -94,4 +104,7 @@
 	/** Bounding rectangle */
 	gfx_rect_t rect;
+
+	/** Maximize rectangle */
+	gfx_rect_t max_rect;
 
 	/** Backbuffer bitmap or @c NULL if not double-buffering */
Index: uspace/srv/hid/display/types/display/idev.h
===================================================================
--- uspace/srv/hid/display/types/display/idev.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/srv/hid/display/types/display/idev.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup display
+ * @{
+ */
+/**
+ * @file Display server input device type
+ */
+
+#ifndef TYPES_DISPLAY_IDEV_H
+#define TYPES_DISPLAY_IDEV_H
+
+#include <types/common.h>
+
+typedef sysarg_t ds_idev_id_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/hid/display/types/display/idevcfg.h
===================================================================
--- uspace/srv/hid/display/types/display/idevcfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/srv/hid/display/types/display/idevcfg.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,59 @@
+/*
+ * 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 display
+ * @{
+ */
+/**
+ * @file Display server input device configuration type
+ */
+
+#ifndef TYPES_DISPLAY_IDEVCFG_H
+#define TYPES_DISPLAY_IDEVCFG_H
+
+#include <adt/list.h>
+#include <loc.h>
+
+/** Display server input device configuration */
+typedef struct ds_idevcfg {
+	/** Parent display */
+	struct ds_display *display;
+	/** Link to display->idevcfgs */
+	link_t ldispidcfgs;
+	/** Link to seat->idevcfgs */
+	link_t lseatidcfgs;
+	/** Service ID */
+	service_id_t svc_id;
+	/** Seat to which this device is assigned */
+	struct ds_seat *seat;
+} ds_idevcfg_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/hid/display/types/display/ptd_event.h
===================================================================
--- uspace/srv/hid/display/types/display/ptd_event.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/types/display/ptd_event.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -42,9 +42,13 @@
 	PTD_ABS_MOVE,
 	PTD_PRESS,
-	PTD_RELEASE
+	PTD_RELEASE,
+	PTD_DCLICK
 } ptd_event_type_t;
 
 /** Pointing device event */
 typedef struct {
+	/** Positioning device ID */
+	unsigned pos_id;
+	/** PTD event type */
 	ptd_event_type_t type;
 	/** Button number for PTD_PRESS or PTD_RELEASE */
Index: uspace/srv/hid/display/types/display/seat.h
===================================================================
--- uspace/srv/hid/display/types/display/seat.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/types/display/seat.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -40,4 +40,6 @@
 #include <gfx/coord.h>
 
+typedef sysarg_t ds_seat_id_t;
+
 /** Display server seat */
 typedef struct ds_seat {
@@ -46,4 +48,10 @@
 	/** Link to display->seats */
 	link_t lseats;
+	/** Seat ID */
+	ds_seat_id_t id;
+	/** Seat name */
+	char *name;
+	/** Input device configurations mapping to this seat */
+	list_t idevcfgs;
 	/** Window this seat is focused on */
 	struct ds_window *focus;
Index: uspace/srv/hid/display/types/display/window.h
===================================================================
--- uspace/srv/hid/display/types/display/window.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/types/display/window.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -79,4 +79,8 @@
 	/** Minimum size */
 	gfx_coord2_t min_size;
+	/** Normal rectangle (when not maximized or minimized) */
+	gfx_rect_t normal_rect;
+	/** Normal display position (when not maximized or minimized) */
+	gfx_coord2_t normal_dpos;
 	/** Window ID */
 	ds_wnd_id_t id;
@@ -99,6 +103,12 @@
 	/** Original position before started to move or resize the window */
 	gfx_coord2_t orig_pos;
+	/** Positioning device that started the move or resize */
+	sysarg_t orig_pos_id;
 	/** Window resize type (if state is dsw_resizing) */
 	display_wnd_rsztype_t rsztype;
+	/** Window caption */
+	char *caption;
+	/** Number of foci */
+	unsigned nfocus;
 } ds_window_t;
 
Index: uspace/srv/hid/display/types/display/wmclient.h
===================================================================
--- uspace/srv/hid/display/types/display/wmclient.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/srv/hid/display/types/display/wmclient.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup display
+ * @{
+ */
+/**
+ * @file Display server WM client type
+ */
+
+#ifndef TYPES_DISPLAY_WMCLIENT_H
+#define TYPES_DISPLAY_WMCLIENT_H
+
+#include <adt/list.h>
+#include <wndmgt.h>
+
+/** Display server WM client callbacks */
+typedef struct {
+	void (*ev_pending)(void *);
+} ds_wmclient_cb_t;
+
+/** Display server WM client */
+typedef struct ds_wmclient {
+	/** Parent display */
+	struct ds_display *display;
+	/** Callbacks */
+	ds_wmclient_cb_t *cb;
+	/** Callback argument */
+	void *cb_arg;
+	/** Link to @c display->wmclients */
+	link_t lwmclients;
+	/** Event queue (of ds_window_ev_t) */
+	list_t events;
+} ds_wmclient_t;
+
+/** WM client event queue entry */
+typedef struct {
+	/** Link to event queue */
+	link_t levents;
+	/** WM client to which the event is delivered */
+	ds_wmclient_t *wmclient;
+	/** Event */
+	wndmgt_ev_t event;
+} ds_wmclient_ev_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/hid/display/window.c
===================================================================
--- uspace/srv/hid/display/window.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/window.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -44,8 +44,11 @@
 #include <memgfx/memgc.h>
 #include <stdlib.h>
+#include <str.h>
+#include <wndmgt.h>
 #include "client.h"
 #include "display.h"
 #include "seat.h"
 #include "window.h"
+#include "wmclient.h"
 
 static void ds_window_invalidate_cb(void *, gfx_rect_t *);
@@ -64,10 +67,10 @@
  * @param client Client owning the window
  * @param params Window parameters
- * @param rgc Place to store pointer to new GC.
+ * @param rwnd Place to store pointer to new window.
  *
  * @return EOK on success or an error code
  */
 errno_t ds_window_create(ds_client_t *client, display_wnd_params_t *params,
-    ds_window_t **rgc)
+    ds_window_t **rwnd)
 {
 	ds_window_t *wnd = NULL;
@@ -85,4 +88,13 @@
 	}
 
+	/* Caption */
+	wnd->caption = str_dup(params->caption);
+	if (wnd->caption == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	wnd->flags = params->flags;
+
 	ds_client_add_window(client, wnd);
 	ds_display_add_window(client->display, wnd);
@@ -90,4 +102,6 @@
 	gfx_bitmap_params_init(&bparams);
 	bparams.rect = params->rect;
+
+	/* Allocate window bitmap */
 
 	dgc = ds_display_get_gc(wnd->display);
@@ -122,5 +136,4 @@
 	wnd->gc = mem_gc_get_ctx(wnd->mgc);
 	wnd->cursor = wnd->display->cursor[dcurs_arrow];
-	wnd->flags = params->flags;
 
 	if ((params->flags & wndf_setpos) != 0) {
@@ -133,6 +146,11 @@
 	}
 
-	seat = ds_display_first_seat(client->display);
-
+	/* Determine which seat should own the window */
+	if (params->idev_id != 0)
+		seat = ds_display_seat_by_idev(wnd->display, params->idev_id);
+	else
+		seat = ds_display_default_seat(wnd->display);
+
+	/* Is this a popup window? */
 	if ((params->flags & wndf_popup) != 0)
 		ds_seat_set_popup(seat, wnd);
@@ -140,12 +158,22 @@
 		ds_seat_set_focus(seat, wnd);
 
+	/* Is this window a panel? */
+	if ((params->flags & wndf_avoid) != 0)
+		ds_display_update_max_rect(wnd->display);
+
 	(void) ds_display_paint(wnd->display, NULL);
 
-	*rgc = wnd;
+	*rwnd = wnd;
 	return EOK;
 error:
 	if (wnd != NULL) {
+		ds_client_remove_window(wnd);
+		ds_display_remove_window(wnd);
+		if (wnd->mgc != NULL)
+			mem_gc_delete(wnd->mgc);
 		if (wnd->bitmap != NULL)
 			gfx_bitmap_destroy(wnd->bitmap);
+		if (wnd->caption != NULL)
+			free(wnd->caption);
 		free(wnd);
 	}
@@ -163,8 +191,13 @@
 
 	disp = wnd->display;
+
+	ds_window_unfocus(wnd);
 
 	ds_client_remove_window(wnd);
 	ds_display_remove_window(wnd);
 
+	if ((wnd->flags & wndf_avoid) != 0)
+		ds_display_update_max_rect(disp);
+
 	mem_gc_delete(wnd->mgc);
 
@@ -172,4 +205,5 @@
 		gfx_bitmap_destroy(wnd->bitmap);
 
+	free(wnd->caption);
 	free(wnd);
 
@@ -183,8 +217,5 @@
 void ds_window_bring_to_top(ds_window_t *wnd)
 {
-	ds_display_t *disp = wnd->display;
-
-	ds_display_remove_window(wnd);
-	ds_display_add_window(disp, wnd);
+	ds_display_window_to_top(wnd);
 	(void) ds_display_paint(wnd->display, NULL);
 }
@@ -198,4 +229,14 @@
 {
 	return wnd->gc;
+}
+
+/** Determine if window is visible.
+ *
+ * @param wnd Window
+ * @return @c true iff window is visible
+ */
+bool ds_window_is_visible(ds_window_t *wnd)
+{
+	return (wnd->flags & wndf_minimized) == 0;
 }
 
@@ -212,5 +253,9 @@
 	gfx_rect_t crect;
 
-	log_msg(LOG_DEFAULT, LVL_DEBUG, "ds_window_paint");
+	log_msg(LOG_DEFAULT, LVL_DEBUG2, "ds_window_paint");
+
+	/* Skip painting the window if not visible */
+	if (!ds_window_is_visible(wnd))
+		return EOK;
 
 	if (rect != NULL) {
@@ -357,5 +402,5 @@
 	bool newr;
 
-	log_msg(LOG_DEFAULT, LVL_DEBUG, "ds_window_repaint_preview");
+	log_msg(LOG_DEFAULT, LVL_DEBUG2, "ds_window_repaint_preview");
 
 	/*
@@ -401,6 +446,8 @@
  * @param wnd Window
  * @param pos Position where mouse button was pressed
- */
-static void ds_window_start_move(ds_window_t *wnd, gfx_coord2_t *pos)
+ * @param pos_id Positioning device ID
+ */
+static void ds_window_start_move(ds_window_t *wnd, gfx_coord2_t *pos,
+    sysarg_t pos_id)
 {
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "ds_window_start_move (%d, %d)",
@@ -411,4 +458,5 @@
 
 	wnd->orig_pos = *pos;
+	wnd->orig_pos_id = pos_id;
 	wnd->state = dsw_moving;
 	wnd->preview_pos = wnd->dpos;
@@ -440,4 +488,5 @@
 	wnd->dpos = nwpos;
 	wnd->state = dsw_idle;
+	wnd->orig_pos_id = 0;
 
 	(void) ds_display_paint(wnd->display, NULL);
@@ -455,5 +504,5 @@
 	gfx_rect_t old_rect;
 
-	log_msg(LOG_DEFAULT, LVL_DEBUG, "ds_window_update_move (%d, %d)",
+	log_msg(LOG_DEFAULT, LVL_DEBUG2, "ds_window_update_move (%d, %d)",
 	    (int) pos->x, (int) pos->y);
 
@@ -474,7 +523,8 @@
  * @param rsztype Resize type (which part of window is being dragged)
  * @param pos Position where mouse button was pressed
+ * @param pos_id Positioning device ID
  */
 static void ds_window_start_resize(ds_window_t *wnd,
-    display_wnd_rsztype_t rsztype, gfx_coord2_t *pos)
+    display_wnd_rsztype_t rsztype, gfx_coord2_t *pos, sysarg_t pos_id)
 {
 	ds_seat_t *seat;
@@ -487,11 +537,15 @@
 		return;
 
+	/* Determine which seat started the resize */
+	seat = ds_display_seat_by_idev(wnd->display, pos_id);
+	if (seat == NULL)
+		return;
+
 	wnd->orig_pos = *pos;
+	wnd->orig_pos_id = pos_id;
 	wnd->state = dsw_resizing;
 	wnd->rsztype = rsztype;
 	wnd->preview_rect = wnd->rect;
 
-	// XXX Need client to tell us which seat started the resize!
-	seat = ds_display_first_seat(wnd->display);
 	ctype = display_cursor_from_wrsz(rsztype);
 	ds_seat_set_wm_cursor(seat, wnd->display->cursor[ctype]);
@@ -523,7 +577,10 @@
 	ds_client_post_resize_event(wnd->client, wnd, &nrect);
 
-	// XXX Need to know which seat started the resize!
-	seat = ds_display_first_seat(wnd->display);
-	ds_seat_set_wm_cursor(seat, NULL);
+	/* Determine which seat started the resize */
+	seat = ds_display_seat_by_idev(wnd->display, wnd->orig_pos_id);
+	if (seat != NULL)
+		ds_seat_set_wm_cursor(seat, NULL);
+
+	wnd->orig_pos_id = 0;
 
 	(void) ds_display_paint(wnd->display, NULL);
@@ -541,5 +598,5 @@
 	gfx_rect_t old_rect;
 
-	log_msg(LOG_DEFAULT, LVL_DEBUG, "ds_window_update_resize (%d, %d)",
+	log_msg(LOG_DEFAULT, LVL_DEBUG2, "ds_window_update_resize (%d, %d)",
 	    (int) pos->x, (int) pos->y);
 
@@ -580,4 +637,6 @@
  * @param wnd Window
  * @param event Position event
+ *
+ * @return EOK on success or an error code
  */
 errno_t ds_window_post_pos_event(ds_window_t *wnd, pos_event_t *event)
@@ -585,8 +644,9 @@
 	pos_event_t tevent;
 	gfx_coord2_t pos;
+	sysarg_t pos_id;
 	gfx_rect_t drect;
 	bool inside;
 
-	log_msg(LOG_DEFAULT, LVL_DEBUG,
+	log_msg(LOG_DEFAULT, LVL_DEBUG2,
 	    "ds_window_post_pos_event type=%d pos=%d,%d", event->type,
 	    (int) event->hpos, (int) event->vpos);
@@ -594,19 +654,24 @@
 	pos.x = event->hpos;
 	pos.y = event->vpos;
+	pos_id = event->pos_id;
 	gfx_rect_translate(&wnd->dpos, &wnd->rect, &drect);
 	inside = gfx_pix_inside_rect(&pos, &drect);
 
-	if (event->type == POS_PRESS && event->btn_num == 2 && inside) {
-		ds_window_start_move(wnd, &pos);
+	if (event->type == POS_PRESS && event->btn_num == 2 && inside &&
+	    (wnd->flags & wndf_maximized) == 0) {
+		ds_window_start_move(wnd, &pos, pos_id);
 		return EOK;
 	}
 
 	if (event->type == POS_RELEASE) {
-		if (wnd->state == dsw_moving) {
+		/* Finish move/resize if they were started by the same seat */
+		if (wnd->state == dsw_moving &&
+		    ds_window_orig_seat(wnd, pos_id)) {
 			ds_window_finish_move(wnd, &pos);
 			return EOK;
 		}
 
-		if (wnd->state == dsw_resizing) {
+		if (wnd->state == dsw_resizing &&
+		    ds_window_orig_seat(wnd, pos_id)) {
 			ds_window_finish_resize(wnd, &pos);
 			return EOK;
@@ -615,10 +680,13 @@
 
 	if (event->type == POS_UPDATE) {
-		if (wnd->state == dsw_moving) {
+		/* Update move/resize if they were started by the same seat */
+		if (wnd->state == dsw_moving &&
+		    ds_window_orig_seat(wnd, pos_id)) {
 			ds_window_update_move(wnd, &pos);
 			return EOK;
 		}
 
-		if (wnd->state == dsw_resizing) {
+		if (wnd->state == dsw_resizing &&
+		    ds_window_orig_seat(wnd, pos_id)) {
 			ds_window_update_resize(wnd, &pos);
 			return EOK;
@@ -637,10 +705,30 @@
  *
  * @param wnd Window
+ * @return EOK on success or an error code
  */
 errno_t ds_window_post_focus_event(ds_window_t *wnd)
 {
+	display_wnd_focus_ev_t efocus;
+	errno_t rc;
+	ds_wmclient_t *wmclient;
+
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "ds_window_post_focus_event");
 
-	return ds_client_post_focus_event(wnd->client, wnd);
+	/* Increase focus counter */
+	++wnd->nfocus;
+	efocus.nfocus = wnd->nfocus;
+
+	rc = ds_client_post_focus_event(wnd->client, wnd, &efocus);
+	if (rc != EOK)
+		return rc;
+
+	/* Notify window managers about window information change */
+	wmclient = ds_display_first_wmclient(wnd->display);
+	while (wmclient != NULL) {
+		ds_wmclient_post_wnd_changed_event(wmclient, wnd->id);
+		wmclient = ds_display_next_wmclient(wmclient);
+	}
+
+	return EOK;
 }
 
@@ -648,10 +736,30 @@
  *
  * @param wnd Window
+ * @return EOK on success or an error code
  */
 errno_t ds_window_post_unfocus_event(ds_window_t *wnd)
 {
+	display_wnd_unfocus_ev_t eunfocus;
+	errno_t rc;
+	ds_wmclient_t *wmclient;
+
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "ds_window_post_unfocus_event");
 
-	return ds_client_post_unfocus_event(wnd->client, wnd);
+	/* Decrease focus counter */
+	--wnd->nfocus;
+	eunfocus.nfocus = wnd->nfocus;
+
+	rc = ds_client_post_unfocus_event(wnd->client, wnd, &eunfocus);
+	if (rc != EOK)
+		return rc;
+
+	/* Notify window managers about window information change */
+	wmclient = ds_display_first_wmclient(wnd->display);
+	while (wmclient != NULL) {
+		ds_wmclient_post_wnd_changed_event(wmclient, wnd->id);
+		wmclient = ds_display_next_wmclient(wmclient);
+	}
+
+	return EOK;
 }
 
@@ -661,7 +769,8 @@
  * @param pos Position where the pointer was when the move started
  *            relative to the window
+ * @param pos_id Positioning device ID
  * @param event Button press event
  */
-void ds_window_move_req(ds_window_t *wnd, gfx_coord2_t *pos)
+void ds_window_move_req(ds_window_t *wnd, gfx_coord2_t *pos, sysarg_t pos_id)
 {
 	gfx_coord2_t orig_pos;
@@ -671,5 +780,5 @@
 
 	gfx_coord2_add(&wnd->dpos, pos, &orig_pos);
-	ds_window_start_move(wnd, &orig_pos);
+	ds_window_start_move(wnd, &orig_pos, pos_id);
 }
 
@@ -691,4 +800,13 @@
 {
 	*dpos = wnd->dpos;
+}
+
+/** Get maximized window rectangle.
+ *
+ * @param wnd Window
+ */
+void ds_window_get_max_rect(ds_window_t *wnd, gfx_rect_t *rect)
+{
+	*rect = wnd->display->max_rect;
 }
 
@@ -699,16 +817,17 @@
  * @param pos Position where the pointer was when the resize started
  *            relative to the window
+ * @param pos_id Positioning device ID
  * @param event Button press event
  */
 void ds_window_resize_req(ds_window_t *wnd, display_wnd_rsztype_t rsztype,
-    gfx_coord2_t *pos)
+    gfx_coord2_t *pos, sysarg_t pos_id)
 {
 	gfx_coord2_t orig_pos;
 
-	log_msg(LOG_DEFAULT, LVL_DEBUG, "ds_window_resize_req (%d, %d, %d)",
-	    (int) rsztype, (int) pos->x, (int) pos->y);
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "ds_window_resize_req (%d, %d, %d, %d)",
+	    (int)rsztype, (int)pos->x, (int)pos->y, (int)pos_id);
 
 	gfx_coord2_add(&wnd->dpos, pos, &orig_pos);
-	ds_window_start_resize(wnd, rsztype, &orig_pos);
+	ds_window_start_resize(wnd, rsztype, &orig_pos, pos_id);
 }
 
@@ -716,4 +835,5 @@
  *
  * @param wnd Window
+ * @return EOK on success or an error code
  */
 errno_t ds_window_resize(ds_window_t *wnd, gfx_coord2_t *offs,
@@ -766,5 +886,113 @@
 	wnd->rect = *nrect;
 
+	if ((wnd->flags & wndf_avoid) != 0)
+		ds_display_update_max_rect(wnd->display);
+
 	(void) ds_display_paint(wnd->display, NULL);
+	return EOK;
+}
+
+/** Minimize window.
+ *
+ * @param wnd Window
+ * @return EOK on success or an error code
+ */
+errno_t ds_window_minimize(ds_window_t *wnd)
+{
+	/* If already minimized, do nothing and return success. */
+	if ((wnd->flags & wndf_minimized) != 0)
+		return EOK;
+
+	ds_window_unfocus(wnd);
+
+	wnd->flags |= wndf_minimized;
+	(void) ds_display_paint(wnd->display, NULL);
+	return EOK;
+}
+
+/** Unminimize window.
+ *
+ * @param wnd Window
+ * @return EOK on success or an error code
+ */
+errno_t ds_window_unminimize(ds_window_t *wnd)
+{
+	/* If not minimized, do nothing and return success. */
+	if ((wnd->flags & wndf_minimized) == 0)
+		return EOK;
+
+	wnd->flags &= ~wndf_minimized;
+	(void) ds_display_paint(wnd->display, NULL);
+	return EOK;
+}
+
+/** Maximize window.
+ *
+ * @param wnd Window
+ * @return EOK on success or an error code
+ */
+errno_t ds_window_maximize(ds_window_t *wnd)
+{
+	gfx_coord2_t old_dpos;
+	gfx_rect_t old_rect;
+	gfx_coord2_t offs;
+	gfx_rect_t max_rect;
+	gfx_rect_t nrect;
+	errno_t rc;
+
+	/* If already maximized, do nothing and return success. */
+	if ((wnd->flags & wndf_maximized) != 0)
+		return EOK;
+
+	/* Remember the old window rectangle and display position */
+	old_rect = wnd->rect;
+	old_dpos = wnd->dpos;
+
+	ds_window_get_max_rect(wnd, &max_rect);
+
+	/* Keep window contents on the same position on the screen */
+	offs.x = max_rect.p0.x - wnd->dpos.x;
+	offs.y = max_rect.p0.y - wnd->dpos.y;
+
+	/* Maximized window's coordinates will start at 0,0 */
+	gfx_rect_rtranslate(&max_rect.p0, &max_rect, &nrect);
+
+	rc = ds_window_resize(wnd, &offs, &nrect);
+	if (rc != EOK)
+		return rc;
+
+	/* Set window flags, remember normal rectangle */
+	wnd->flags |= wndf_maximized;
+	wnd->normal_rect = old_rect;
+	wnd->normal_dpos = old_dpos;
+
+	return EOK;
+}
+
+/** Unmaximize window.
+ *
+ * @param wnd Window
+ * @return EOK on success or an error code
+ */
+errno_t ds_window_unmaximize(ds_window_t *wnd)
+{
+	gfx_coord2_t offs;
+	errno_t rc;
+
+	/* If not maximized, do nothing and return success. */
+	if ((wnd->flags & wndf_maximized) == 0)
+		return EOK;
+
+	/* Keep window contents on the same position on the screen */
+	offs.x = wnd->normal_dpos.x - wnd->dpos.x;
+	offs.y = wnd->normal_dpos.y - wnd->dpos.y;
+
+	rc = ds_window_resize(wnd, &offs, &wnd->normal_rect);
+	if (rc != EOK)
+		return rc;
+
+	/* Clear maximized flag */
+	wnd->flags &= ~wndf_maximized;
+
 	return EOK;
 }
@@ -812,4 +1040,5 @@
  *
  * @param wnd Window
+ * @param cursor New cursor
  * @return EOK on success, EINVAL if @a cursor is invalid
  */
@@ -825,4 +1054,158 @@
 }
 
+/** Set window caption.
+ *
+ * @param wnd Window
+ * @param caption New caption
+ *
+ * @return EOK on success, EINVAL if @a cursor is invalid
+ */
+errno_t ds_window_set_caption(ds_window_t *wnd, const char *caption)
+{
+	char *dcaption;
+	ds_wmclient_t *wmclient;
+
+	dcaption = str_dup(caption);
+	if (dcaption == NULL)
+		return ENOMEM;
+
+	free(wnd->caption);
+	wnd->caption = dcaption;
+
+	/* Notify window managers about window information change */
+	wmclient = ds_display_first_wmclient(wnd->display);
+	while (wmclient != NULL) {
+		ds_wmclient_post_wnd_changed_event(wmclient, wnd->id);
+		wmclient = ds_display_next_wmclient(wmclient);
+	}
+
+	return EOK;
+}
+
+/** Find alternate window with the allowed flags.
+ *
+ * An alternate window is a *different* window that is preferably previous
+ * in the display order and only has the @a allowed flags.
+ *
+ * @param wnd Window
+ * @param allowed_flags Bitmask of flags that the window is allowed to have
+ *
+ * @return Alternate window matching the criteria or @c NULL if there is none
+ */
+ds_window_t *ds_window_find_prev(ds_window_t *wnd,
+    display_wnd_flags_t allowed_flags)
+{
+	ds_window_t *nwnd;
+
+	/* Try preceding windows in display order */
+	nwnd = ds_display_next_window(wnd);
+	while (nwnd != NULL && (nwnd->flags & ~allowed_flags) != 0) {
+		nwnd = ds_display_next_window(nwnd);
+	}
+
+	/* Do we already have a matching window? */
+	if (nwnd != NULL && (nwnd->flags & ~allowed_flags) == 0) {
+		return nwnd;
+	}
+
+	/* Try succeeding windows in display order */
+	nwnd = ds_display_first_window(wnd->display);
+	while (nwnd != NULL && nwnd != wnd &&
+	    (nwnd->flags & ~allowed_flags) != 0) {
+		nwnd = ds_display_next_window(nwnd);
+	}
+
+	if (nwnd == wnd)
+		return NULL;
+
+	return nwnd;
+}
+
+/** Find alternate window with the allowed flags.
+ *
+ * An alternate window is a *different* window that is preferably previous
+ * in the display order and only has the @a allowed flags.
+ *
+ * @param wnd Window
+ * @param allowed_flags Bitmask of flags that the window is allowed to have
+ *
+ * @return Alternate window matching the criteria or @c NULL if there is none
+ */
+ds_window_t *ds_window_find_next(ds_window_t *wnd,
+    display_wnd_flags_t allowed_flags)
+{
+	ds_window_t *nwnd;
+
+	/* Try preceding windows in display order */
+	nwnd = ds_display_prev_window(wnd);
+	while (nwnd != NULL && (nwnd->flags & ~allowed_flags) != 0) {
+		nwnd = ds_display_prev_window(nwnd);
+	}
+
+	/* Do we already have a matching window? */
+	if (nwnd != NULL && (nwnd->flags & ~allowed_flags) == 0) {
+		return nwnd;
+	}
+
+	/* Try succeeding windows in display order */
+	nwnd = ds_display_last_window(wnd->display);
+	while (nwnd != NULL && nwnd != wnd &&
+	    (nwnd->flags & ~allowed_flags) != 0) {
+		nwnd = ds_display_prev_window(nwnd);
+	}
+
+	if (nwnd == wnd)
+		return NULL;
+
+	return nwnd;
+}
+
+/** Remove focus from window.
+ *
+ * Used to switch focus to another window when closing or minimizing window.
+ *
+ * @param wnd Window
+ */
+void ds_window_unfocus(ds_window_t *wnd)
+{
+	ds_seat_t *seat;
+
+	/* Make sure window is no longer focused in any seat */
+	seat = ds_display_first_seat(wnd->display);
+	while (seat != NULL) {
+		ds_seat_unfocus_wnd(seat, wnd);
+		seat = ds_display_next_seat(seat);
+	}
+}
+
+/** Determine if input device belongs to the same seat as the original device.
+ *
+ * Compare the seat ownning @a idev_id with the seat owning @a wnd->orig_pos_id
+ * (the device that started the window move or resize).
+ *
+ * This is used to make sure that, when two seats focus the same window,
+ * only devices owned by the seat that started the resize or move can
+ * affect it. Otherwise moving the other pointer(s) would disrupt the
+ * resize or move operation.
+ *
+ * @param wnd Window (that is currently being resized or moved)
+ * @param idev_id Input device ID
+ * @return @c true iff idev_id is owned by the same seat as the input
+ *         device that started the resize or move
+ */
+bool ds_window_orig_seat(ds_window_t *wnd, sysarg_t idev_id)
+{
+	ds_seat_t *orig_seat;
+	ds_seat_t *seat;
+
+	/* Window must be in state such that wnd->orig_pos_id is valid */
+	assert(wnd->state == dsw_moving || wnd->state == dsw_resizing);
+
+	orig_seat = ds_display_seat_by_idev(wnd->display, wnd->orig_pos_id);
+	seat = ds_display_seat_by_idev(wnd->display, idev_id);
+
+	return seat == orig_seat;
+}
+
 /** Window memory GC invalidate callback.
  *
Index: uspace/srv/hid/display/window.h
===================================================================
--- uspace/srv/hid/display/window.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/display/window.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -41,4 +41,5 @@
 #include <errno.h>
 #include <io/pos_event.h>
+#include <stdbool.h>
 #include <types/gfx/context.h>
 #include <types/gfx/coord.h>
@@ -55,4 +56,5 @@
 extern void ds_window_bring_to_top(ds_window_t *);
 extern gfx_context_t *ds_window_get_ctx(ds_window_t *);
+extern bool ds_window_is_visible(ds_window_t *);
 extern errno_t ds_window_paint(ds_window_t *, gfx_rect_t *);
 errno_t ds_window_paint_preview(ds_window_t *, gfx_rect_t *);
@@ -61,13 +63,23 @@
 extern errno_t ds_window_post_focus_event(ds_window_t *);
 extern errno_t ds_window_post_unfocus_event(ds_window_t *);
-extern void ds_window_move_req(ds_window_t *, gfx_coord2_t *);
+extern void ds_window_move_req(ds_window_t *, gfx_coord2_t *, sysarg_t);
 extern void ds_window_move(ds_window_t *, gfx_coord2_t *);
 extern void ds_window_get_pos(ds_window_t *, gfx_coord2_t *);
+extern void ds_window_get_max_rect(ds_window_t *, gfx_rect_t *);
 extern void ds_window_resize_req(ds_window_t *, display_wnd_rsztype_t,
-    gfx_coord2_t *);
+    gfx_coord2_t *, sysarg_t);
 extern errno_t ds_window_resize(ds_window_t *, gfx_coord2_t *, gfx_rect_t *);
+extern errno_t ds_window_minimize(ds_window_t *);
+extern errno_t ds_window_unminimize(ds_window_t *);
+extern errno_t ds_window_maximize(ds_window_t *);
+extern errno_t ds_window_unmaximize(ds_window_t *);
 extern void ds_window_calc_resize(ds_window_t *, gfx_coord2_t *,
     gfx_rect_t *);
 extern errno_t ds_window_set_cursor(ds_window_t *, display_stock_cursor_t);
+extern errno_t ds_window_set_caption(ds_window_t *, const char *);
+extern ds_window_t *ds_window_find_next(ds_window_t *, display_wnd_flags_t);
+extern ds_window_t *ds_window_find_prev(ds_window_t *, display_wnd_flags_t);
+extern void ds_window_unfocus(ds_window_t *);
+extern bool ds_window_orig_seat(ds_window_t *, sysarg_t);
 
 #endif
Index: uspace/srv/hid/display/wmclient.c
===================================================================
--- uspace/srv/hid/display/wmclient.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/srv/hid/display/wmclient.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,223 @@
+/*
+ * 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 display
+ * @{
+ */
+/**
+ * @file Display server WM client
+ */
+
+#include <adt/list.h>
+#include <errno.h>
+#include <io/log.h>
+#include <stdlib.h>
+#include "display.h"
+#include "window.h"
+#include "wmclient.h"
+
+/** Create WM client.
+ *
+ * @param display Parent display
+ * @param cb WM client callbacks
+ * @param cb_arg Callback argument
+ * @param rwmclient Place to store pointer to new WM client.
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ds_wmclient_create(ds_display_t *display, ds_wmclient_cb_t *cb,
+    void *cb_arg, ds_wmclient_t **rwmclient)
+{
+	ds_wmclient_t *wmclient;
+
+	wmclient = calloc(1, sizeof(ds_wmclient_t));
+	if (wmclient == NULL)
+		return ENOMEM;
+
+	list_initialize(&wmclient->events);
+	wmclient->cb = cb;
+	wmclient->cb_arg = cb_arg;
+
+	ds_display_add_wmclient(display, wmclient);
+
+	*rwmclient = wmclient;
+	return EOK;
+}
+
+/** Destroy WM client.
+ *
+ * @param wmclient WM client
+ */
+void ds_wmclient_destroy(ds_wmclient_t *wmclient)
+{
+	ds_wmclient_purge_events(wmclient);
+	ds_display_remove_wmclient(wmclient);
+	free(wmclient);
+}
+
+/** Get next event from WM client event queue.
+ *
+ * @param wmclient WM client
+ * @param event Place to store event
+ * @return EOK on success, ENOENT if event queue is empty
+ */
+errno_t ds_wmclient_get_event(ds_wmclient_t *wmclient, wndmgt_ev_t *event)
+{
+	link_t *link;
+	ds_wmclient_ev_t *wevent;
+
+	link = list_first(&wmclient->events);
+	if (link == NULL)
+		return ENOENT;
+
+	wevent = list_get_instance(link, ds_wmclient_ev_t, levents);
+	list_remove(link);
+
+	*event = wevent->event;
+	free(wevent);
+	return EOK;
+}
+
+/** Purge events from WM client event queue.
+ *
+ * @param client Client
+ */
+void ds_wmclient_purge_events(ds_wmclient_t *wmclient)
+{
+	link_t *cur;
+	link_t *next;
+	ds_wmclient_ev_t *wevent;
+
+	cur = list_first(&wmclient->events);
+	while (cur != NULL) {
+		next = list_next(cur, &wmclient->events);
+		wevent = list_get_instance(cur, ds_wmclient_ev_t, levents);
+
+		list_remove(cur);
+		free(wevent);
+		cur = next;
+	}
+}
+
+/** Post window added event to the WM client's message queue.
+ *
+ * @param wmclient WM client
+ * @param wnd_id Window ID of the added window
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ds_wmclient_post_wnd_added_event(ds_wmclient_t *wmclient,
+    sysarg_t wnd_id)
+{
+	ds_wmclient_ev_t *wevent;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "wmclient_pos_wnd_added_event "
+	    "wmclient=%p wnd_id=%zu\n", (void *)wmclient, wnd_id);
+
+	wevent = calloc(1, sizeof(ds_wmclient_ev_t));
+	if (wevent == NULL)
+		return ENOMEM;
+
+	wevent->wmclient = wmclient;
+	wevent->event.etype = wmev_window_added;
+	wevent->event.wnd_id = wnd_id;
+	list_append(&wevent->levents, &wmclient->events);
+
+	/* Notify the client */
+	// TODO Do not send more than once until client drains the queue
+	if (wmclient->cb != NULL && wmclient->cb->ev_pending != NULL)
+		wmclient->cb->ev_pending(wmclient->cb_arg);
+
+	return EOK;
+}
+
+/** Post window removed event to the WM client's message queue.
+ *
+ * @param wmclient WM client
+ * @param wnd_id Window ID of the added window
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ds_wmclient_post_wnd_removed_event(ds_wmclient_t *wmclient,
+    sysarg_t wnd_id)
+{
+	ds_wmclient_ev_t *wevent;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "wmclient_pos_wnd_removed_event "
+	    "wmclient=%p wnd_id=%zu\n", (void *)wmclient, wnd_id);
+
+	wevent = calloc(1, sizeof(ds_wmclient_ev_t));
+	if (wevent == NULL)
+		return ENOMEM;
+
+	wevent->wmclient = wmclient;
+	wevent->event.etype = wmev_window_removed;
+	wevent->event.wnd_id = wnd_id;
+	list_append(&wevent->levents, &wmclient->events);
+
+	/* Notify the client */
+	// TODO Do not send more than once until client drains the queue
+	if (wmclient->cb != NULL && wmclient->cb->ev_pending != NULL)
+		wmclient->cb->ev_pending(wmclient->cb_arg);
+
+	return EOK;
+}
+
+/** Post window changed event to the WM client's message queue.
+ *
+ * @param wmclient WM client
+ * @param wnd_id Window ID of the added window
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ds_wmclient_post_wnd_changed_event(ds_wmclient_t *wmclient,
+    sysarg_t wnd_id)
+{
+	ds_wmclient_ev_t *wevent;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "wmclient_pos_wnd_changed_event "
+	    "wmclient=%p wnd_id=%zu\n", (void *)wmclient, wnd_id);
+
+	wevent = calloc(1, sizeof(ds_wmclient_ev_t));
+	if (wevent == NULL)
+		return ENOMEM;
+
+	wevent->wmclient = wmclient;
+	wevent->event.etype = wmev_window_changed;
+	wevent->event.wnd_id = wnd_id;
+	list_append(&wevent->levents, &wmclient->events);
+
+	/* Notify the client */
+	// TODO Do not send more than once until client drains the queue
+	if (wmclient->cb != NULL && wmclient->cb->ev_pending != NULL)
+		wmclient->cb->ev_pending(wmclient->cb_arg);
+
+	return EOK;
+}
+
+/** @}
+ */
Index: uspace/srv/hid/display/wmclient.h
===================================================================
--- uspace/srv/hid/display/wmclient.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/srv/hid/display/wmclient.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup display
+ * @{
+ */
+/**
+ * @file Display server WM client
+ */
+
+#ifndef WMCLIENT_H
+#define WMCLIENT_H
+
+#include <errno.h>
+#include <wndmgt.h>
+#include "types/display/display.h"
+#include "types/display/wmclient.h"
+
+extern errno_t ds_wmclient_create(ds_display_t *, ds_wmclient_cb_t *, void *,
+    ds_wmclient_t **);
+extern void ds_wmclient_destroy(ds_wmclient_t *);
+extern errno_t ds_wmclient_get_event(ds_wmclient_t *,  wndmgt_ev_t *);
+extern errno_t ds_wmclient_post_wnd_added_event(ds_wmclient_t *, sysarg_t);
+extern errno_t ds_wmclient_post_wnd_removed_event(ds_wmclient_t *, sysarg_t);
+extern errno_t ds_wmclient_post_wnd_changed_event(ds_wmclient_t *, sysarg_t);
+extern void ds_wmclient_purge_events(ds_wmclient_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/hid/display/wmops.c
===================================================================
--- uspace/srv/hid/display/wmops.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/srv/hid/display/wmops.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,225 @@
+/*
+ * 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 display
+ * @{
+ */
+/**
+ * @file Window management ops implementation
+ */
+
+#include <errno.h>
+#include <io/log.h>
+#include <stdlib.h>
+#include <str.h>
+#include <wndmgt_srv.h>
+#include "display.h"
+#include "seat.h"
+#include "window.h"
+#include "wmclient.h"
+
+static errno_t dispwm_get_window_list(void *, wndmgt_window_list_t **);
+static errno_t dispwm_get_window_info(void *, sysarg_t, wndmgt_window_info_t **);
+static errno_t dispwm_activate_window(void *, sysarg_t, sysarg_t);
+static errno_t dispwm_close_window(void *, sysarg_t);
+static errno_t dispwm_get_event(void *, wndmgt_ev_t *);
+
+wndmgt_ops_t wndmgt_srv_ops = {
+	.get_window_list = dispwm_get_window_list,
+	.get_window_info = dispwm_get_window_info,
+	.activate_window = dispwm_activate_window,
+	.close_window = dispwm_close_window,
+	.get_event = dispwm_get_event,
+};
+
+/** Get window list.
+ *
+ * @param arg Argument (WM client)
+ * @param rlist Place to store pointer to new list
+ * @return EOK on success or an error code
+ */
+static errno_t dispwm_get_window_list(void *arg, wndmgt_window_list_t **rlist)
+{
+	ds_wmclient_t *wmclient = (ds_wmclient_t *)arg;
+	wndmgt_window_list_t *list;
+	ds_window_t *wnd;
+	unsigned i;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dispwm_get_window_list()");
+
+	list = calloc(1, sizeof(wndmgt_window_list_t));
+	if (list == NULL)
+		return ENOMEM;
+
+	ds_display_lock(wmclient->display);
+
+	/* Count the number of windows */
+	list->nwindows = 0;
+	wnd = ds_display_first_window(wmclient->display);
+	while (wnd != NULL) {
+		++list->nwindows;
+		wnd = ds_display_next_window(wnd);
+	}
+
+	/* Allocate array for window IDs */
+	list->windows = calloc(list->nwindows, sizeof(sysarg_t));
+	if (list->windows == NULL) {
+		ds_display_unlock(wmclient->display);
+		free(list);
+		return ENOMEM;
+	}
+
+	/* Fill in window IDs */
+	i = 0;
+	wnd = ds_display_first_window(wmclient->display);
+	while (wnd != NULL) {
+		list->windows[i++] = wnd->id;
+		wnd = ds_display_next_window(wnd);
+	}
+
+	ds_display_unlock(wmclient->display);
+	*rlist = list;
+	return EOK;
+}
+
+/** Get window information.
+ *
+ * @param arg Argument (WM client)
+ * @param wnd_id Window ID
+ * @param rinfo Place to store pointer to new window information structure
+ * @return EOK on success or an error code
+ */
+static errno_t dispwm_get_window_info(void *arg, sysarg_t wnd_id,
+    wndmgt_window_info_t **rinfo)
+{
+	ds_wmclient_t *wmclient = (ds_wmclient_t *)arg;
+	ds_window_t *wnd;
+	wndmgt_window_info_t *info;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dispwm_get_window_info()");
+
+	ds_display_lock(wmclient->display);
+	wnd = ds_display_find_window(wmclient->display, wnd_id);
+	if (wnd == NULL) {
+		ds_display_unlock(wmclient->display);
+		return ENOENT;
+	}
+
+	info = calloc(1, sizeof(wndmgt_window_info_t));
+	if (info == NULL) {
+		ds_display_unlock(wmclient->display);
+		return ENOMEM;
+	}
+
+	info->caption = str_dup(wnd->caption);
+	if (info->caption == NULL) {
+		ds_display_unlock(wmclient->display);
+		free(info);
+		return ENOMEM;
+	}
+
+	info->flags = wnd->flags;
+	info->nfocus = wnd->nfocus;
+
+	ds_display_unlock(wmclient->display);
+	*rinfo = info;
+	return EOK;
+}
+
+/** Activate window.
+ *
+ * @param arg Argument (WM client)
+ * @param dev_id Input device ID
+ * @param wnd_id Window ID
+ * @return EOK on success or an error code
+ */
+static errno_t dispwm_activate_window(void *arg, sysarg_t dev_id,
+    sysarg_t wnd_id)
+{
+	ds_wmclient_t *wmclient = (ds_wmclient_t *)arg;
+	ds_window_t *wnd;
+	ds_seat_t *seat;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dispwm_activate_window()");
+
+	ds_display_lock(wmclient->display);
+	wnd = ds_display_find_window(wmclient->display, wnd_id);
+	if (wnd == NULL) {
+		ds_display_unlock(wmclient->display);
+		return ENOENT;
+	}
+
+	/* Determine which seat's focus should be changed */
+	seat = ds_display_seat_by_idev(wnd->display, dev_id);
+	if (seat == NULL) {
+		ds_display_unlock(wmclient->display);
+		return ENOENT;
+	}
+
+	/* Switch focus */
+	ds_seat_set_focus(seat, wnd);
+
+	ds_display_unlock(wmclient->display);
+	return EOK;
+}
+
+/** Close window.
+ *
+ * @param arg Argument (WM client)
+ * @param wnd_id Window ID
+ * @return EOK on success or an error code
+ */
+static errno_t dispwm_close_window(void *arg, sysarg_t wnd_id)
+{
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dispwm_close_window()");
+	(void)arg;
+	(void)wnd_id;
+	return EOK;
+}
+
+/** Get window management event.
+ *
+ * @param arg Argument (WM client)
+ * @param ev Place to store event
+ * @return EOK on success, ENOENT if there are no events
+ */
+static errno_t dispwm_get_event(void *arg, wndmgt_ev_t *ev)
+{
+	ds_wmclient_t *wmclient = (ds_wmclient_t *)arg;
+	errno_t rc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dispwm_get_event()");
+
+	ds_display_lock(wmclient->display);
+	rc = ds_wmclient_get_event(wmclient, ev);
+	ds_display_unlock(wmclient->display);
+	return rc;
+}
+
+/** @}
+ */
Index: uspace/srv/hid/display/wmops.h
===================================================================
--- uspace/srv/hid/display/wmops.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
+++ uspace/srv/hid/display/wmops.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2022 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup display
+ * @{
+ */
+/**
+ * @file Window management ops implementation
+ */
+
+#ifndef WMOPS_H
+#define WMOPS_H
+
+#include <wndmgt_srv.h>
+
+extern wndmgt_ops_t wndmgt_srv_ops;
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/hid/input/ctl/stty.c
===================================================================
--- uspace/srv/hid/input/ctl/stty.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/input/ctl/stty.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2011 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -228,4 +228,6 @@
 	0,	KC_RIGHT,	0x1b, 0x5b, 0x43, GSP_END,
 
+	/* Ctrl + key */
+
 	KM_CTRL, 	KC_Q,		0x11, GSP_END,
 	KM_CTRL, 	KC_W,		0x17, GSP_END,
@@ -253,4 +255,62 @@
 	KM_CTRL,	KC_N,		0x0e, GSP_END,
 
+	/* Alt + key */
+
+	KM_ALT,	KC_BACKTICK,	0x1b, 0x60, GSP_END,
+
+	KM_ALT,	KC_1,		0x1b, 0x31, GSP_END,
+	KM_ALT,	KC_2,		0x1b, 0x32, GSP_END,
+	KM_ALT,	KC_3,		0x1b, 0x33, GSP_END,
+	KM_ALT,	KC_4,		0x1b, 0x34, GSP_END,
+	KM_ALT,	KC_5,		0x1b, 0x35, GSP_END,
+	KM_ALT,	KC_6,		0x1b, 0x36, GSP_END,
+	KM_ALT,	KC_7,		0x1b, 0x37, GSP_END,
+	KM_ALT,	KC_8,		0x1b, 0x38, GSP_END,
+	KM_ALT,	KC_9,		0x1b, 0x39, GSP_END,
+	KM_ALT,	KC_0,		0x1b, 0x30, GSP_END,
+
+	KM_ALT,	KC_MINUS,	0x1b, 0x2d, GSP_END,
+	KM_ALT,	KC_EQUALS,	0x1b, 0x3d, GSP_END,
+
+	KM_ALT,	KC_Q,		0x1b, 0x71, GSP_END,
+	KM_ALT,	KC_W,		0x1b, 0x77, GSP_END,
+	KM_ALT,	KC_E,		0x1b, 0x65, GSP_END,
+	KM_ALT,	KC_R,		0x1b, 0x72, GSP_END,
+	KM_ALT,	KC_T,		0x1b, 0x74, GSP_END,
+	KM_ALT,	KC_Y,		0x1b, 0x79, GSP_END,
+	KM_ALT,	KC_U,		0x1b, 0x75, GSP_END,
+	KM_ALT,	KC_I,		0x1b, 0x69, GSP_END,
+	KM_ALT,	KC_O,		0x1b, 0x6f, GSP_END,
+	KM_ALT,	KC_P,		0x1b, 0x70, GSP_END,
+
+	/* 0x1b, 0x5b is used by other keys/sequences */
+	KM_ALT,	KC_RBRACKET,	0x1b, 0x5d, GSP_END,
+
+	KM_ALT,	KC_A,		0x1b, 0x61, GSP_END,
+	KM_ALT,	KC_S,		0x1b, 0x73, GSP_END,
+	KM_ALT,	KC_D,		0x1b, 0x64, GSP_END,
+	KM_ALT,	KC_F,		0x1b, 0x66, GSP_END,
+	KM_ALT,	KC_G,		0x1b, 0x67, GSP_END,
+	KM_ALT,	KC_H,		0x1b, 0x68, GSP_END,
+	KM_ALT,	KC_J,		0x1b, 0x6a, GSP_END,
+	KM_ALT,	KC_K,		0x1b, 0x6b, GSP_END,
+	KM_ALT,	KC_L,		0x1b, 0x6c, GSP_END,
+
+	KM_ALT,	KC_SEMICOLON,	0x1b, 0x3b, GSP_END,
+	KM_ALT,	KC_QUOTE,	0x1b, 0x27, GSP_END,
+	KM_ALT,	KC_BACKSLASH,	0x1b, 0x5c, GSP_END,
+
+	KM_ALT,	KC_Z,		0x1b, 0x7a, GSP_END,
+	KM_ALT,	KC_X,		0x1b, 0x78, GSP_END,
+	KM_ALT,	KC_C,		0x1b, 0x63, GSP_END,
+	KM_ALT,	KC_V,		0x1b, 0x76, GSP_END,
+	KM_ALT,	KC_B,		0x1b, 0x62, GSP_END,
+	KM_ALT,	KC_N,		0x1b, 0x6e, GSP_END,
+	KM_ALT,	KC_M,		0x1b, 0x6d, GSP_END,
+
+	KM_ALT,	KC_COMMA,	0x1b, 0x2c, GSP_END,
+	KM_ALT,	KC_PERIOD,	0x1b, 0x2e, GSP_END,
+	KM_ALT,	KC_SLASH,	0x1b, 0x2f, GSP_END,
+
 	/*
 	 * Sequences specific to Gnome terminal
Index: uspace/srv/hid/input/gsp.c
===================================================================
--- uspace/srv/hid/input/gsp.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/input/gsp.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -84,5 +84,5 @@
 }
 
-static hash_table_ops_t trans_ops = {
+static const hash_table_ops_t trans_ops = {
 	.hash = trans_hash,
 	.key_hash = trans_key_hash,
Index: uspace/srv/hid/input/input.c
===================================================================
--- uspace/srv/hid/input/input.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/input/input.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,5 +1,5 @@
 /*
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2006 Josef Cejka
- * Copyright (c) 2011 Jiri Svoboda
  * All rights reserved.
  *
@@ -253,5 +253,6 @@
 		if (client->active) {
 			async_exch_t *exch = async_exchange_begin(client->sess);
-			async_msg_4(exch, INPUT_EVENT_KEY, ev.type, ev.key, ev.mods, ev.c);
+			async_msg_5(exch, INPUT_EVENT_KEY, kdev->svc_id,
+			    ev.type, ev.key, ev.mods, ev.c);
 			async_exchange_end(exch);
 		}
@@ -267,5 +268,6 @@
 
 			if ((dx) || (dy))
-				async_msg_2(exch, INPUT_EVENT_MOVE, dx, dy);
+				async_msg_3(exch, INPUT_EVENT_MOVE,
+				    mdev->svc_id, dx, dy);
 
 			if (dz) {
@@ -274,7 +276,10 @@
 
 				for (unsigned int i = 0; i < 3; i++)
-					async_msg_4(exch, INPUT_EVENT_KEY, KEY_PRESS, code, 0, 0);
-
-				async_msg_4(exch, INPUT_EVENT_KEY, KEY_RELEASE, code, 0, 0);
+					async_msg_5(exch, INPUT_EVENT_KEY,
+					    0 /* XXX kbd_id */,
+					    KEY_PRESS, code, 0, 0);
+
+				async_msg_5(exch, INPUT_EVENT_KEY, KEY_RELEASE,
+				    0 /* XXX kbd_id */, code, 0, 0);
 			}
 
@@ -292,5 +297,6 @@
 			if ((max_x) && (max_y)) {
 				async_exch_t *exch = async_exchange_begin(client->sess);
-				async_msg_4(exch, INPUT_EVENT_ABS_MOVE, x, y, max_x, max_y);
+				async_msg_5(exch, INPUT_EVENT_ABS_MOVE,
+				    mdev->svc_id, x, y, max_x, max_y);
 				async_exchange_end(exch);
 			}
@@ -305,5 +311,6 @@
 		if (client->active) {
 			async_exch_t *exch = async_exchange_begin(client->sess);
-			async_msg_2(exch, INPUT_EVENT_BUTTON, bnum, press);
+			async_msg_3(exch, INPUT_EVENT_BUTTON, mdev->svc_id,
+			    bnum, press);
 			async_exchange_end(exch);
 		}
@@ -311,5 +318,18 @@
 }
 
-/** Arbitrate client actiovation */
+/** Mouse button has been double-clicked. */
+void mouse_push_event_dclick(mouse_dev_t *mdev, int bnum)
+{
+	list_foreach(clients, link, client_t, client) {
+		if (client->active) {
+			async_exch_t *exch = async_exchange_begin(client->sess);
+			async_msg_2(exch, INPUT_EVENT_DCLICK, mdev->svc_id,
+			    bnum);
+			async_exchange_end(exch);
+		}
+	}
+}
+
+/** Arbitrate client activation */
 static void client_arbitration(void)
 {
@@ -858,4 +878,5 @@
 {
 	errno_t rc;
+	loc_srv_t *srv;
 
 	if (argc < 2) {
@@ -881,5 +902,5 @@
 	async_set_fallback_port_handler(client_connection, NULL);
 
-	rc = loc_server_register(NAME);
+	rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		printf("%s: Unable to register server\n", NAME);
@@ -888,5 +909,5 @@
 
 	service_id_t service_id;
-	rc = loc_service_register(argv[1], &service_id);
+	rc = loc_service_register(srv, argv[1], &service_id);
 	if (rc != EOK) {
 		printf("%s: Unable to register service %s\n", NAME, argv[1]);
Index: uspace/srv/hid/input/layout/ar.c
===================================================================
--- uspace/srv/hid/input/layout/ar.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/input/layout/ar.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -216,8 +216,4 @@
 	char32_t c;
 
-	/* Produce no characters when Ctrl or Alt is pressed. */
-	if ((ev->mods & (KM_CTRL | KM_ALT)) != 0)
-		return 0;
-
 	c = translate(ev->key, map_neutral, sizeof(map_neutral) / sizeof(char32_t));
 	if (c != 0)
Index: uspace/srv/hid/input/layout/cz.c
===================================================================
--- uspace/srv/hid/input/layout/cz.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/input/layout/cz.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -322,8 +322,4 @@
 	char32_t c;
 
-	/* Produce no characters when Ctrl or Alt is pressed. */
-	if ((ev->mods & (KM_CTRL | KM_ALT)) != 0)
-		return 0;
-
 	if (ev->key == KC_EQUALS) {
 		if ((ev->mods & KM_SHIFT) != 0)
Index: uspace/srv/hid/input/layout/fr_azerty.c
===================================================================
--- uspace/srv/hid/input/layout/fr_azerty.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/input/layout/fr_azerty.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -223,7 +223,4 @@
 static char32_t fr_azerty_parse_ev (layout_t *s, kbd_event_t *e)
 {
-	if ((e->mods & (KM_CTRL | KM_ALT)))
-		return 0; // Produce no characters when Ctrl or Alt is pressed
-
 	char32_t c = translate (e->key, map_neutral, sizeof (map_neutral) / sizeof (char32_t));
 	if (c)
Index: uspace/srv/hid/input/layout/us_dvorak.c
===================================================================
--- uspace/srv/hid/input/layout/us_dvorak.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/input/layout/us_dvorak.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -225,8 +225,4 @@
 	char32_t c;
 
-	/* Produce no characters when Ctrl or Alt is pressed. */
-	if ((ev->mods & (KM_CTRL | KM_ALT)) != 0)
-		return 0;
-
 	c = translate(ev->key, map_neutral, sizeof(map_neutral) / sizeof(char32_t));
 	if (c != 0)
Index: uspace/srv/hid/input/layout/us_qwerty.c
===================================================================
--- uspace/srv/hid/input/layout/us_qwerty.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/input/layout/us_qwerty.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -219,8 +219,4 @@
 	char32_t c;
 
-	/* Produce no characters when Ctrl or Alt is pressed. */
-	if ((ev->mods & (KM_CTRL | KM_ALT)) != 0)
-		return 0;
-
 	c = translate(ev->key, map_neutral, sizeof(map_neutral) / sizeof(char32_t));
 	if (c != 0)
Index: uspace/srv/hid/input/meson.build
===================================================================
--- uspace/srv/hid/input/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/input/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -28,5 +28,5 @@
 #
 
-deps = [ 'drv' ]
+deps = [ 'drv', 'console', 'input' ]
 src = files(
 	'layout/cz.c',
Index: uspace/srv/hid/input/mouse.h
===================================================================
--- uspace/srv/hid/input/mouse.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/input/mouse.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2011 Jiri Svoboda
+ * Copyright (c) 2021 Jiri Svoboda
  * All rights reserved.
  *
@@ -66,4 +66,5 @@
     unsigned int, unsigned int);
 extern void mouse_push_event_button(mouse_dev_t *, int, int);
+extern void mouse_push_event_dclick(mouse_dev_t *, int);
 
 #endif
Index: uspace/srv/hid/input/proto/mousedev.c
===================================================================
--- uspace/srv/hid/input/proto/mousedev.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/input/proto/mousedev.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2021 Jiri Svoboda
  * Copyright (c) 2011 Martin Decky
  * All rights reserved.
@@ -43,4 +44,5 @@
 #include <loc.h>
 #include <stdlib.h>
+#include <time.h>
 #include "../mouse.h"
 #include "../mouse_port.h"
@@ -48,8 +50,17 @@
 #include "../input.h"
 
+enum {
+	/** Default double-click speed in milliseconds */
+	dclick_delay_ms = 500
+};
+
 /** Mousedev softstate */
 typedef struct {
 	/** Link to generic mouse device */
 	mouse_dev_t *mouse_dev;
+	/** Button number of last button pressed (or -1 if none) */
+	int press_bnum;
+	/** Time at which button was last pressed */
+	struct timespec press_time;
 } mousedev_t;
 
@@ -61,4 +72,5 @@
 
 	mousedev->mouse_dev = mdev;
+	mousedev->press_bnum = -1;
 
 	return mousedev;
@@ -68,4 +80,28 @@
 {
 	free(mousedev);
+}
+
+static void mousedev_press(mousedev_t *mousedev, int bnum)
+{
+	struct timespec now;
+	nsec_t ms_delay;
+
+	getuptime(&now);
+
+	/* Same button was pressed previously */
+	if (mousedev->press_bnum == bnum) {
+		/* Compute milliseconds since previous press */
+		ms_delay = ts_sub_diff(&now, &mousedev->press_time) / 1000000;
+
+		if (ms_delay <= dclick_delay_ms) {
+			mouse_push_event_dclick(mousedev->mouse_dev, bnum);
+			mousedev->press_bnum = -1;
+			return;
+		}
+	}
+
+	/* Record which button was last pressed and at what time */
+	mousedev->press_bnum = bnum;
+	mousedev->press_time = now;
 }
 
@@ -103,4 +139,6 @@
 			mouse_push_event_button(mousedev->mouse_dev,
 			    ipc_get_arg1(&call), ipc_get_arg2(&call));
+			if (ipc_get_arg2(&call) != 0)
+				mousedev_press(mousedev, ipc_get_arg1(&call));
 			retval = EOK;
 			break;
Index: uspace/srv/hid/input/stroke.c
===================================================================
--- uspace/srv/hid/input/stroke.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/input/stroke.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2011 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -47,4 +47,5 @@
 /** Correspondence between modifers and the modifier keycodes. */
 static unsigned int mods_keys[][2] = {
+	{ KM_LALT, KC_LALT },
 	{ KM_LSHIFT, KC_LSHIFT },
 	{ KM_LCTRL, KC_LCTRL },
Index: uspace/srv/hid/isdv4_tablet/main.c
===================================================================
--- uspace/srv/hid/isdv4_tablet/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/isdv4_tablet/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2012 Martin Sucha
  * All rights reserved.
@@ -184,4 +185,5 @@
 	serial_t *serial;
 	char *serial_port_name = NULL;
+	loc_srv_t *srv;
 
 	int arg = 1;
@@ -315,5 +317,5 @@
 
 	async_set_fallback_port_handler(mouse_connection, NULL);
-	rc = loc_server_register(NAME);
+	rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		printf("%s: Unable to register driver.\n", NAME);
@@ -329,6 +331,7 @@
 	}
 
-	rc = loc_service_register(service_name, &service_id);
-	if (rc != EOK) {
+	rc = loc_service_register(srv, service_name, &service_id);
+	if (rc != EOK) {
+		loc_server_unregister(srv);
 		printf(NAME ": Unable to register service %s.\n", service_name);
 		return rc;
@@ -340,5 +343,5 @@
 		printf(NAME ": Unable to get mouse category id.\n");
 	} else {
-		rc = loc_service_add_to_cat(service_id, mouse_category);
+		rc = loc_service_add_to_cat(srv, service_id, mouse_category);
 		if (rc != EOK) {
 			printf(NAME ": Unable to add device to mouse category.\n");
Index: uspace/srv/hid/isdv4_tablet/meson.build
===================================================================
--- uspace/srv/hid/isdv4_tablet/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/isdv4_tablet/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -27,4 +27,4 @@
 #
 
-deps = [ 'drv' ]
+deps = [ 'drv', 'input' ]
 src = files('main.c', 'isdv4.c')
Index: uspace/srv/hid/output/meson.build
===================================================================
--- uspace/srv/hid/output/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/output/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -28,5 +28,5 @@
 #
 
-deps = [ 'codepage', 'drv', 'fbfont', 'pixconv', 'ddev' ]
+deps = [ 'codepage', 'console', 'drv', 'fbfont', 'pixconv', 'ddev', 'output' ]
 src = files(
 	'ctl/serial.c',
Index: uspace/srv/hid/output/output.c
===================================================================
--- uspace/srv/hid/output/output.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/output/output.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2011 Martin Decky
  * All rights reserved.
@@ -44,7 +45,4 @@
 #include "output.h"
 
-#define MAX_COLS  128
-#define MAX_ROWS  128
-
 typedef struct {
 	link_t link;
@@ -115,10 +113,18 @@
 static void srv_get_dimensions(ipc_call_t *icall)
 {
-	sysarg_t cols = MAX_COLS;
-	sysarg_t rows = MAX_ROWS;
-
-	list_foreach(outdevs, link, outdev_t, dev) {
-		cols = min(cols, dev->cols);
-		rows = min(rows, dev->rows);
+	sysarg_t cols = 0;
+	sysarg_t rows = 0;
+	bool first;
+
+	first = true;
+	list_foreach(outdevs, link, outdev_t, dev) {
+		if (first) {
+			cols = dev->cols;
+			rows = dev->rows;
+			first = false;
+		} else {
+			cols = min(cols, dev->cols);
+			rows = min(rows, dev->rows);
+		}
 	}
 
@@ -463,4 +469,6 @@
 int main(int argc, char *argv[])
 {
+	loc_srv_t *srv;
+
 	if (argc < 2) {
 		usage(argv[0]);
@@ -472,5 +480,5 @@
 	/* Register server */
 	async_set_fallback_port_handler(client_connection, NULL);
-	errno_t rc = loc_server_register(NAME);
+	errno_t rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		printf("%s: Unable to register driver\n", NAME);
@@ -479,6 +487,8 @@
 
 	service_id_t service_id;
-	rc = loc_service_register(argv[1], &service_id);
+	rc = loc_service_register(srv, argv[1], &service_id);
 	if (rc != EOK) {
+		loc_server_unregister(srv);
+
 		printf("%s: Unable to register service %s\n", NAME, argv[1]);
 		return rc;
Index: uspace/srv/hid/output/port/ega.c
===================================================================
--- uspace/srv/hid/output/port/ega.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/output/port/ega.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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/hid/output/proto/vt100.c
===================================================================
--- uspace/srv/hid/output/proto/vt100.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/output/proto/vt100.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -41,5 +41,14 @@
 #include "vt100.h"
 
-#define MAX_CONTROL  20
+/** Buffer size when creating actual VT100 commands.
+ *
+ * This is absurdly large but since we accept numbers via sysarg_t,
+ * we make it big enough for the largest value to be on the safe side
+ * (and to silence compiler too).
+ *
+ * TODO: find out if VT100 has some hard limits or perhaps simply cut-out
+ * values larger than 16 bits or something.
+ */
+#define MAX_CONTROL 64
 
 typedef enum {
Index: uspace/srv/hid/remcons/meson.build
===================================================================
--- uspace/srv/hid/remcons/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/remcons/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -28,4 +28,4 @@
 #
 
-deps = [ 'inet' ]
+deps = [ 'inet', 'console' ]
 src = files('remcons.c', 'user.c')
Index: uspace/srv/hid/remcons/remcons.c
===================================================================
--- uspace/srv/hid/remcons/remcons.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/remcons/remcons.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2012 Vojtech Horky
  * All rights reserved.
@@ -110,4 +111,6 @@
 };
 
+static loc_srv_t *remcons_srv;
+
 static telnet_user_t *srv_to_user(con_srv_t *srv)
 {
@@ -301,5 +304,6 @@
 	telnet_user_add(user);
 
-	errno_t rc = loc_service_register(user->service_name, &user->service_id);
+	errno_t rc = loc_service_register(remcons_srv, user->service_name,
+	    &user->service_id);
 	if (rc != EOK) {
 		telnet_user_error(user, "Unable to register %s with loc: %s.",
@@ -332,5 +336,5 @@
 	fibril_mutex_unlock(&user->guard);
 
-	rc = loc_service_unregister(user->service_id);
+	rc = loc_service_unregister(remcons_srv, user->service_id);
 	if (rc != EOK) {
 		telnet_user_error(user,
@@ -351,5 +355,5 @@
 
 	async_set_fallback_port_handler(client_connection, NULL);
-	rc = loc_server_register(NAME);
+	rc = loc_server_register(NAME, &remcons_srv);
 	if (rc != EOK) {
 		fprintf(stderr, "%s: Unable to register server\n", NAME);
Index: uspace/srv/hid/rfb/main.c
===================================================================
--- uspace/srv/hid/rfb/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/rfb/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2013 Martin Sucha
  * All rights reserved.
@@ -443,4 +443,5 @@
 {
 	rfb_t rfb;
+	loc_srv_t *srv;
 
 	log_init(NAME);
@@ -482,5 +483,5 @@
 	async_set_fallback_port_handler(client_connection, &rfb);
 
-	errno_t rc = loc_server_register(NAME);
+	errno_t rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		printf("%s: Unable to register server.\n", NAME);
@@ -497,5 +498,5 @@
 	service_id_t service_id;
 
-	rc = loc_service_register(service_name, &service_id);
+	rc = loc_service_register(srv, service_name, &service_id);
 	if (rc != EOK) {
 		printf(NAME ": Unable to register service %s.\n", service_name);
@@ -512,5 +513,5 @@
 	}
 
-	rc = loc_service_add_to_cat(service_id, ddev_cid);
+	rc = loc_service_add_to_cat(srv, service_id, ddev_cid);
 	if (rc != EOK) {
 		fprintf(stderr, NAME ": Unable to add service to display device category.\n");
Index: uspace/srv/hid/s3c24xx_ts/meson.build
===================================================================
--- uspace/srv/hid/s3c24xx_ts/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/s3c24xx_ts/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -27,3 +27,4 @@
 #
 
+deps = [ 'input' ]
 src = files('s3c24xx_ts.c')
Index: uspace/srv/hid/s3c24xx_ts/s3c24xx_ts.c
===================================================================
--- uspace/srv/hid/s3c24xx_ts/s3c24xx_ts.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hid/s3c24xx_ts/s3c24xx_ts.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2010 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,5 +39,4 @@
 #include <ddi.h>
 #include <loc.h>
-#include <io/console.h>
 #include <vfs/vfs.h>
 #include <ipc/mouseev.h>
@@ -81,8 +80,10 @@
 int main(int argc, char *argv[])
 {
+	loc_srv_t *srv;
+
 	printf("%s: S3C24xx touchscreen driver\n", NAME);
 
 	async_set_fallback_port_handler(s3c24xx_ts_connection, NULL);
-	errno_t rc = loc_server_register(NAME);
+	errno_t rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		printf("%s: Unable to register driver.\n", NAME);
@@ -91,12 +92,20 @@
 
 	ts = malloc(sizeof(s3c24xx_ts_t));
-	if (ts == NULL)
+	if (ts == NULL) {
+		loc_server_unregister(srv);
 		return -1;
-
-	if (s3c24xx_ts_init(ts) != EOK)
+	}
+
+	if (s3c24xx_ts_init(ts) != EOK) {
+		free(ts);
+		loc_server_unregister(srv);
 		return -1;
-
-	rc = loc_service_register(NAMESPACE "/mouse", &ts->service_id);
+	}
+
+	rc = loc_service_register(srv, NAMESPACE "/mouse", &ts->service_id);
 	if (rc != EOK) {
+		// XXX s3c24xx_ts_fini();
+		free(ts);
+		loc_server_unregister(srv);
 		printf(NAME ": Unable to register device %s.\n",
 		    NAMESPACE "/mouse");
Index: uspace/srv/hw/char/s3c24xx_uart/s3c24xx_uart.c
===================================================================
--- uspace/srv/hw/char/s3c24xx_uart/s3c24xx_uart.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/hw/char/s3c24xx_uart/s3c24xx_uart.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2010 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -80,8 +80,10 @@
 int main(int argc, char *argv[])
 {
+	loc_srv_t *srv;
+
 	printf("%s: S3C24xx on-chip UART driver\n", NAME);
 
 	async_set_fallback_port_handler(s3c24xx_uart_connection, uart);
-	errno_t rc = loc_server_register(NAME);
+	errno_t rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		printf("%s: Unable to register server.\n", NAME);
@@ -90,12 +92,20 @@
 
 	uart = malloc(sizeof(s3c24xx_uart_t));
-	if (uart == NULL)
-		return -1;
-
-	if (s3c24xx_uart_init(uart) != EOK)
-		return -1;
-
-	rc = loc_service_register(NAMESPACE "/" NAME, &uart->service_id);
+	if (uart == NULL) {
+		loc_server_unregister(srv);
+		return -1;
+	}
+
+	if (s3c24xx_uart_init(uart) != EOK) {
+		free(uart);
+		loc_server_unregister(srv);
+		return -1;
+	}
+
+	rc = loc_service_register(srv, NAMESPACE "/" NAME, &uart->service_id);
 	if (rc != EOK) {
+		// XXX s3c24xx_uart_fini(uart);
+		free(uart);
+		loc_server_unregister(srv);
 		printf(NAME ": Unable to register device %s.\n",
 		    NAMESPACE "/" NAME);
Index: uspace/srv/locsrv/locsrv.c
===================================================================
--- uspace/srv/locsrv/locsrv.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/locsrv/locsrv.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -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);
@@ -1376,7 +1379,4 @@
 
 	cat = category_new("irc");
-	categ_dir_add_cat(&cdir, cat);
-
-	cat = category_new("renderer");
 	categ_dir_add_cat(&cdir, cat);
 
Index: uspace/srv/net/dhcp/dhcp.c
===================================================================
--- uspace/srv/net/dhcp/dhcp.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/net/dhcp/dhcp.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -47,4 +47,5 @@
 #include <io/log.h>
 #include <loc.h>
+#include <rndgen.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -95,4 +96,6 @@
 	/** DNS server */
 	inet_addr_t dns_server;
+	/** Transaction ID */
+	uint32_t xid;
 } dhcp_offer_t;
 
@@ -114,4 +117,6 @@
 	/** Last received offer */
 	dhcp_offer_t offer;
+	/** Random number generator */
+	rndgen_t *rndgen;
 } dhcp_link_t;
 
@@ -154,4 +159,11 @@
 	dhcp_hdr_t *hdr = (dhcp_hdr_t *)msgbuf;
 	uint8_t *opt = msgbuf + sizeof(dhcp_hdr_t);
+	uint32_t xid;
+	errno_t rc;
+	size_t i;
+
+	rc = rndgen_uint32(dlink->rndgen, &xid);
+	if (rc != EOK)
+		return rc;
 
 	memset(msgbuf, 0, MAX_MSG_SIZE);
@@ -159,16 +171,25 @@
 	hdr->htype = 1; /* AHRD_ETHERNET */
 	hdr->hlen = ETH_ADDR_SIZE;
-	hdr->xid = host2uint32_t_be(42);
-	hdr->flags = flag_broadcast;
+	hdr->xid = host2uint32_t_be(xid);
+	hdr->flags = host2uint16_t_be(flag_broadcast);
 
 	eth_addr_encode(&dlink->link_info.mac_addr, hdr->chaddr);
 	hdr->opt_magic = host2uint32_t_be(dhcp_opt_magic);
 
-	opt[0] = opt_msg_type;
-	opt[1] = 1;
-	opt[2] = msg_dhcpdiscover;
-	opt[3] = opt_end;
-
-	return dhcp_send(&dlink->dt, msgbuf, sizeof(dhcp_hdr_t) + 4);
+	i = 0;
+
+	opt[i++] = opt_msg_type;
+	opt[i++] = 1;
+	opt[i++] = msg_dhcpdiscover;
+
+	opt[i++] = opt_param_req_list;
+	opt[i++] = 3;
+	opt[i++] = 1; /* subnet mask */
+	opt[i++] = 6; /* DNS server */
+	opt[i++] = 3; /* router */
+
+	opt[i++] = opt_end;
+
+	return dhcp_send(&dlink->dt, msgbuf, sizeof(dhcp_hdr_t) + i);
 }
 
@@ -183,7 +204,6 @@
 	hdr->htype = 1; /* AHRD_ETHERNET */
 	hdr->hlen = 6;
-	hdr->xid = host2uint32_t_be(42);
-	hdr->flags = flag_broadcast;
-	hdr->ciaddr = host2uint32_t_be(offer->oaddr.addr);
+	hdr->xid = host2uint32_t_be(offer->xid);
+	hdr->flags = host2uint16_t_be(flag_broadcast);
 	eth_addr_encode(&dlink->link_info.mac_addr, hdr->chaddr);
 	hdr->opt_magic = host2uint32_t_be(dhcp_opt_magic);
@@ -258,4 +278,5 @@
 
 	inet_naddr_set(yiaddr.addr, 0, &offer->oaddr);
+	offer->xid = uint32_t_be2host(hdr->xid);
 
 	msgb = (uint8_t *)msg;
@@ -457,4 +478,8 @@
 		return ENOMEM;
 
+	rc = rndgen_create(&dlink->rndgen);
+	if (rc != EOK)
+		goto error;
+
 	dlink->link_id = link_id;
 	dlink->timeout = fibril_timer_create(NULL);
@@ -494,4 +519,6 @@
 	return EOK;
 error:
+	if (dlink != NULL && dlink->rndgen != NULL)
+		rndgen_destroy(dlink->rndgen);
 	if (dlink != NULL && dlink->timeout != NULL)
 		fibril_timer_destroy(dlink->timeout);
Index: uspace/srv/net/dhcp/dhcp_std.h
===================================================================
--- uspace/srv/net/dhcp/dhcp_std.h	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/net/dhcp/dhcp_std.h	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2013 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -87,5 +87,5 @@
 /** Values for dhcp_hdr_t.flags */
 enum dhcp_flags {
-	flag_broadcast = 0x80
+	flag_broadcast = 0x8000
 };
 
@@ -110,4 +110,6 @@
 	/** Server identifier */
 	opt_server_id = 54,
+	/** Parameter request list */
+	opt_param_req_list = 55,
 	/** End */
 	opt_end = 255
Index: uspace/srv/net/dhcp/main.c
===================================================================
--- uspace/srv/net/dhcp/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/net/dhcp/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2013 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -54,4 +54,5 @@
 static errno_t dhcp_init(void)
 {
+	loc_srv_t *srv;
 	errno_t rc;
 
@@ -68,5 +69,5 @@
 	async_set_fallback_port_handler(dhcp_client_conn, NULL);
 
-	rc = loc_server_register(NAME);
+	rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering server: %s.", str_error(rc));
@@ -75,6 +76,7 @@
 
 	service_id_t sid;
-	rc = loc_service_register(SERVICE_NAME_DHCP, &sid);
+	rc = loc_service_register(srv, SERVICE_NAME_DHCP, &sid);
 	if (rc != EOK) {
+		loc_server_unregister(srv);
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering service: %s.", str_error(rc));
 		return EEXIST;
Index: uspace/srv/net/dnsrsrv/dnsrsrv.c
===================================================================
--- uspace/srv/net/dnsrsrv/dnsrsrv.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/net/dnsrsrv/dnsrsrv.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2013 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -57,4 +57,5 @@
 static errno_t dnsr_init(void)
 {
+	loc_srv_t *srv;
 	errno_t rc;
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "dnsr_init()");
@@ -68,5 +69,5 @@
 	async_set_fallback_port_handler(dnsr_client_conn, NULL);
 
-	rc = loc_server_register(NAME);
+	rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering server: %s.", str_error(rc));
@@ -76,6 +77,7 @@
 
 	service_id_t sid;
-	rc = loc_service_register(SERVICE_NAME_DNSR, &sid);
-	if (rc != EOK) {
+	rc = loc_service_register(srv, SERVICE_NAME_DNSR, &sid);
+	if (rc != EOK) {
+		loc_server_unregister(srv);
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering service: %s.", str_error(rc));
 		transport_fini();
Index: uspace/srv/net/ethip/ethip.c
===================================================================
--- uspace/srv/net/ethip/ethip.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/net/ethip/ethip.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -78,9 +78,11 @@
 };
 
+static loc_srv_t *ethip_srv;
+
 static errno_t ethip_init(void)
 {
 	async_set_fallback_port_handler(ethip_client_conn, NULL);
 
-	errno_t rc = loc_server_register(NAME);
+	errno_t rc = loc_server_register(NAME, &ethip_srv);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering server.");
@@ -115,5 +117,5 @@
 	}
 
-	rc = loc_service_register(svc_name, &sid);
+	rc = loc_service_register(ethip_srv, svc_name, &sid);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering service %s.", svc_name);
@@ -129,5 +131,5 @@
 	}
 
-	rc = loc_service_add_to_cat(sid, iplink_cat);
+	rc = loc_service_add_to_cat(ethip_srv, sid, iplink_cat);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed adding %s to category.", svc_name);
Index: uspace/srv/net/inetsrv/inetsrv.c
===================================================================
--- uspace/srv/net/inetsrv/inetsrv.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/net/inetsrv/inetsrv.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2012 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -86,4 +86,6 @@
 static errno_t inet_init(void)
 {
+	loc_srv_t *srv;
+
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "inet_init()");
 
@@ -104,5 +106,5 @@
 		return rc;
 
-	rc = loc_server_register(NAME);
+	rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering server: %s.", str_error(rc));
@@ -111,6 +113,7 @@
 
 	service_id_t sid;
-	rc = loc_service_register(SERVICE_NAME_INET, &sid);
-	if (rc != EOK) {
+	rc = loc_service_register(srv, SERVICE_NAME_INET, &sid);
+	if (rc != EOK) {
+		loc_server_unregister(srv);
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering service: %s.", str_error(rc));
 		return EEXIST;
Index: uspace/srv/net/loopip/loopip.c
===================================================================
--- uspace/srv/net/loopip/loopip.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/net/loopip/loopip.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -103,7 +103,9 @@
 static errno_t loopip_init(void)
 {
+	loc_srv_t *srv;
+
 	async_set_fallback_port_handler(loopip_client_conn, NULL);
 
-	errno_t rc = loc_server_register(NAME);
+	errno_t rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering server.");
@@ -119,6 +121,7 @@
 	const char *svc_name = "net/loopback";
 	service_id_t sid;
-	rc = loc_service_register(svc_name, &sid);
-	if (rc != EOK) {
+	rc = loc_service_register(srv, svc_name, &sid);
+	if (rc != EOK) {
+		loc_server_unregister(srv);
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering service %s.",
 		    svc_name);
@@ -129,10 +132,14 @@
 	rc = loc_category_get_id("iplink", &iplink_cat, IPC_FLAG_BLOCKING);
 	if (rc != EOK) {
+		loc_service_unregister(srv, sid);
+		loc_server_unregister(srv);
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed resolving category 'iplink'.");
 		return rc;
 	}
 
-	rc = loc_service_add_to_cat(sid, iplink_cat);
-	if (rc != EOK) {
+	rc = loc_service_add_to_cat(srv, sid, iplink_cat);
+	if (rc != EOK) {
+		loc_service_unregister(srv, sid);
+		loc_server_unregister(srv);
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed adding %s to category.",
 		    svc_name);
@@ -141,6 +148,9 @@
 
 	fid_t fid = fibril_create(loopip_recv_fibril, NULL);
-	if (fid == 0)
-		return ENOMEM;
+	if (fid == 0) {
+		loc_service_unregister(srv, sid);
+		loc_server_unregister(srv);
+		return ENOMEM;
+	}
 
 	fibril_add_ready(fid);
Index: uspace/srv/net/nconfsrv/nconfsrv.c
===================================================================
--- uspace/srv/net/nconfsrv/nconfsrv.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/net/nconfsrv/nconfsrv.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2013 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -59,4 +59,5 @@
 {
 	service_id_t sid;
+	loc_srv_t *srv;
 	errno_t rc;
 
@@ -79,5 +80,5 @@
 	async_set_fallback_port_handler(ncs_client_conn, NULL);
 
-	rc = loc_server_register(NAME);
+	rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering server: %s.", str_error(rc));
@@ -85,6 +86,7 @@
 	}
 
-	rc = loc_service_register(SERVICE_NAME_NETCONF, &sid);
+	rc = loc_service_register(srv, SERVICE_NAME_NETCONF, &sid);
 	if (rc != EOK) {
+		loc_server_unregister(srv);
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering service: %s.", str_error(rc));
 		return EEXIST;
@@ -92,6 +94,9 @@
 
 	rc = ncs_link_discovery_start();
-	if (rc != EOK)
+	if (rc != EOK) {
+		loc_service_unregister(srv, sid);
+		loc_server_unregister(srv);
 		return EEXIST;
+	}
 
 	return EOK;
Index: uspace/srv/net/slip/slip.c
===================================================================
--- uspace/srv/net/slip/slip.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/net/slip/slip.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * Copyright (c) 2013 Jakub Jermar
  * All rights reserved.
@@ -307,4 +307,5 @@
 	chardev_t *chardev_out = NULL;
 	fid_t fid;
+	loc_srv_t *srv;
 	errno_t rc;
 
@@ -314,5 +315,5 @@
 	async_set_fallback_port_handler(slip_client_conn, NULL);
 
-	rc = loc_server_register(NAME);
+	rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR,
@@ -323,4 +324,5 @@
 	rc = loc_service_get_id(svcstr, &svcid, 0);
 	if (rc != EOK) {
+		loc_server_unregister(srv);
 		log_msg(LOG_DEFAULT, LVL_ERROR,
 		    "Failed getting ID for service %s", svcstr);
@@ -330,4 +332,5 @@
 	rc = loc_category_get_id(CAT_IPLINK, &iplinkcid, 0);
 	if (rc != EOK) {
+		loc_server_unregister(srv);
 		log_msg(LOG_DEFAULT, LVL_ERROR,
 		    "Failed to get category ID for %s",
@@ -342,4 +345,5 @@
 	sess_out = loc_service_connect(svcid, INTERFACE_DDF, 0);
 	if (!sess_out) {
+		loc_server_unregister(srv);
 		log_msg(LOG_DEFAULT, LVL_ERROR,
 		    "Failed to connect to service %s (ID=%d)",
@@ -350,4 +354,5 @@
 	rc = chardev_open(sess_out, &chardev_out);
 	if (rc != EOK) {
+		loc_server_unregister(srv);
 		log_msg(LOG_DEFAULT, LVL_ERROR,
 		    "Failed opening character device.");
@@ -373,5 +378,5 @@
 	}
 
-	rc = loc_service_register(linkstr, &linksid);
+	rc = loc_service_register(srv, linkstr, &linksid);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR,
@@ -381,6 +386,7 @@
 	}
 
-	rc = loc_service_add_to_cat(linksid, iplinkcid);
-	if (rc != EOK) {
+	rc = loc_service_add_to_cat(srv, linksid, iplinkcid);
+	if (rc != EOK) {
+		loc_service_unregister(srv, linksid);
 		log_msg(LOG_DEFAULT, LVL_ERROR,
 		    "Failed to add service %d (%s) to category %d (%s).",
@@ -401,4 +407,5 @@
 
 fail:
+	loc_server_unregister(srv);
 	chardev_close(chardev_out);
 	if (sess_out)
Index: uspace/srv/net/tcp/service.c
===================================================================
--- uspace/srv/net/tcp/service.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/net/tcp/service.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2015 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -1242,8 +1242,9 @@
 	errno_t rc;
 	service_id_t sid;
+	loc_srv_t *srv;
 
 	async_set_fallback_port_handler(tcp_client_conn, NULL);
 
-	rc = loc_server_register(NAME);
+	rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering server.");
@@ -1251,6 +1252,7 @@
 	}
 
-	rc = loc_service_register(SERVICE_NAME_TCP, &sid);
-	if (rc != EOK) {
+	rc = loc_service_register(srv, SERVICE_NAME_TCP, &sid);
+	if (rc != EOK) {
+		loc_server_unregister(srv);
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering service.");
 		return EIO;
Index: uspace/srv/net/udp/assoc.c
===================================================================
--- uspace/srv/net/udp/assoc.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/net/udp/assoc.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -243,5 +243,6 @@
  *
  * @param assoc		Association
- * @param remote	Remote endpoint or NULL not to override @a assoc
+ * @param remote	Remote endpoint or inet_addr_any/inet_port_any
+ *			not to override association's remote endpoint
  * @param msg		Message
  *
@@ -261,5 +262,6 @@
 	/* @a remote can be used to override the remote endpoint */
 	epp = assoc->ident;
-	if (remote != NULL)
+	if (!inet_addr_is_any(&remote->addr) &&
+	    remote->port != inet_port_any)
 		epp.remote = *remote;
 
Index: uspace/srv/net/udp/service.c
===================================================================
--- uspace/srv/net/udp/service.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/net/udp/service.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2015 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -647,8 +647,9 @@
 	errno_t rc;
 	service_id_t sid;
+	loc_srv_t *srv;
 
 	async_set_fallback_port_handler(udp_client_conn, NULL);
 
-	rc = loc_server_register(NAME);
+	rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering server.");
@@ -656,6 +657,7 @@
 	}
 
-	rc = loc_service_register(SERVICE_NAME_UDP, &sid);
-	if (rc != EOK) {
+	rc = loc_service_register(srv, SERVICE_NAME_UDP, &sid);
+	if (rc != EOK) {
+		loc_server_unregister(srv);
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering service.");
 		return EIO;
Index: uspace/srv/net/udp/test/assoc.c
===================================================================
--- uspace/srv/net/udp/test/assoc.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/net/udp/test/assoc.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -250,11 +250,12 @@
 }
 
-/** Sending message with destination set in association and NULL destination
- * argument
+/** Sending message with destination set in association and inet_addr_any /
+ * inet_port_any destination argument
  */
-PCUT_TEST(send_assoc_null_dest)
-{
-	udp_assoc_t *assoc;
-	inet_ep2_t epp;
+PCUT_TEST(send_assoc_any_dest)
+{
+	udp_assoc_t *assoc;
+	inet_ep2_t epp;
+	inet_ep_t ep;
 	errno_t rc;
 	udp_msg_t *msg;
@@ -272,4 +273,6 @@
 	epp.local.port = 1;
 
+	inet_ep_init(&ep);
+
 	assoc = udp_assoc_new(&epp, &test_assoc_cb, NULL);
 	PCUT_ASSERT_NOT_NULL(assoc);
@@ -281,5 +284,5 @@
 	sent_msg = NULL;
 
-	rc = udp_assoc_send(assoc, NULL, msg);
+	rc = udp_assoc_send(assoc, &ep, msg);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_EQUALS(msg, sent_msg);
@@ -311,6 +314,4 @@
 
 	inet_ep2_init(&epp);
-	inet_addr(&epp.remote.addr, 127, 0, 0, 1);
-	epp.remote.port = 42;
 	inet_addr(&epp.local.addr, 127, 0, 0, 1);
 	epp.local.port = 1;
Index: uspace/srv/ns/service.c
===================================================================
--- uspace/srv/ns/service.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/ns/service.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -112,5 +112,5 @@
 
 /** Operations for service hash table. */
-static hash_table_ops_t service_hash_table_ops = {
+static const hash_table_ops_t service_hash_table_ops = {
 	.hash = service_hash,
 	.key_hash = service_key_hash,
@@ -121,5 +121,5 @@
 
 /** Operations for interface hash table. */
-static hash_table_ops_t iface_hash_table_ops = {
+static const hash_table_ops_t iface_hash_table_ops = {
 	.hash = iface_hash,
 	.key_hash = iface_key_hash,
Index: uspace/srv/ns/task.c
===================================================================
--- uspace/srv/ns/task.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/ns/task.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -80,5 +80,5 @@
 
 /** Operations for task hash table. */
-static hash_table_ops_t task_hash_table_ops = {
+static const hash_table_ops_t task_hash_table_ops = {
 	.hash = task_hash,
 	.key_hash = task_key_hash,
@@ -131,5 +131,5 @@
 
 /** Operations for task hash table. */
-static hash_table_ops_t p2i_ops = {
+static const hash_table_ops_t p2i_ops = {
 	.hash = p2i_hash,
 	.key_hash = p2i_key_hash,
Index: uspace/srv/taskmon/meson.build
===================================================================
--- uspace/srv/taskmon/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/taskmon/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 #
-# Copyright (c) 2010 Jiri Svoboda
+# Copyright (c) 2023 Jiri Svoboda
 # All rights reserved.
 #
@@ -27,3 +27,4 @@
 #
 
+deps = [ 'corecfg' ]
 src = files('taskmon.c')
Index: uspace/srv/taskmon/taskmon.c
===================================================================
--- uspace/srv/taskmon/taskmon.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/taskmon/taskmon.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2013 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -136,4 +136,6 @@
 int main(int argc, char *argv[])
 {
+	loc_srv_t *srv;
+
 	printf("%s: Task Monitoring Service\n", NAME);
 
@@ -150,5 +152,5 @@
 	async_set_fallback_port_handler(corecfg_client_conn, NULL);
 
-	errno_t rc = loc_server_register(NAME);
+	errno_t rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		printf("%s: Failed registering server: %s.\n",
@@ -158,6 +160,7 @@
 
 	service_id_t sid;
-	rc = loc_service_register(SERVICE_NAME_CORECFG, &sid);
+	rc = loc_service_register(srv, SERVICE_NAME_CORECFG, &sid);
 	if (rc != EOK) {
+		loc_server_unregister(srv);
 		printf("%s: Failed registering service: %s.\n",
 		    NAME, str_error(rc));
Index: uspace/srv/test/chardev-test/main.c
===================================================================
--- uspace/srv/test/chardev-test/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/test/chardev-test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2017 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -118,9 +118,10 @@
 {
 	errno_t rc;
+	loc_srv_t *srv;
 
 	printf("%s: Character device test service\n", NAME);
 	async_set_fallback_port_handler(chardev_test_connection, NULL);
 
-	rc = loc_server_register(NAME);
+	rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		printf("%s: Failed registering server.: %s\n", NAME, str_error(rc));
@@ -140,20 +141,23 @@
 	partialx_srvs.sarg = NULL;
 
-	rc = loc_service_register(SERVICE_NAME_CHARDEV_TEST_SMALLX, &smallx_svc_id);
+	rc = loc_service_register(srv, SERVICE_NAME_CHARDEV_TEST_SMALLX,
+	    &smallx_svc_id);
 	if (rc != EOK) {
 		printf("%s: Failed registering service.: %s\n", NAME, str_error(rc));
-		return rc;
-	}
-
-	rc = loc_service_register(SERVICE_NAME_CHARDEV_TEST_LARGEX, &largex_svc_id);
+		goto error;
+	}
+
+	rc = loc_service_register(srv, SERVICE_NAME_CHARDEV_TEST_LARGEX,
+	    &largex_svc_id);
 	if (rc != EOK) {
 		printf("%s: Failed registering service.: %s\n", NAME, str_error(rc));
-		return rc;
-	}
-
-	rc = loc_service_register(SERVICE_NAME_CHARDEV_TEST_PARTIALX, &partialx_svc_id);
+		goto error;
+	}
+
+	rc = loc_service_register(srv, SERVICE_NAME_CHARDEV_TEST_PARTIALX,
+	    &partialx_svc_id);
 	if (rc != EOK) {
 		printf("%s: Failed registering service.: %s\n", NAME, str_error(rc));
-		return rc;
+		goto error;
 	}
 
@@ -164,4 +168,13 @@
 	/* Not reached */
 	return 0;
+error:
+	if (smallx_svc_id != 0)
+		loc_service_unregister(srv, smallx_svc_id);
+	if (largex_svc_id != 0)
+		loc_service_unregister(srv, largex_svc_id);
+	if (partialx_svc_id != 0)
+		loc_service_unregister(srv, partialx_svc_id);
+	loc_server_unregister(srv);
+	return rc;
 }
 
Index: uspace/srv/test/ipc-test/main.c
===================================================================
--- uspace/srv/test/ipc-test/main.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/test/ipc-test/main.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2018 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -48,4 +48,5 @@
 #include <mem.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <task.h>
 
@@ -53,4 +54,8 @@
 
 static service_id_t svc_id;
+
+enum {
+	max_rw_buf_size = 16384,
+};
 
 /** Object in read-only memory area that will be shared.
@@ -67,4 +72,12 @@
  */
 static char rw_data[] = "Hello, world!";
+
+/** Buffer for reading/writing via read/write messages.
+ *
+ * It is allocated / size is set by IPC_TEST_SET_RW_BUF_SIZE
+ */
+static void *rw_buf;
+/** Read/write buffer size */
+size_t rw_buf_size;
 
 static void ipc_test_get_ro_area_size_srv(ipc_call_t *icall)
@@ -182,4 +195,103 @@
 }
 
+static void ipc_test_set_rw_buf_size_srv(ipc_call_t *icall)
+{
+	size_t size;
+	void *nbuf;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "ipc_test_set_rw_buf_size_srv");
+
+	size = ipc_get_arg1(icall);
+
+	if (size == 0) {
+		async_answer_0(icall, ERANGE);
+		log_msg(LOG_DEFAULT, LVL_ERROR,
+		    "Requested read/write buffer size is zero.");
+		return;
+	}
+
+	if (size > max_rw_buf_size) {
+		async_answer_0(icall, ERANGE);
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Requested read/write buffer "
+		    "size > %u", max_rw_buf_size);
+		return;
+	}
+
+	nbuf = realloc(rw_buf, size);
+	if (nbuf == NULL) {
+		async_answer_0(icall, ENOMEM);
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Out of memory.");
+		return;
+	}
+
+	rw_buf = nbuf;
+	rw_buf_size = size;
+	async_answer_0(icall, EOK);
+}
+
+static void ipc_test_read_srv(ipc_call_t *icall)
+{
+	ipc_call_t call;
+	errno_t rc;
+	size_t size;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "ipc_test_read_srv");
+
+	if (!async_data_read_receive(&call, &size)) {
+		async_answer_0(icall, EREFUSED);
+		log_msg(LOG_DEFAULT, LVL_ERROR, "data_read_receive failed");
+		return;
+	}
+
+	if (size > rw_buf_size) {
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Invalid read size.");
+		return;
+	}
+
+	rc = async_data_read_finalize(&call, rw_buf, size);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR,
+		    "data_read_finalize failed");
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	async_answer_0(icall, EOK);
+}
+
+static void ipc_test_write_srv(ipc_call_t *icall)
+{
+	ipc_call_t call;
+	errno_t rc;
+	size_t size;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "ipc_test_write_srv");
+
+	if (!async_data_write_receive(&call, &size)) {
+		async_answer_0(icall, EREFUSED);
+		log_msg(LOG_DEFAULT, LVL_ERROR, "data_write_receive failed");
+		return;
+	}
+
+	if (size > rw_buf_size) {
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Invalid write size.");
+		return;
+	}
+
+	rc = async_data_write_finalize(&call, rw_buf, size);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR,
+		    "data_write_finalize failed");
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	async_answer_0(icall, EOK);
+}
+
 static void ipc_test_connection(ipc_call_t *icall, void *arg)
 {
@@ -212,4 +324,13 @@
 			ipc_test_share_in_rw_srv(&call);
 			break;
+		case IPC_TEST_SET_RW_BUF_SIZE:
+			ipc_test_set_rw_buf_size_srv(&call);
+			break;
+		case IPC_TEST_READ:
+			ipc_test_read_srv(&call);
+			break;
+		case IPC_TEST_WRITE:
+			ipc_test_write_srv(&call);
+			break;
 		default:
 			async_answer_0(&call, ENOTSUP);
@@ -222,4 +343,5 @@
 {
 	errno_t rc;
+	loc_srv_t *srv;
 
 	printf("%s: IPC test service\n", NAME);
@@ -232,5 +354,5 @@
 	}
 
-	rc = loc_server_register(NAME);
+	rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering server. (%s)\n",
@@ -239,6 +361,7 @@
 	}
 
-	rc = loc_service_register(SERVICE_NAME_IPC_TEST, &svc_id);
-	if (rc != EOK) {
+	rc = loc_service_register(srv, SERVICE_NAME_IPC_TEST, &svc_id);
+	if (rc != EOK) {
+		loc_server_unregister(srv);
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering service. (%s)\n",
 		    str_error(rc));
Index: uspace/srv/test/ipc-test/meson.build
===================================================================
--- uspace/srv/test/ipc-test/meson.build	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/test/ipc-test/meson.build	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 #
-# Copyright (c) 2018 Jiri Svoboda
+# Copyright (c) 2023 Jiri Svoboda
 # All rights reserved.
 #
@@ -27,3 +27,4 @@
 #
 
+deps = [ 'ipctest' ]
 src = files('main.c')
Index: uspace/srv/vfs/vfs_node.c
===================================================================
--- uspace/srv/vfs/vfs_node.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/vfs/vfs_node.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -66,5 +66,5 @@
 
 /** VFS node hash table operations. */
-hash_table_ops_t nodes_ops = {
+const hash_table_ops_t nodes_ops = {
 	.hash = nodes_hash,
 	.key_hash = nodes_key_hash,
Index: uspace/srv/volsrv/empty.c
===================================================================
--- uspace/srv/volsrv/empty.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/volsrv/empty.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2015 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -62,5 +62,5 @@
 	label_bd_t lbd;
 
-	rc = block_init(sid, 2048);
+	rc = block_init(sid);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Error opening "
@@ -83,5 +83,5 @@
 	label_bd_t lbd;
 
-	rc = block_init(sid, 2048);
+	rc = block_init(sid);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Error opening "
Index: uspace/srv/volsrv/volsrv.c
===================================================================
--- uspace/srv/volsrv/volsrv.c	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ uspace/srv/volsrv/volsrv.c	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2015 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -62,4 +62,5 @@
 	vol_volumes_t *volumes = NULL;
 	vol_parts_t *parts = NULL;
+	loc_srv_t *srv = NULL;
 
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "vol_init()");
@@ -79,5 +80,5 @@
 	async_set_fallback_port_handler(vol_client_conn, parts);
 
-	rc = loc_server_register(NAME);
+	rc = loc_server_register(NAME, &srv);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering server: %s.", str_error(rc));
@@ -86,5 +87,5 @@
 
 	service_id_t sid;
-	rc = loc_service_register(SERVICE_NAME_VOLSRV, &sid);
+	rc = loc_service_register(srv, SERVICE_NAME_VOLSRV, &sid);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering service: %s.", str_error(rc));
@@ -95,4 +96,6 @@
 	return EOK;
 error:
+	if (srv != NULL)
+		loc_server_unregister(srv);
 	vol_volumes_destroy(volumes);
 	vol_parts_destroy(parts);
Index: version
===================================================================
--- version	(revision 8a9a41e01a710f4ef36a33613b89d0973f674280)
+++ version	(revision 6955907c088fa03863559fbf2f191e184d0109a3)
@@ -28,5 +28,5 @@
 
 
-HELENOS_RELEASE = 0.11.1
-HELENOS_CODENAME = Jubilee
-HELENOS_COPYRIGHT = Copyright (c) 2001-2021 HelenOS project
+HELENOS_RELEASE = 0.14.1
+HELENOS_CODENAME = Aladar
+HELENOS_COPYRIGHT = Copyright (c) 2001-2024 HelenOS project
