Index: uspace/lib/display/include/disp_srv.h
===================================================================
--- uspace/lib/display/include/disp_srv.h	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
+++ uspace/lib/display/include/disp_srv.h	(revision a2e104e0deacc5a9fdacf4d8e3824726c77c47a5)
@@ -54,4 +54,5 @@
 	errno_t (*window_create)(void *, display_wnd_params_t *, sysarg_t *);
 	errno_t (*window_destroy)(void *, sysarg_t);
+	errno_t (*window_move_req)(void *, sysarg_t, gfx_coord2_t *);
 	errno_t (*window_resize)(void *, sysarg_t, gfx_coord2_t *, gfx_rect_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 338d09353f53dcb5fd2a315562bcd0b2588792d0)
+++ uspace/lib/display/include/display.h	(revision a2e104e0deacc5a9fdacf4d8e3824726c77c47a5)
@@ -49,4 +49,5 @@
 extern errno_t display_window_destroy(display_window_t *);
 extern errno_t display_window_get_gc(display_window_t *, gfx_context_t **);
+extern errno_t display_window_move_req(display_window_t *, gfx_coord2_t *);
 extern errno_t display_window_resize(display_window_t *,
     gfx_coord2_t *, gfx_rect_t *);
Index: uspace/lib/display/include/ipc/display.h
===================================================================
--- uspace/lib/display/include/ipc/display.h	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
+++ uspace/lib/display/include/ipc/display.h	(revision a2e104e0deacc5a9fdacf4d8e3824726c77c47a5)
@@ -42,4 +42,5 @@
 	DISPLAY_WINDOW_CREATE,
 	DISPLAY_WINDOW_DESTROY,
+	DISPLAY_WINDOW_MOVE_REQ,
 	DISPLAY_WINDOW_RESIZE,
 	DISPLAY_GET_EVENT
Index: uspace/lib/display/src/disp_srv.c
===================================================================
--- uspace/lib/display/src/disp_srv.c	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
+++ uspace/lib/display/src/disp_srv.c	(revision a2e104e0deacc5a9fdacf4d8e3824726c77c47a5)
@@ -100,5 +100,5 @@
 	wnd_id = ipc_get_arg1(icall);
 
-	if (srv->ops->window_create == NULL) {
+	if (srv->ops->window_destroy == NULL) {
 		async_answer_0(icall, ENOTSUP);
 		return;
@@ -106,4 +106,42 @@
 
 	rc = srv->ops->window_destroy(srv->arg, wnd_id);
+	async_answer_0(icall, rc);
+}
+
+static void display_window_move_req_srv(display_srv_t *srv, ipc_call_t *icall)
+{
+	sysarg_t wnd_id;
+	ipc_call_t call;
+	gfx_coord2_t pos;
+	size_t size;
+	errno_t rc;
+
+	wnd_id = ipc_get_arg1(icall);
+
+	if (!async_data_write_receive(&call, &size)) {
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != sizeof(gfx_coord2_t)) {
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	rc = async_data_write_finalize(&call, &pos, size);
+	if (rc != EOK) {
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	if (srv->ops->window_move_req == NULL) {
+		async_answer_0(icall, ENOTSUP);
+		return;
+	}
+
+	rc = srv->ops->window_move_req(srv->arg, wnd_id, &pos);
 	async_answer_0(icall, rc);
 }
@@ -215,4 +253,7 @@
 		case DISPLAY_WINDOW_DESTROY:
 			display_window_destroy_srv(srv, &call);
+			break;
+		case DISPLAY_WINDOW_MOVE_REQ:
+			display_window_move_req_srv(srv, &call);
 			break;
 		case DISPLAY_WINDOW_RESIZE:
Index: uspace/lib/display/src/display.c
===================================================================
--- uspace/lib/display/src/display.c	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
+++ uspace/lib/display/src/display.c	(revision a2e104e0deacc5a9fdacf4d8e3824726c77c47a5)
@@ -244,4 +244,36 @@
 }
 
+/** Request a window move.
+ *
+ * Request the display service to initiate a user window move operation
+ * (i.e. let the user move the window). Used when the client detects
+ * mouse press on the title bar or such.
+ *
+ * @param window Window
+ * @return EOK on success or an error code
+ */
+errno_t display_window_move_req(display_window_t *window, gfx_coord2_t *pos)
+{
+	async_exch_t *exch;
+	aid_t req;
+	ipc_call_t answer;
+	errno_t rc;
+
+	exch = async_exchange_begin(window->display->sess);
+	req = async_send_1(exch, DISPLAY_WINDOW_MOVE_REQ, window->id, &answer);
+	rc = async_data_write_start(exch, (void *)pos, sizeof (gfx_coord2_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;
+}
+
 /** Resize display window.
  *
@@ -283,6 +315,5 @@
  * @param nrect New bounding rectangle
  * @param offs
- * @return EOK on success or an error code. In both cases @a window must
- *         not be accessed anymore
+ * @return EOK on success or an error code
  */
 errno_t display_window_resize(display_window_t *window, gfx_coord2_t *offs,
Index: uspace/lib/display/test/display.c
===================================================================
--- uspace/lib/display/test/display.c	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
+++ uspace/lib/display/test/display.c	(revision a2e104e0deacc5a9fdacf4d8e3824726c77c47a5)
@@ -56,4 +56,5 @@
 static errno_t test_window_create(void *, display_wnd_params_t *, sysarg_t *);
 static errno_t test_window_destroy(void *, sysarg_t);
+static errno_t test_window_move_req(void *, sysarg_t, gfx_coord2_t *);
 static errno_t test_window_resize(void *, sysarg_t, gfx_coord2_t *,
     gfx_rect_t *);
@@ -65,4 +66,5 @@
 	.window_create = test_window_create,
 	.window_destroy = test_window_destroy,
+	.window_move_req = test_window_move_req,
 	.window_resize = test_window_resize,
 	.get_event = test_get_event
@@ -94,4 +96,8 @@
 	bool window_destroy_called;
 	sysarg_t destroy_wnd_id;
+
+	bool window_move_req_called;
+	sysarg_t move_req_wnd_id;
+	gfx_coord2_t move_req_pos;
 
 	bool window_resize_called;
@@ -294,4 +300,114 @@
 }
 
+/** display_window_move_req() with server returning error response works. */
+PCUT_TEST(window_move_req_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;
+	gfx_coord2_t pos;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(test_display_svc, &sid);
+	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_move_req_called = false;
+
+	pos.x = 42;
+	pos.y = 43;
+
+	rc = display_window_move_req(wnd, &pos);
+	PCUT_ASSERT_TRUE(resp.window_move_req_called);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+	PCUT_ASSERT_INT_EQUALS(wnd->id, resp.move_req_wnd_id);
+	PCUT_ASSERT_INT_EQUALS(pos.x, resp.move_req_pos.x);
+	PCUT_ASSERT_INT_EQUALS(pos.y, resp.move_req_pos.y);
+
+	display_window_destroy(wnd);
+	display_close(disp);
+	rc = loc_service_unregister(sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** display_window_move_req() with server returning success response works. */
+PCUT_TEST(window_move_req_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;
+	gfx_coord2_t pos;
+
+	async_set_fallback_port_handler(test_display_conn, &resp);
+
+	// FIXME This causes this test to be non-reentrant!
+	rc = loc_server_register(test_display_server);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = loc_service_register(test_display_svc, &sid);
+	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_move_req_called = false;
+
+	pos.x = 42;
+	pos.y = 43;
+
+	rc = display_window_move_req(wnd, &pos);
+	PCUT_ASSERT_TRUE(resp.window_move_req_called);
+	PCUT_ASSERT_ERRNO_VAL(resp.rc, rc);
+	PCUT_ASSERT_INT_EQUALS(wnd->id, resp.move_req_wnd_id);
+	PCUT_ASSERT_INT_EQUALS(pos.x, resp.move_req_pos.x);
+	PCUT_ASSERT_INT_EQUALS(pos.y, resp.move_req_pos.y);
+
+	display_window_destroy(wnd);
+	display_close(disp);
+	rc = loc_service_unregister(sid);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
 /** display_window_resize() with server returning error response works. */
 PCUT_TEST(window_resize_failure)
@@ -1013,4 +1129,15 @@
 }
 
+static errno_t test_window_move_req(void *arg, sysarg_t wnd_id,
+    gfx_coord2_t *pos)
+{
+	test_response_t *resp = (test_response_t *) arg;
+
+	resp->window_move_req_called = true;
+	resp->move_req_wnd_id = wnd_id;
+	resp->move_req_pos = *pos;
+	return resp->rc;
+}
+
 static errno_t test_window_resize(void *arg, sysarg_t wnd_id,
     gfx_coord2_t *offs, gfx_rect_t *nrect)
Index: uspace/lib/gui/window.c
===================================================================
--- uspace/lib/gui/window.c	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
+++ uspace/lib/gui/window.c	(revision a2e104e0deacc5a9fdacf4d8e3824726c77c47a5)
@@ -272,4 +272,6 @@
 static void root_handle_position_event(widget_t *widget, pos_event_t event)
 {
+	gfx_coord2_t pos;
+
 	if (widget->window->is_decorated) {
 		sysarg_t width = widget->width;
@@ -339,5 +341,8 @@
 			flags |= GF_MOVE_X;
 			flags |= GF_MOVE_Y;
-			//win_grab(widget->window->osess, event.pos_id, flags);
+			pos.x = event.hpos;
+			pos.y = event.vpos;
+			(void) display_window_move_req(widget->window->dwindow,
+			    &pos);
 		} else {
 			list_foreach(widget->children, link, widget_t, child) {
Index: uspace/srv/hid/display/dsops.c
===================================================================
--- uspace/srv/hid/display/dsops.c	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
+++ uspace/srv/hid/display/dsops.c	(revision a2e104e0deacc5a9fdacf4d8e3824726c77c47a5)
@@ -46,4 +46,5 @@
 static errno_t disp_window_create(void *, display_wnd_params_t *, sysarg_t *);
 static errno_t disp_window_destroy(void *, sysarg_t);
+static errno_t disp_window_move_req(void *, sysarg_t, gfx_coord2_t *);
 static errno_t disp_window_resize(void *, sysarg_t, gfx_coord2_t *,
     gfx_rect_t *);
@@ -53,4 +54,5 @@
 	.window_create = disp_window_create,
 	.window_destroy = disp_window_destroy,
+	.window_move_req = disp_window_move_req,
 	.window_resize = disp_window_resize,
 	.get_event = disp_get_event
@@ -102,4 +104,19 @@
 }
 
+static errno_t disp_window_move_req(void *arg, sysarg_t wnd_id,
+    gfx_coord2_t *pos)
+{
+	ds_client_t *client = (ds_client_t *) arg;
+	ds_window_t *wnd;
+
+	wnd = ds_client_find_window(client, wnd_id);
+	if (wnd == NULL)
+		return ENOENT;
+
+	log_msg(LVL_NOTE, LVL_DEBUG, "disp_window_move_req()");
+	ds_window_move_req(wnd, pos);
+	return EOK;
+}
+
 static errno_t disp_window_resize(void *arg, sysarg_t wnd_id,
     gfx_coord2_t *offs, gfx_rect_t *nbound)
Index: uspace/srv/hid/display/test/window.c
===================================================================
--- uspace/srv/hid/display/test/window.c	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
+++ uspace/srv/hid/display/test/window.c	(revision a2e104e0deacc5a9fdacf4d8e3824726c77c47a5)
@@ -201,5 +201,9 @@
 	PCUT_ASSERT_INT_EQUALS(dsw_idle, wnd->state);
 
+	wnd->dpos.x = 10;
+	wnd->dpos.y = 10;
+
 	event.type = POS_PRESS;
+	event.btn_num = 2;
 	event.hpos = 10;
 	event.vpos = 10;
@@ -216,6 +220,6 @@
 
 	PCUT_ASSERT_INT_EQUALS(dsw_moving, wnd->state);
-	PCUT_ASSERT_INT_EQUALS(wnd->dpos.x, 1);
-	PCUT_ASSERT_INT_EQUALS(wnd->dpos.y, 2);
+	PCUT_ASSERT_INT_EQUALS(11, wnd->dpos.x);
+	PCUT_ASSERT_INT_EQUALS(12, wnd->dpos.y);
 
 	event.type = POS_RELEASE;
@@ -227,6 +231,48 @@
 
 	PCUT_ASSERT_INT_EQUALS(dsw_idle, wnd->state);
-	PCUT_ASSERT_INT_EQUALS(wnd->dpos.x, 3);
-	PCUT_ASSERT_INT_EQUALS(wnd->dpos.y, 4);
+	PCUT_ASSERT_INT_EQUALS(13, wnd->dpos.x);
+	PCUT_ASSERT_INT_EQUALS(14, wnd->dpos.y);
+
+	ds_window_destroy(wnd);
+	ds_client_destroy(client);
+	ds_display_destroy(disp);
+}
+
+/** Test ds_window_move_req() */
+PCUT_TEST(window_move_req)
+{
+	gfx_context_t *gc;
+	ds_display_t *disp;
+	ds_client_t *client;
+	ds_window_t *wnd;
+	display_wnd_params_t params;
+	gfx_coord2_t pos;
+	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);
+
+	PCUT_ASSERT_INT_EQUALS(dsw_idle, wnd->state);
+
+	pos.x = 42;
+	pos.y = 43;
+	ds_window_move_req(wnd, &pos);
+
+	PCUT_ASSERT_INT_EQUALS(dsw_moving, wnd->state);
+	PCUT_ASSERT_INT_EQUALS(pos.x, wnd->orig_pos.x);
+	PCUT_ASSERT_INT_EQUALS(pos.y, wnd->orig_pos.y);
 
 	ds_window_destroy(wnd);
Index: uspace/srv/hid/display/window.c
===================================================================
--- uspace/srv/hid/display/window.c	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
+++ uspace/srv/hid/display/window.c	(revision a2e104e0deacc5a9fdacf4d8e3824726c77c47a5)
@@ -436,4 +436,23 @@
 }
 
+/** Start moving a window, detected by client.
+ *
+ * @param wnd Window
+ * @param pos Position where the pointer was when the move started
+ *            relative to the window
+ * @param event Button press event
+ */
+void ds_window_move_req(ds_window_t *wnd, gfx_coord2_t *pos)
+{
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "ds_window_move_req (%d, %d)",
+	    (int) pos->x, (int) pos->y);
+
+	if (wnd->state != dsw_idle)
+		return;
+
+	gfx_coord2_add(&wnd->dpos, pos, &wnd->orig_pos);
+	wnd->state = dsw_moving;
+}
+
 /** Start moving a window by mouse drag.
  *
@@ -446,5 +465,6 @@
 	    (int) event->hpos, (int) event->vpos);
 
-	assert(wnd->state == dsw_idle);
+	if (wnd->state != dsw_idle)
+		return;
 
 	wnd->orig_pos.x = event->hpos;
@@ -467,5 +487,7 @@
 	    (int) event->hpos, (int) event->vpos);
 
-	assert(wnd->state == dsw_moving);
+	if (wnd->state != dsw_moving)
+		return;
+
 	pos.x = event->hpos;
 	pos.y = event->vpos;
@@ -497,4 +519,7 @@
 	    (int) event->hpos, (int) event->vpos);
 
+	if (wnd->state != dsw_moving)
+		return;
+
 	gfx_rect_translate(&wnd->dpos, &wnd->rect, &drect);
 
@@ -505,5 +530,4 @@
 	}
 
-	assert(wnd->state == dsw_moving);
 	pos.x = event->hpos;
 	pos.y = event->vpos;
@@ -559,4 +583,7 @@
 {
 	pos_event_t tevent;
+	gfx_coord2_t pos;
+	gfx_rect_t drect;
+	bool inside;
 
 	log_msg(LOG_DEFAULT, LVL_DEBUG,
@@ -564,18 +591,17 @@
 	    (int) event->hpos, (int) event->vpos);
 
-	if (event->type == POS_PRESS) {
-		if (wnd->state == dsw_idle)
-			ds_window_start_move(wnd, event);
-	}
-
-	if (event->type == POS_RELEASE) {
-		if (wnd->state == dsw_moving)
-			ds_window_finish_move(wnd, event);
-	}
-
-	if (event->type == POS_UPDATE) {
-		if (wnd->state == dsw_moving)
-			ds_window_update_move(wnd, event);
-	}
+	pos.x = event->hpos;
+	pos.y = event->vpos;
+	gfx_rect_translate(&wnd->dpos, &wnd->rect, &drect);
+	inside = gfx_pix_inside_rect(&pos, &drect);
+
+	if (event->type == POS_PRESS && event->btn_num == 2 && inside)
+		ds_window_start_move(wnd, event);
+
+	if (event->type == POS_RELEASE)
+		ds_window_finish_move(wnd, event);
+
+	if (event->type == POS_UPDATE)
+		ds_window_update_move(wnd, event);
 
 	/* Transform event coordinates to window-local */
Index: uspace/srv/hid/display/window.h
===================================================================
--- uspace/srv/hid/display/window.h	(revision 338d09353f53dcb5fd2a315562bcd0b2588792d0)
+++ uspace/srv/hid/display/window.h	(revision a2e104e0deacc5a9fdacf4d8e3824726c77c47a5)
@@ -58,4 +58,5 @@
 extern errno_t ds_window_post_focus_event(ds_window_t *);
 extern errno_t ds_window_post_unfocus_event(ds_window_t *);
+extern void ds_window_move_req(ds_window_t *wnd, gfx_coord2_t *);
 
 #endif
