Index: uspace/app/gfxdemo/gfxdemo.c
===================================================================
--- uspace/app/gfxdemo/gfxdemo.c	(revision 7bb45e33b98aa347a21453b63e27f209dc3ab3d9)
+++ uspace/app/gfxdemo/gfxdemo.c	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
@@ -50,7 +50,9 @@
 #include <window.h>
 
+static void wnd_close_event(void *);
 static void wnd_kbd_event(void *, kbd_event_t *);
 
 static display_wnd_cb_t wnd_cb = {
+	.close_event = wnd_close_event,
 	.kbd_event = wnd_kbd_event
 };
@@ -500,4 +502,10 @@
 }
 
+static void wnd_close_event(void *arg)
+{
+	printf("Close event\n");
+	quit = true;
+}
+
 static void wnd_kbd_event(void *arg, kbd_event_t *event)
 {
Index: uspace/lib/display/include/types/display.h
===================================================================
--- uspace/lib/display/include/types/display.h	(revision 7bb45e33b98aa347a21453b63e27f209dc3ab3d9)
+++ uspace/lib/display/include/types/display.h	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
@@ -62,9 +62,11 @@
 /** Display window callbacks */
 typedef struct {
+	/** Close event */
+	void (*close_event)(void *);
 	/** Focus event */
 	void (*focus_event)(void *);
-	/** Keyboard event callback */
+	/** Keyboard event */
 	void (*kbd_event)(void *, kbd_event_t *);
-	/** Position event callback */
+	/** Position event */
 	void (*pos_event)(void *, pos_event_t *);
 	/** Unfocus event */
Index: uspace/lib/display/include/types/display/event.h
===================================================================
--- uspace/lib/display/include/types/display/event.h	(revision 7bb45e33b98aa347a21453b63e27f209dc3ab3d9)
+++ uspace/lib/display/include/types/display/event.h	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
@@ -41,4 +41,6 @@
 /** Display window event type */
 typedef enum {
+	/** Request to close window */
+	wev_close,
 	/** Window gained focus */
 	wev_focus,
Index: uspace/lib/display/src/display.c
===================================================================
--- uspace/lib/display/src/display.c	(revision 7bb45e33b98aa347a21453b63e27f209dc3ab3d9)
+++ uspace/lib/display/src/display.c	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
@@ -370,4 +370,9 @@
 
 		switch (event.etype) {
+		case wev_close:
+			if (window->cb != NULL && window->cb->close_event != NULL) {
+				window->cb->close_event(window->cb_arg);
+			}
+			break;
 		case wev_focus:
 			if (window->cb != NULL && window->cb->focus_event != NULL) {
Index: uspace/lib/display/test/display.c
===================================================================
--- uspace/lib/display/test/display.c	(revision 7bb45e33b98aa347a21453b63e27f209dc3ab3d9)
+++ uspace/lib/display/test/display.c	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
@@ -48,4 +48,5 @@
 static void test_display_conn(ipc_call_t *, void *);
 
+static void test_close_event(void *);
 static void test_focus_event(void *);
 static void test_kbd_event(void *, kbd_event_t *);
@@ -69,4 +70,5 @@
 
 static display_wnd_cb_t test_display_wnd_cb = {
+	.close_event = test_close_event,
 	.focus_event = test_focus_event,
 	.kbd_event = test_kbd_event,
@@ -100,4 +102,5 @@
 	bool get_event_called;
 	bool set_color_called;
+	bool close_event_called;
 	bool focus_event_called;
 	bool kbd_event_called;
@@ -526,4 +529,69 @@
 
 	display_close(disp);
+	rc = loc_service_unregister(sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Close event can be delivered from server to client callback function */
+PCUT_TEST(close_event_deliver)
+{
+	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);
+	PCUT_ASSERT_NOT_NULL(resp.srv);
+
+	wnd = NULL;
+	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.event_cnt = 1;
+	resp.event.etype = wev_close;
+	resp.wnd_id = wnd->id;
+	resp.close_event_called = false;
+	fibril_mutex_initialize(&resp.event_lock);
+	fibril_condvar_initialize(&resp.event_cv);
+	display_srv_ev_pending(resp.srv);
+
+	/* Wait for the event handler to be called. */
+	fibril_mutex_lock(&resp.event_lock);
+	while (!resp.close_event_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_EQUALS(resp.event.etype,
+	    resp.revent.etype);
+
+	rc = display_window_destroy(wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	display_close(disp);
+
 	rc = loc_service_unregister(sid);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
@@ -861,4 +929,16 @@
 }
 
+static void test_close_event(void *arg)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->revent.etype = wev_close;
+
+	fibril_mutex_lock(&resp->event_lock);
+	resp->close_event_called = true;
+	fibril_condvar_broadcast(&resp->event_cv);
+	fibril_mutex_unlock(&resp->event_lock);
+}
+
 static void test_focus_event(void *arg)
 {
Index: uspace/lib/gui/window.c
===================================================================
--- uspace/lib/gui/window.c	(revision 7bb45e33b98aa347a21453b63e27f209dc3ab3d9)
+++ uspace/lib/gui/window.c	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
@@ -83,4 +83,5 @@
 static pixel_t color_caption_unfocus = PIXEL(255, 207, 207, 207);
 
+static void window_close_event(void *);
 static void window_focus_event(void *);
 static void window_kbd_event(void *, kbd_event_t *);
@@ -89,4 +90,5 @@
 
 static display_wnd_cb_t window_cb = {
+	.close_event = window_close_event,
 	.focus_event = window_focus_event,
 	.kbd_event = window_kbd_event,
@@ -332,5 +334,5 @@
 			//win_grab(widget->window->osess, event.pos_id, flags);
 		} else if (close && btn_left) {
-			//win_close_request(widget->window->osess);
+			window_close(widget->window);
 		} else if (header && btn_left) {
 			window_grab_flags_t flags = GF_EMPTY;
@@ -766,6 +768,20 @@
 void window_close(window_t *win)
 {
-	/* Request compositor to init closing cascade. */
-	//win_close_request(win->osess);
+	window_event_t *event;
+
+	event = (window_event_t *) calloc(1, sizeof(window_event_t));
+	if (event == NULL)
+		return;
+
+	link_initialize(&event->link);
+	event->type = ET_WINDOW_CLOSE;
+	prodcons_produce(&win->events, &event->link);
+}
+
+static void window_close_event(void *arg)
+{
+	window_t *win = (window_t *) arg;
+
+	window_close(win);
 }
 
Index: uspace/srv/hid/display/client.c
===================================================================
--- uspace/srv/hid/display/client.c	(revision 7bb45e33b98aa347a21453b63e27f209dc3ab3d9)
+++ uspace/srv/hid/display/client.c	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
@@ -203,21 +203,21 @@
 }
 
-/** Post focus event to the client's message queue.
- *
- * @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)
-{
-	ds_window_ev_t *wevent;
-
-	wevent = calloc(1, sizeof(ds_window_ev_t));
-	if (wevent == NULL)
-		return ENOMEM;
-
-	wevent->window = ewindow;
-	wevent->event.etype = wev_focus;
+/** Post close event to the client's message queue.
+ *
+ * @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_close_event(ds_client_t *client, ds_window_t *ewindow)
+{
+	ds_window_ev_t *wevent;
+
+	wevent = calloc(1, sizeof(ds_window_ev_t));
+	if (wevent == NULL)
+		return ENOMEM;
+
+	wevent->window = ewindow;
+	wevent->event.etype = wev_close;
 	list_append(&wevent->levents, &client->events);
 
@@ -230,4 +230,31 @@
 }
 
+/** Post focus event to the client's message queue.
+ *
+ * @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)
+{
+	ds_window_ev_t *wevent;
+
+	wevent = calloc(1, sizeof(ds_window_ev_t));
+	if (wevent == NULL)
+		return ENOMEM;
+
+	wevent->window = ewindow;
+	wevent->event.etype = wev_focus;
+	list_append(&wevent->levents, &client->events);
+
+	/* Notify the client */
+	// TODO Do not send more than once until client drains the queue
+	if (client->cb != NULL && client->cb->ev_pending != NULL)
+		client->cb->ev_pending(client->cb_arg);
+
+	return EOK;
+}
+
 /** Post keyboard event to the client's message queue.
  *
Index: uspace/srv/hid/display/client.h
===================================================================
--- uspace/srv/hid/display/client.h	(revision 7bb45e33b98aa347a21453b63e27f209dc3ab3d9)
+++ uspace/srv/hid/display/client.h	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
@@ -53,4 +53,5 @@
 extern errno_t ds_client_get_event(ds_client_t *, ds_window_t **,
     display_wnd_ev_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_kbd_event(ds_client_t *, ds_window_t *,
Index: uspace/srv/hid/display/seat.c
===================================================================
--- uspace/srv/hid/display/seat.c	(revision 7bb45e33b98aa347a21453b63e27f209dc3ab3d9)
+++ uspace/srv/hid/display/seat.c	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
@@ -137,5 +137,5 @@
 		return EOK;
 
-	return ds_client_post_kbd_event(dwindow->client, dwindow, event);
+	return ds_window_post_kbd_event(dwindow, event);
 }
 
Index: uspace/srv/hid/display/test/client.c
===================================================================
--- uspace/srv/hid/display/test/client.c	(revision 7bb45e33b98aa347a21453b63e27f209dc3ab3d9)
+++ uspace/srv/hid/display/test/client.c	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
@@ -163,4 +163,51 @@
 }
 
+/** Test ds_client_get_event(), ds_client_post_close_event(). */
+PCUT_TEST(client_get_post_close_event)
+{
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_window_t *wnd;
+	display_wnd_params_t params;
+	ds_window_t *rwindow;
+	display_wnd_ev_t revent;
+	bool called_cb = NULL;
+	errno_t rc;
+
+	rc = ds_display_create(NULL, &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);
+
+	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_FALSE(called_cb);
+
+	rc = ds_client_get_event(client, &rwindow, &revent);
+	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
+
+	rc = ds_client_post_close_event(client, wnd);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_TRUE(called_cb);
+
+	rc = ds_client_get_event(client, &rwindow, &revent);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS(wnd, rwindow);
+	PCUT_ASSERT_EQUALS(wev_close, revent.etype);
+
+	rc = ds_client_get_event(client, &rwindow, &revent);
+	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
+
+	ds_window_destroy(wnd);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
 /** Test ds_client_get_event(), ds_client_post_focus_event(). */
 PCUT_TEST(client_get_post_focus_event)
Index: uspace/srv/hid/display/test/window.c
===================================================================
--- uspace/srv/hid/display/test/window.c	(revision 7bb45e33b98aa347a21453b63e27f209dc3ab3d9)
+++ uspace/srv/hid/display/test/window.c	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
@@ -124,4 +124,52 @@
 }
 
+/** Test ds_window_post_kbd_event() with Alt-F4 sends close event */
+PCUT_TEST(window_post_kbd_event_alt_f4)
+{
+	gfx_context_t *gc;
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_window_t *wnd;
+	ds_window_t *rwindow;
+	display_wnd_ev_t revent;
+	display_wnd_params_t params;
+	kbd_event_t event;
+	errno_t rc;
+
+	rc = gfx_context_new(&dummy_ops, NULL, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_display_create(gc, &disp);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_create(disp, NULL, NULL, &client);
+	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);
+
+	event.type = KEY_PRESS;
+	event.mods = KM_ALT;
+	event.key = KC_F4;
+	rc = ds_window_post_kbd_event(wnd, &event);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ds_client_get_event(client, &rwindow, &revent);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS(wnd, rwindow);
+	PCUT_ASSERT_EQUALS(wev_close, revent.etype);
+
+	rc = ds_client_get_event(client, &rwindow, &revent);
+	PCUT_ASSERT_ERRNO_VAL(ENOENT, rc);
+
+	ds_window_destroy(wnd);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
 /** Test ds_window_post_pos_event() */
 PCUT_TEST(window_post_pos_event)
Index: uspace/srv/hid/display/window.c
===================================================================
--- uspace/srv/hid/display/window.c	(revision 7bb45e33b98aa347a21453b63e27f209dc3ab3d9)
+++ uspace/srv/hid/display/window.c	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
@@ -529,4 +529,26 @@
 }
 
+/** Post keyboard event to window.
+ *
+ * @param wnd Window
+ * @param event Event
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ds_window_post_kbd_event(ds_window_t *wnd, kbd_event_t *event)
+{
+	bool alt_or_shift;
+
+	alt_or_shift = event->mods & (KM_SHIFT | KM_ALT);
+
+	if (event->type == KEY_PRESS && alt_or_shift && event->key == KC_F4) {
+		/* On Alt-F4 or Shift-F4 send close event to the window */
+		ds_client_post_close_event(wnd->client, wnd);
+		return EOK;
+	}
+
+	return ds_client_post_kbd_event(wnd->client, wnd, event);
+}
+
 /** Post position event to window.
  *
Index: uspace/srv/hid/display/window.h
===================================================================
--- uspace/srv/hid/display/window.h	(revision 7bb45e33b98aa347a21453b63e27f209dc3ab3d9)
+++ uspace/srv/hid/display/window.h	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
@@ -54,4 +54,5 @@
 extern gfx_context_t *ds_window_get_ctx(ds_window_t *);
 extern errno_t ds_window_paint(ds_window_t *, gfx_rect_t *);
+extern errno_t ds_window_post_kbd_event(ds_window_t *, kbd_event_t *);
 extern errno_t ds_window_post_pos_event(ds_window_t *, pos_event_t *);
 extern errno_t ds_window_post_focus_event(ds_window_t *);
