Index: uspace/app/terminal/terminal.c
===================================================================
--- uspace/app/terminal/terminal.c	(revision b433f680f34a7d7efa45fd40a984f289825e59b4)
+++ uspace/app/terminal/terminal.c	(revision 77ffa018e96bf200a6d7a188492a0574ecda4c99)
@@ -843,5 +843,5 @@
 	sysarg_t sy = -term->off.y;
 
-	if (event->type == POS_PRESS) {
+	if (event->type == POS_PRESS || event->type == POS_RELEASE) {
 		cevent.type = CEV_POS;
 		cevent.ev.pos.type = event->type;
Index: uspace/lib/ui/include/types/ui/ui.h
===================================================================
--- uspace/lib/ui/include/types/ui/ui.h	(revision b433f680f34a7d7efa45fd40a984f289825e59b4)
+++ uspace/lib/ui/include/types/ui/ui.h	(revision 77ffa018e96bf200a6d7a188492a0574ecda4c99)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2021 Jiri Svoboda
  * All rights reserved.
  *
@@ -45,4 +45,14 @@
 #define UI_DISPLAY_DEFAULT NULL
 
+/** Window system */
+typedef enum {
+	/** Unknown */
+	ui_ws_unknown,
+	/** Display service */
+	ui_ws_display,
+	/** Console */
+	ui_ws_console
+} ui_winsys_t;
+
 #endif
 
Index: uspace/lib/ui/include/ui/ui.h
===================================================================
--- uspace/lib/ui/include/ui/ui.h	(revision b433f680f34a7d7efa45fd40a984f289825e59b4)
+++ uspace/lib/ui/include/ui/ui.h	(revision 77ffa018e96bf200a6d7a188492a0574ecda4c99)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2021 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,7 +39,9 @@
 #include <display.h>
 #include <errno.h>
+#include <io/console.h>
 #include <types/ui/ui.h>
 
 extern errno_t ui_create(const char *, ui_t **);
+extern errno_t ui_create_cons(console_ctrl_t *, ui_t **);
 extern errno_t ui_create_disp(display_t *, ui_t **);
 extern void ui_destroy(ui_t *);
Index: uspace/lib/ui/meson.build
===================================================================
--- uspace/lib/ui/meson.build	(revision b433f680f34a7d7efa45fd40a984f289825e59b4)
+++ uspace/lib/ui/meson.build	(revision 77ffa018e96bf200a6d7a188492a0574ecda4c99)
@@ -27,5 +27,5 @@
 #
 
-deps = [ 'gfx', 'gfxfont', 'memgfx', 'display' ]
+deps = [ 'gfx', 'gfxfont', 'memgfx', 'display', 'congfx' ]
 src = files(
 	'src/checkbox.c',
Index: uspace/lib/ui/private/ui.h
===================================================================
--- uspace/lib/ui/private/ui.h	(revision b433f680f34a7d7efa45fd40a984f289825e59b4)
+++ uspace/lib/ui/private/ui.h	(revision 77ffa018e96bf200a6d7a188492a0574ecda4c99)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2021 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,4 +39,5 @@
 
 #include <display.h>
+#include <io/console.h>
 #include <stdbool.h>
 
@@ -46,4 +47,6 @@
  */
 struct ui {
+	/** Console */
+	console_ctrl_t *console;
 	/** Display */
 	display_t *display;
@@ -52,4 +55,6 @@
 	/** @c true if terminating */
 	bool quit;
+	/** Root window (in fullscreen/console mode) */
+	struct ui_window *root_wnd;
 };
 
Index: uspace/lib/ui/private/window.h
===================================================================
--- uspace/lib/ui/private/window.h	(revision b433f680f34a7d7efa45fd40a984f289825e59b4)
+++ uspace/lib/ui/private/window.h	(revision 77ffa018e96bf200a6d7a188492a0574ecda4c99)
@@ -44,4 +44,6 @@
 #include <io/pos_event.h>
 #include <memgfx/memgc.h>
+#include <types/ui/cursor.h>
+#include <types/ui/window.h>
 
 /** Actual structure of window.
Index: uspace/lib/ui/src/ui.c
===================================================================
--- uspace/lib/ui/src/ui.c	(revision b433f680f34a7d7efa45fd40a984f289825e59b4)
+++ uspace/lib/ui/src/ui.c	(revision 77ffa018e96bf200a6d7a188492a0574ecda4c99)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2020 Jiri Svoboda
+ * Copyright (c) 2021 Jiri Svoboda
  * All rights reserved.
  *
@@ -34,11 +34,60 @@
  */
 
+#include <ctype.h>
 #include <display.h>
 #include <errno.h>
 #include <fibril.h>
+#include <io/console.h>
 #include <stdlib.h>
+#include <str.h>
 #include <task.h>
 #include <ui/ui.h>
+#include <ui/wdecor.h>
+#include "../private/window.h"
 #include "../private/ui.h"
+
+/** Parse output specification.
+ *
+ * Output specification has the form <proto>@<service> where proto is
+ * eiher 'disp' for display service or 'cons' for console. Service
+ * is a location ID service name (e.g. hid/display).
+ *
+ * @param ospec Output specification
+ * @param ws Place to store window system type (protocol)
+ * @param osvc Place to store pointer to output service name
+ */
+static void ui_ospec_parse(const char *ospec, ui_winsys_t *ws,
+    const char **osvc)
+{
+	const char *cp;
+
+	if (ospec == UI_DISPLAY_DEFAULT) {
+		*ws = ui_ws_display;
+		*osvc = DISPLAY_DEFAULT;
+		return;
+	}
+
+	cp = ospec;
+	while (isalpha(*cp))
+		++cp;
+
+	if (*cp == '@') {
+		if (str_lcmp(ospec, "disp@", str_length("disp@")) == 0) {
+			*ws = ui_ws_display;
+		} else if (str_lcmp(ospec, "cons@", str_length("cons@")) == 0) {
+			*ws = ui_ws_console;
+		} else {
+			*ws = ui_ws_unknown;
+		}
+
+		if (cp[1] != '\0')
+			*osvc = cp + 1;
+		else
+			*osvc = NULL;
+	} else {
+		*ws = ui_ws_display;
+		*osvc = ospec;
+	}
+}
 
 /** Create new user interface.
@@ -53,17 +102,36 @@
 	errno_t rc;
 	display_t *display;
+	console_ctrl_t *console;
+	ui_winsys_t ws;
+	const char *osvc;
 	ui_t *ui;
 
-	rc = display_open(ospec, &display);
-	if (rc != EOK)
-		return rc;
-
-	rc = ui_create_disp(display, &ui);
-	if (rc != EOK) {
-		display_close(display);
-		return rc;
-	}
-
-	ui->display = display;
+	ui_ospec_parse(ospec, &ws, &osvc);
+
+	if (ws == ui_ws_display) {
+		rc = display_open(osvc, &display);
+		if (rc != EOK)
+			return rc;
+
+		rc = ui_create_disp(display, &ui);
+		if (rc != EOK) {
+			display_close(display);
+			return rc;
+		}
+	} else if (ws == ui_ws_console) {
+		console = console_init(stdin, stdout);
+		if (console == NULL)
+			return EIO;
+
+		/* ws == ui_ws_console */
+		rc = ui_create_cons(console, &ui);
+		if (rc != EOK) {
+			console_done(console);
+			return rc;
+		}
+	} else {
+		return EINVAL;
+	}
+
 	ui->myoutput = true;
 	*rui = ui;
@@ -71,4 +139,22 @@
 }
 
+/** Create new user interface using console service.
+ *
+ * @param rui Place to store pointer to new UI
+ * @return EOK on success or an error code
+ */
+errno_t ui_create_cons(console_ctrl_t *console, ui_t **rui)
+{
+	ui_t *ui;
+
+	ui = calloc(1, sizeof(ui_t));
+	if (ui == NULL)
+		return ENOMEM;
+
+	ui->console = console;
+	*rui = ui;
+	return EOK;
+}
+
 /** Create new user interface using display service.
  *
@@ -99,7 +185,28 @@
 		return;
 
-	if (ui->myoutput)
-		display_close(ui->display);
+	if (ui->myoutput) {
+		if (ui->console != NULL)
+			console_done(ui->console);
+		if (ui->display != NULL)
+			display_close(ui->display);
+	}
+
 	free(ui);
+}
+
+static void ui_cons_event_process(ui_t *ui, cons_event_t *event)
+{
+	if (ui->root_wnd == NULL)
+		return;
+
+	switch (event->type) {
+	case CEV_KEY:
+		ui_window_send_kbd(ui->root_wnd, &event->ev.key);
+		break;
+	case CEV_POS:
+		ui_wdecor_pos_event(ui->root_wnd->wdecor, &event->ev.pos);
+		ui_window_send_pos(ui->root_wnd, &event->ev.pos);
+		break;
+	}
 }
 
@@ -113,8 +220,23 @@
 void ui_run(ui_t *ui)
 {
-	task_retval(0);
-
-	while (!ui->quit)
-		fibril_usleep(100000);
+	bool have_event;
+	cons_event_t event;
+	usec_t timeout;
+
+	/* Only return command prompt if we are running in a separate window */
+	if (ui->display != NULL)
+		task_retval(0);
+
+	while (!ui->quit) {
+		if (ui->console != NULL) {
+			timeout = 100000;
+			have_event = console_get_event_timeout(ui->console,
+			    &event, &timeout);
+			if (have_event)
+				ui_cons_event_process(ui, &event);
+		} else {
+			fibril_usleep(100000);
+		}
+	}
 }
 
Index: uspace/lib/ui/src/window.c
===================================================================
--- uspace/lib/ui/src/window.c	(revision b433f680f34a7d7efa45fd40a984f289825e59b4)
+++ uspace/lib/ui/src/window.c	(revision 77ffa018e96bf200a6d7a188492a0574ecda4c99)
@@ -34,4 +34,5 @@
  */
 
+#include <congfx/console.h>
 #include <display.h>
 #include <errno.h>
@@ -130,5 +131,9 @@
 	gfx_bitmap_t *bmp = NULL;
 	mem_gc_t *memgc = NULL;
+	console_gc_t *cgc;
 	errno_t rc;
+
+	if (ui->root_wnd != NULL)
+		return EEXIST;
 
 	window = calloc(1, sizeof(ui_window_t));
@@ -194,4 +199,10 @@
 		if (rc != EOK)
 			goto error;
+	} else if (ui->console != NULL) {
+		rc = console_gc_create(ui->console, NULL, &cgc);
+		if (rc != EOK)
+			goto error;
+
+		gc = console_gc_get_ctx(cgc);
 	} else {
 		/* Needed for unit tests */
@@ -207,5 +218,7 @@
 	gfx_bitmap_params_init(&bparams);
 #ifndef CONFIG_WIN_DOUBLE_BUF
-	bparams.flags |= bmpf_direct_output;
+	/* Console does not support direct output */
+	if (ui->display != NULL)
+		bparams.flags |= bmpf_direct_output;
 #endif
 
@@ -262,4 +275,6 @@
 	window->cursor = ui_curs_arrow;
 	*rwindow = window;
+
+	ui->root_wnd = window;
 	return EOK;
 error:
@@ -303,5 +318,6 @@
 		gfx_bitmap_destroy(window->bmp);
 	gfx_context_delete(window->gc);
-	display_window_destroy(window->dwindow);
+	if (window->dwindow != NULL)
+		display_window_destroy(window->dwindow);
 	free(window);
 }
@@ -650,5 +666,6 @@
 	ui_window_t *window = (ui_window_t *) arg;
 
-	(void) display_window_move_req(window->dwindow, pos);
+	if (window->dwindow != NULL)
+		(void) display_window_move_req(window->dwindow, pos);
 }
 
@@ -665,5 +682,6 @@
 	ui_window_t *window = (ui_window_t *) arg;
 
-	(void) display_window_resize_req(window->dwindow, rsztype, pos);
+	if (window->dwindow != NULL)
+		(void) display_window_resize_req(window->dwindow, rsztype, pos);
 }
 
@@ -703,5 +721,6 @@
 	}
 
-	(void) display_window_set_cursor(window->dwindow, dcursor);
+	if (window->dwindow != NULL)
+		(void) display_window_set_cursor(window->dwindow, dcursor);
 	window->cursor = cursor;
 }
