Index: uspace/lib/ui/include/types/ui/wdecor.h
===================================================================
--- uspace/lib/ui/include/types/ui/wdecor.h	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
+++ uspace/lib/ui/include/types/ui/wdecor.h	(revision 1af103e4973625ad986fb43b4eea1bcd9414c3c1)
@@ -38,4 +38,5 @@
 
 #include <gfx/coord.h>
+#include <types/common.h>
 #include <types/ui/cursor.h>
 
@@ -51,14 +52,16 @@
 	/** Window has a title bar */
 	ui_wds_titlebar = 0x2,
+	/** Window has a system menu */
+	ui_wds_sysmenu = 0x4,
 	/** Window has a minimize button */
-	ui_wds_minimize_btn = 0x4,
+	ui_wds_minimize_btn = 0x8,
 	/** Window has a maximize button */
-	ui_wds_maximize_btn = 0x8,
+	ui_wds_maximize_btn = 0x10,
 	/** Window has a close button */
-	ui_wds_close_btn = 0x10,
+	ui_wds_close_btn = 0x20,
 	/** Window is resizable */
-	ui_wds_resizable = 0x20,
+	ui_wds_resizable = 0x40,
 	/** Window is decorated (default decoration) */
-	ui_wds_decorated = ui_wds_frame | ui_wds_titlebar |
+	ui_wds_decorated = ui_wds_frame | ui_wds_titlebar | ui_wds_sysmenu |
 	    ui_wds_minimize_btn | ui_wds_close_btn
 } ui_wdecor_style_t;
@@ -81,4 +84,5 @@
 /** Window decoration callbacks */
 typedef struct ui_wdecor_cb {
+	void (*sysmenu)(ui_wdecor_t *, void *, sysarg_t);
 	void (*minimize)(ui_wdecor_t *, void *);
 	void (*maximize)(ui_wdecor_t *, void *);
Index: uspace/lib/ui/include/types/ui/window.h
===================================================================
--- uspace/lib/ui/include/types/ui/window.h	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
+++ uspace/lib/ui/include/types/ui/window.h	(revision 1af103e4973625ad986fb43b4eea1bcd9414c3c1)
@@ -40,4 +40,5 @@
 #include <io/kbd_event.h>
 #include <io/pos_event.h>
+#include <types/common.h>
 #include <types/ui/wdecor.h>
 
@@ -95,4 +96,5 @@
 /** 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 *);
Index: uspace/lib/ui/include/ui/wdecor.h
===================================================================
--- uspace/lib/ui/include/ui/wdecor.h	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
+++ uspace/lib/ui/include/ui/wdecor.h	(revision 1af103e4973625ad986fb43b4eea1bcd9414c3c1)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2022 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,4 +39,5 @@
 #include <errno.h>
 #include <gfx/coord.h>
+#include <io/kbd_event.h>
 #include <io/pos_event.h>
 #include <stdbool.h>
@@ -54,4 +55,5 @@
 extern errno_t ui_wdecor_set_caption(ui_wdecor_t *, const char *);
 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 *,
Index: uspace/lib/ui/include/ui/window.h
===================================================================
--- uspace/lib/ui/include/ui/window.h	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
+++ uspace/lib/ui/include/ui/window.h	(revision 1af103e4973625ad986fb43b4eea1bcd9414c3c1)
@@ -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>
@@ -65,4 +66,5 @@
 extern void ui_window_set_ctl_cursor(ui_window_t *, ui_stock_cursor_t);
 extern errno_t ui_window_paint(ui_window_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 *);
Index: uspace/lib/ui/private/wdecor.h
===================================================================
--- uspace/lib/ui/private/wdecor.h	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
+++ uspace/lib/ui/private/wdecor.h	(revision 1af103e4973625ad986fb43b4eea1bcd9414c3c1)
@@ -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>
@@ -94,4 +95,5 @@
 } ui_wdecor_geom_t;
 
+extern void ui_wdecor_sysmenu(ui_wdecor_t *, sysarg_t);
 extern void ui_wdecor_minimize(ui_wdecor_t *);
 extern void ui_wdecor_maximize(ui_wdecor_t *);
Index: uspace/lib/ui/private/window.h
===================================================================
--- uspace/lib/ui/private/window.h	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
+++ uspace/lib/ui/private/window.h	(revision 1af103e4973625ad986fb43b4eea1bcd9414c3c1)
@@ -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>
@@ -93,4 +94,6 @@
 	/** Window decoration */
 	struct ui_wdecor *wdecor;
+	/** System menu */
+	struct ui_menu *sysmenu;
 	/** Top-level control in the application area */
 	struct ui_control *control;
@@ -112,4 +115,5 @@
 
 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 *);
Index: uspace/lib/ui/src/wdecor.c
===================================================================
--- uspace/lib/ui/src/wdecor.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
+++ uspace/lib/ui/src/wdecor.c	(revision 1af103e4973625ad986fb43b4eea1bcd9414c3c1)
@@ -421,4 +421,15 @@
 }
 
+/** Send decoration sysmenu event.
+ *
+ * @param wdecor Window decoration
+ * @param idev_id Input device ID
+ */
+void ui_wdecor_sysmenu(ui_wdecor_t *wdecor, sysarg_t idev_id)
+{
+	if (wdecor->cb != NULL && wdecor->cb->sysmenu != NULL)
+		wdecor->cb->sysmenu(wdecor, wdecor->arg, idev_id);
+}
+
 /** Send decoration minimize event.
  *
@@ -805,4 +816,23 @@
 }
 
+/** 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)
+{
+	(void)wdecor;
+
+	if (event->type == KEY_PRESS && (event->mods & (KM_CTRL | KM_ALT |
+	    KM_SHIFT)) == 0) {
+		if (event->key == KC_F9)
+			ui_wdecor_sysmenu(wdecor, event->kbd_id);
+	}
+
+	return ui_unclaimed;
+}
+
 /** Handle window frame position event.
  *
Index: uspace/lib/ui/src/window.c
===================================================================
--- uspace/lib/ui/src/window.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
+++ uspace/lib/ui/src/window.c	(revision 1af103e4973625ad986fb43b4eea1bcd9414c3c1)
@@ -47,4 +47,6 @@
 #include <stdlib.h>
 #include <ui/control.h>
+#include <ui/menu.h>
+#include <ui/menuentry.h>
 #include <ui/resource.h>
 #include <ui/ui.h>
@@ -74,4 +76,5 @@
 };
 
+static void wd_sysmenu(ui_wdecor_t *, void *, sysarg_t);
 static void wd_minimize(ui_wdecor_t *, void *);
 static void wd_maximize(ui_wdecor_t *, void *);
@@ -84,4 +87,5 @@
 
 static ui_wdecor_cb_t wdecor_cb = {
+	.sysmenu = wd_sysmenu,
 	.minimize = wd_minimize,
 	.maximize = wd_maximize,
@@ -92,4 +96,18 @@
 	.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_eclose(ui_menu_entry_t *, void *);
 
 static void ui_window_invalidate(void *, gfx_rect_t *);
@@ -417,4 +435,5 @@
 	list_remove(&window->lwindows);
 	ui_control_destroy(window->control);
+	ui_menu_destroy(window->sysmenu);
 	ui_wdecor_destroy(window->wdecor);
 	ui_resource_destroy(window->res);
@@ -929,4 +948,17 @@
 }
 
+/** 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(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 window minimization.
  *
@@ -1070,4 +1102,17 @@
 }
 
+/** 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.
  *
@@ -1174,4 +1219,49 @@
 	else
 		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_menu_entry_t *mclose;
+	ui_wdecor_geom_t geom;
+
+	if (window->sysmenu == NULL) {
+		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, "~C~lose", "Alt-F4",
+		    &mclose);
+		if (rc != EOK)
+			goto error;
+
+		ui_menu_entry_set_cb(mclose, wnd_sysmenu_eclose,
+		    (void *)window);
+	}
+
+	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;
 }
 
@@ -1260,6 +1350,13 @@
 ui_evclaim_t ui_window_def_kbd(ui_window_t *window, kbd_event_t *kbd)
 {
+	ui_evclaim_t claim;
+
 	if (window->control != NULL)
-		return 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;
@@ -1316,4 +1413,70 @@
 	if (window->control != NULL)
 		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)
+{
+	(void)sysmenu;
+	(void)arg;
+	(void)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)
+{
+	(void)sysmenu;
+	(void)arg;
+	(void)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)
+{
+	(void)arg;
+
+	ui_menu_close(sysmenu);
+}
+
+/** 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/wdecor.c
===================================================================
--- uspace/lib/ui/test/wdecor.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
+++ uspace/lib/ui/test/wdecor.c	(revision 1af103e4973625ad986fb43b4eea1bcd9414c3c1)
@@ -62,4 +62,5 @@
 };
 
+static void test_wdecor_sysmenu(ui_wdecor_t *, void *, sysarg_t);
 static void test_wdecor_minimize(ui_wdecor_t *, void *);
 static void test_wdecor_maximize(ui_wdecor_t *, void *);
@@ -72,4 +73,5 @@
 
 static ui_wdecor_cb_t test_wdecor_cb = {
+	.sysmenu = test_wdecor_sysmenu,
 	.minimize = test_wdecor_minimize,
 	.maximize = test_wdecor_maximize,
@@ -102,4 +104,5 @@
 
 typedef struct {
+	bool sysmenu;
 	bool minimize;
 	bool maximize;
@@ -109,4 +112,5 @@
 	gfx_coord2_t pos;
 	sysarg_t pos_id;
+	sysarg_t idev_id;
 	bool resize;
 	ui_wdecor_rsztype_t rsztype;
@@ -241,4 +245,32 @@
 	rc = gfx_context_delete(gc);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Test ui_wdecor_sysmenu() */
+PCUT_TEST(sysmenu)
+{
+	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 callback with no callbacks set */
+	ui_wdecor_sysmenu(wdecor, 42);
+
+	/* Sysmenu callback with sysmenu callback not implemented */
+	ui_wdecor_set_cb(wdecor, &dummy_wdecor_cb, NULL);
+	ui_wdecor_sysmenu(wdecor, 42);
+
+	/* Sysmenu callback with real callback set */
+	resp.sysmenu = false;
+	resp.idev_id = 0;
+	ui_wdecor_set_cb(wdecor, &test_wdecor_cb, &resp);
+	ui_wdecor_sysmenu(wdecor, 42);
+	PCUT_ASSERT_TRUE(resp.sysmenu);
+	PCUT_ASSERT_INT_EQUALS(42, resp.idev_id);
+
+	ui_wdecor_destroy(wdecor);
 }
 
@@ -544,4 +576,56 @@
 	PCUT_ASSERT_INT_EQUALS(event.hpos, resp.pos.x);
 	PCUT_ASSERT_INT_EQUALS(event.vpos, resp.pos.y);
+
+	ui_wdecor_destroy(wdecor);
+	ui_resource_destroy(resource);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Pressing F9 generates sysmenu event */
+PCUT_TEST(kbd_f9_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);
+
+	resp.move = false;
+	resp.pos.x = 0;
+	resp.pos.y = 0;
+
+	event.type = KEY_PRESS;
+	event.mods = 0;
+	event.key = KC_F9;
+	event.kbd_id = 42;
+	ui_wdecor_kbd_event(wdecor, &event);
+
+	PCUT_ASSERT_TRUE(resp.sysmenu);
+	PCUT_ASSERT_INT_EQUALS(event.kbd_id, resp.idev_id);
 
 	ui_wdecor_destroy(wdecor);
@@ -1111,4 +1195,13 @@
 }
 
+static void test_wdecor_sysmenu(ui_wdecor_t *wdecor, void *arg,
+    sysarg_t idev_id)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->sysmenu = true;
+	resp->idev_id = idev_id;
+}
+
 static void test_wdecor_minimize(ui_wdecor_t *wdecor, void *arg)
 {
Index: uspace/lib/ui/test/window.c
===================================================================
--- uspace/lib/ui/test/window.c	(revision 46bd63c90e62ae2887633555d123a6aa959d3b05)
+++ uspace/lib/ui/test/window.c	(revision 1af103e4973625ad986fb43b4eea1bcd9414c3c1)
@@ -45,4 +45,5 @@
 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 *);
@@ -56,4 +57,5 @@
 
 static ui_window_cb_t test_window_cb = {
+	.sysmenu = test_window_sysmenu,
 	.minimize = test_window_minimize,
 	.maximize = test_window_maximize,
@@ -82,4 +84,6 @@
 typedef struct {
 	errno_t rc;
+	bool sysmenu;
+	sysarg_t sysmenu_idev_id;
 	bool minimize;
 	bool maximize;
@@ -544,4 +548,42 @@
 }
 
+/** 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)
@@ -894,4 +936,12 @@
 }
 
+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)
 {
