Index: uspace/app/uidemo/uidemo.c
===================================================================
--- uspace/app/uidemo/uidemo.c	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/app/uidemo/uidemo.c	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -609,5 +609,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 */
Index: uspace/lib/display/include/disp_srv.h
===================================================================
--- uspace/lib/display/include/disp_srv.h	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/display/include/disp_srv.h	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -62,5 +62,8 @@
 	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_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 (*get_event)(void *, sysarg_t *, display_wnd_ev_t *);
Index: uspace/lib/display/include/display.h
===================================================================
--- uspace/lib/display/include/display.h	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/display/include/display.h	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -59,6 +59,9 @@
 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_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);
Index: uspace/lib/display/include/ipc/display.h
===================================================================
--- uspace/lib/display/include/ipc/display.h	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/display/include/ipc/display.h	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -42,10 +42,13 @@
 	DISPLAY_WINDOW_CREATE,
 	DISPLAY_WINDOW_DESTROY,
+	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_UNMAXIMIZE,
 	DISPLAY_GET_EVENT,
 	DISPLAY_GET_INFO
Index: uspace/lib/display/include/types/display/wndparams.h
===================================================================
--- uspace/lib/display/include/types/display/wndparams.h	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/display/include/types/display/wndparams.h	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -43,5 +43,7 @@
 	wndf_popup = 0x1,
 	/** Set specific initial window position */
-	wndf_setpos = 0x2
+	wndf_setpos = 0x2,
+	/** Window is maximized */
+	wndf_maximized = 0x4
 } display_wnd_flags_t;
 
Index: uspace/lib/display/src/disp_srv.c
===================================================================
--- uspace/lib/display/src/disp_srv.c	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/display/src/disp_srv.c	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -230,4 +230,48 @@
 }
 
+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(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);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	async_answer_0(icall, EOK);
+}
+
 static void display_window_resize_req_srv(display_srv_t *srv, ipc_call_t *icall)
 {
@@ -306,4 +350,36 @@
 	rc = srv->ops->window_resize(srv->arg, wnd_id, &wresize.offs,
 	    &wresize.nrect);
+	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);
 }
@@ -445,4 +521,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);
@@ -450,4 +529,10 @@
 		case DISPLAY_WINDOW_RESIZE:
 			display_window_resize_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:
Index: uspace/lib/display/src/display.c
===================================================================
--- uspace/lib/display/src/display.c	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/display/src/display.c	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -350,4 +350,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.
  *
@@ -453,4 +485,38 @@
 
 	return EOK;
+}
+
+/** 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;
 }
 
Index: uspace/lib/display/test/display.c
===================================================================
--- uspace/lib/display/test/display.c	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/display/test/display.c	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -60,8 +60,11 @@
 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 *);
 static errno_t test_window_resize(void *, sysarg_t, gfx_coord2_t *,
     gfx_rect_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_get_event(void *, sysarg_t *, display_wnd_ev_t *);
@@ -76,6 +79,9 @@
 	.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_maximize = test_window_maximize,
+	.window_unmaximize = test_window_unmaximize,
 	.window_set_cursor = test_window_set_cursor,
 	.get_event = test_get_event,
@@ -122,4 +128,8 @@
 	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;
 	sysarg_t resize_req_wnd_id;
@@ -131,4 +141,7 @@
 	gfx_rect_t resize_nbound;
 	sysarg_t resize_wnd_id;
+
+	bool window_maximize_called;
+	bool window_unmaximize_called;
 
 	bool window_set_cursor_called;
@@ -674,4 +687,126 @@
 }
 
+/** 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;
+
+	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);
+
+	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(sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** 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;
+
+	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);
+
+	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(sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
 /** display_window_resize_req() with server returning error response works. */
 PCUT_TEST(window_resize_req_failure)
@@ -908,4 +1043,100 @@
 	PCUT_ASSERT_INT_EQUALS(nrect.p1.x, resp.resize_nbound.p1.x);
 	PCUT_ASSERT_INT_EQUALS(nrect.p1.y, resp.resize_nbound.p1.y);
+
+	display_window_destroy(wnd);
+	display_close(disp);
+	rc = loc_service_unregister(sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** 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;
+
+	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);
+
+	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(sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** 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;
+
+	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);
+
+	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);
@@ -1719,4 +1950,18 @@
 }
 
+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)
@@ -1740,4 +1985,22 @@
 	resp->resize_offs = *offs;
 	resp->resize_nbound = *nrect;
+	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;
 }
Index: uspace/lib/ui/include/types/ui/wdecor.h
===================================================================
--- uspace/lib/ui/include/types/ui/wdecor.h	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/ui/include/types/ui/wdecor.h	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -51,8 +51,10 @@
 	/** Window has a title bar */
 	ui_wds_titlebar = 0x2,
+	/** Window has a maximize button */
+	ui_wds_maximize_btn = 0x4,
 	/** Window has a close button */
-	ui_wds_close_btn = 0x4,
+	ui_wds_close_btn = 0x8,
 	/** Window is resizable */
-	ui_wds_resizable = 0x8,
+	ui_wds_resizable = 0x10,
 	/** Window is decorated (default decoration) */
 	ui_wds_decorated = ui_wds_frame | ui_wds_titlebar | ui_wds_close_btn
@@ -76,4 +78,6 @@
 /** Window decoration callbacks */
 typedef struct ui_wdecor_cb {
+	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 *);
Index: uspace/lib/ui/include/types/ui/window.h
===================================================================
--- uspace/lib/ui/include/types/ui/window.h	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/ui/include/types/ui/window.h	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -87,4 +87,6 @@
 /** Window callbacks */
 typedef struct ui_window_cb {
+	void (*maximize)(ui_window_t *, void *);
+	void (*unmaximize)(ui_window_t *, void *);
 	void (*close)(ui_window_t *, void *);
 	void (*focus)(ui_window_t *, void *);
Index: uspace/lib/ui/include/ui/paint.h
===================================================================
--- uspace/lib/ui/include/ui/paint.h	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/ui/include/ui/paint.h	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -65,4 +65,8 @@
 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_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(ui_resource_t *, gfx_rect_t *,
     ui_box_style_t, gfx_color_t *);
Index: uspace/lib/ui/include/ui/wdecor.h
===================================================================
--- uspace/lib/ui/include/ui/wdecor.h	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/ui/include/ui/wdecor.h	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -51,4 +51,5 @@
 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 errno_t ui_wdecor_paint(ui_wdecor_t *);
Index: uspace/lib/ui/include/ui/window.h
===================================================================
--- uspace/lib/ui/include/ui/window.h	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/ui/include/ui/window.h	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -65,4 +65,6 @@
 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_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 *);
Index: uspace/lib/ui/private/wdecor.h
===================================================================
--- uspace/lib/ui/private/wdecor.h	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/ui/private/wdecor.h	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -63,4 +63,8 @@
 	/** Window is active */
 	bool active;
+	/** Window is maximized */
+	bool maximized;
+	/** Maximize button */
+	struct ui_pbutton *btn_max;
 	/** Close button */
 	struct ui_pbutton *btn_close;
@@ -76,4 +80,6 @@
 	/** Title bar rectangle */
 	gfx_rect_t title_bar_rect;
+	/** Maximize button rectangle */
+	gfx_rect_t btn_max_rect;
 	/** Close button rectangle */
 	gfx_rect_t btn_close_rect;
@@ -82,4 +88,6 @@
 } ui_wdecor_geom_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 *);
Index: uspace/lib/ui/private/window.h
===================================================================
--- uspace/lib/ui/private/window.h	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/ui/private/window.h	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -77,4 +77,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;
@@ -99,5 +101,17 @@
 };
 
+/** 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_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 *);
@@ -106,4 +120,6 @@
 extern void ui_window_send_pos(ui_window_t *, pos_event_t *);
 extern void ui_window_send_unfocus(ui_window_t *);
+extern errno_t ui_window_size_change(ui_window_t *, gfx_rect_t *,
+    ui_wnd_sc_op_t);
 
 #endif
Index: uspace/lib/ui/src/paint.c
===================================================================
--- uspace/lib/ui/src/paint.c	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/ui/src/paint.c	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -529,4 +529,78 @@
 }
 
+/** 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 text box.
  *
Index: uspace/lib/ui/src/wdecor.c
===================================================================
--- uspace/lib/ui/src/wdecor.c	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/ui/src/wdecor.c	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -49,10 +49,22 @@
 #include "../private/wdecor.h"
 
-static void ui_wdecor_btn_clicked(ui_pbutton_t *, void *);
+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_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
 };
 
@@ -62,14 +74,36 @@
 
 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,
+	/** Title bar height */
 	wdecor_tbar_h = 22,
+	/** Window width */
 	wdecor_frame_w = 4,
+	/** Window frame width in text mode */
 	wdecor_frame_w_text = 1,
+	/** Close button cross leg length */
 	wdecor_close_cross_n = 5,
+	/** Close button cross pen width */
 	wdecor_close_cross_w = 2,
-	wdecor_close_cross_h = 1
+	/** Close button cross pen height */
+	wdecor_close_cross_h = 1,
+	/** 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
 };
 
@@ -98,16 +132,31 @@
 	}
 
-	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);
-
-	ui_pbutton_set_decor_ops(wdecor->btn_close,
-	    &ui_wdecor_btn_close_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;
@@ -127,4 +176,5 @@
 		return;
 
+	ui_pbutton_destroy(wdecor->btn_max);
 	ui_pbutton_destroy(wdecor->btn_close);
 	free(wdecor->caption);
@@ -156,5 +206,9 @@
 
 	ui_wdecor_get_geom(wdecor, &geom);
-	ui_pbutton_set_rect(wdecor->btn_close, &geom.btn_close_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);
 }
 
@@ -169,4 +223,16 @@
 {
 	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;
 }
 
@@ -289,5 +355,11 @@
 			return rc;
 
-		if ((wdecor->style & ui_wds_close_btn) != 0) {
+		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)
@@ -303,4 +375,24 @@
 }
 
+/** 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.
  *
@@ -356,4 +448,6 @@
 {
 	gfx_coord_t frame_w;
+	gfx_coord_t btn_x;
+	gfx_coord_t btn_y;
 
 	/* Does window have a frame? */
@@ -376,4 +470,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;
@@ -381,4 +478,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;
 		}
 
@@ -386,4 +486,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;
@@ -393,4 +494,6 @@
 
 		geom->app_area_rect = geom->interior_rect;
+		btn_x = 0;
+		btn_y = 0;
 	}
 
@@ -398,21 +501,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 {
@@ -422,4 +521,24 @@
 		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;
+		} 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;
+		}
+	} 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;
+	}
 }
 
@@ -495,4 +614,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))
@@ -627,5 +750,11 @@
 	ui_wdecor_get_geom(wdecor, &geom);
 
-	if ((wdecor->style & ui_wds_close_btn) != 0) {
+	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)
@@ -635,5 +764,5 @@
 	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)) {
@@ -646,10 +775,50 @@
 }
 
-/** Window decoration close button was clicked.
+/** Window decoration (un)maximize button was clicked.
  *
  * @param pbutton Close button
  * @param arg Argument (ui_wdecor_t)
  */
-static void ui_wdecor_btn_clicked(ui_pbutton_t *pbutton, void *arg)
+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 (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;
+}
+
+/** Window decoration close button was clicked.
+ *
+ * @param pbutton Close button
+ * @param arg Argument (ui_wdecor_t)
+ */
+static void ui_wdecor_btn_close_clicked(ui_pbutton_t *pbutton, void *arg)
 {
 	ui_wdecor_t *wdecor = (ui_wdecor_t *) arg;
Index: uspace/lib/ui/src/window.c
===================================================================
--- uspace/lib/ui/src/window.c	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/ui/src/window.c	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -74,4 +74,6 @@
 };
 
+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 *);
@@ -81,4 +83,6 @@
 
 static ui_wdecor_cb_t wdecor_cb = {
+	.maximize = wd_maximize,
+	.unmaximize = wd_unmaximize,
 	.close = wd_close,
 	.move = wd_move,
@@ -469,15 +473,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;
@@ -545,9 +548,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;
+		}
 	}
 
@@ -597,4 +615,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.
  *
@@ -846,4 +879,28 @@
 	ui_window_send_unfocus(window);
 	ui_unlock(window->ui);
+}
+
+/** 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);
 }
 
@@ -948,4 +1005,28 @@
 }
 
+/** 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.
  *
@@ -1014,4 +1095,61 @@
 	else
 		return ui_window_def_unfocus(window);
+}
+
+/** 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);
+
+	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);
+
+	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;
 }
 
Index: uspace/lib/ui/test/paint.c
===================================================================
--- uspace/lib/ui/test/paint.c	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/ui/test/paint.c	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -354,4 +354,60 @@
 }
 
+/** 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)
Index: uspace/lib/ui/test/wdecor.c
===================================================================
--- uspace/lib/ui/test/wdecor.c	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/ui/test/wdecor.c	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -62,4 +62,6 @@
 };
 
+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 *);
@@ -69,4 +71,6 @@
 
 static ui_wdecor_cb_t test_wdecor_cb = {
+	.maximize = test_wdecor_maximize,
+	.unmaximize = test_wdecor_unmaximize,
 	.close = test_wdecor_close,
 	.move = test_wdecor_move,
@@ -96,4 +100,6 @@
 
 typedef struct {
+	bool maximize;
+	bool unmaximize;
 	bool close;
 	bool move;
@@ -168,4 +174,24 @@
 }
 
+/** 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);
+}
+
 /** Paint button */
 PCUT_TEST(paint)
@@ -196,4 +222,56 @@
 	rc = gfx_context_delete(gc);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** 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);
 }
 
@@ -936,4 +1014,18 @@
 }
 
+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)
 {
Index: uspace/lib/ui/test/window.c
===================================================================
--- uspace/lib/ui/test/window.c	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/lib/ui/test/window.c	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -45,4 +45,6 @@
 PCUT_TEST_SUITE(window);
 
+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 *);
@@ -53,4 +55,6 @@
 
 static ui_window_cb_t test_window_cb = {
+	.maximize = test_window_maximize,
+	.unmaximize = test_window_unmaximize,
 	.close = test_window_close,
 	.focus = test_window_focus,
@@ -76,4 +80,6 @@
 typedef struct {
 	errno_t rc;
+	bool maximize;
+	bool unmaximize;
 	bool close;
 	bool focus;
@@ -526,4 +532,76 @@
 	/* 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_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);
@@ -771,4 +849,18 @@
 }
 
+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;
+}
+
 static void test_window_close(ui_window_t *window, void *arg)
 {
Index: uspace/srv/hid/display/dsops.c
===================================================================
--- uspace/srv/hid/display/dsops.c	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/srv/hid/display/dsops.c	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -49,8 +49,11 @@
 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 *);
 static errno_t disp_window_resize(void *, sysarg_t, gfx_coord2_t *,
     gfx_rect_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_get_event(void *, sysarg_t *, display_wnd_ev_t *);
@@ -63,6 +66,9 @@
 	.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_maximize = disp_window_maximize,
+	.window_unmaximize = disp_window_unmaximize,
 	.window_set_cursor = disp_window_set_cursor,
 	.get_event = disp_get_event,
@@ -175,4 +181,24 @@
 }
 
+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)
@@ -219,4 +245,44 @@
 }
 
+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)
Index: uspace/srv/hid/display/test/window.c
===================================================================
--- uspace/srv/hid/display/test/window.c	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/srv/hid/display/test/window.c	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -99,4 +99,171 @@
 }
 
+/** 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, &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, &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_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, &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, &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)
Index: uspace/srv/hid/display/types/display/window.h
===================================================================
--- uspace/srv/hid/display/types/display/window.h	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/srv/hid/display/types/display/window.h	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 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;
Index: uspace/srv/hid/display/window.c
===================================================================
--- uspace/srv/hid/display/window.c	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/srv/hid/display/window.c	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -580,4 +580,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)
@@ -597,5 +599,6 @@
 	inside = gfx_pix_inside_rect(&pos, &drect);
 
-	if (event->type == POS_PRESS && event->btn_num == 2 && inside) {
+	if (event->type == POS_PRESS && event->btn_num == 2 && inside &&
+	    (wnd->flags & wndf_maximized) == 0) {
 		ds_window_start_move(wnd, &pos);
 		return EOK;
@@ -637,4 +640,5 @@
  *
  * @param wnd Window
+ * @return EOK on success or an error code
  */
 errno_t ds_window_post_focus_event(ds_window_t *wnd)
@@ -648,4 +652,5 @@
  *
  * @param wnd Window
+ * @return EOK on success or an error code
  */
 errno_t ds_window_post_unfocus_event(ds_window_t *wnd)
@@ -691,4 +696,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->rect;
 }
 
@@ -716,4 +730,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,
@@ -770,4 +785,75 @@
 }
 
+/** 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;
+
+	/* Set window flags, remember normal rectangle */
+	wnd->flags &= ~wndf_maximized;
+
+	return EOK;
+}
+
 /** Compute new window rectangle after resize operation.
  *
Index: uspace/srv/hid/display/window.h
===================================================================
--- uspace/srv/hid/display/window.h	(revision fd05ea65d3ce558eba4ac6cd5b55b70eebc1fcf5)
+++ uspace/srv/hid/display/window.h	(revision 35cffeac21acf1766c2acbf62f162a7a62c130f7)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2022 Jiri Svoboda
  * All rights reserved.
  *
@@ -64,7 +64,10 @@
 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 *);
 extern errno_t ds_window_resize(ds_window_t *, gfx_coord2_t *, gfx_rect_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 *);
