Index: uspace/app/terminal/terminal.c
===================================================================
--- uspace/app/terminal/terminal.c	(revision 2ee6351d833181079f0e8f8c240f5ac2ade332a3)
+++ uspace/app/terminal/terminal.c	(revision 899bdfda27b53bba2ce96d32870c576201ab6ba8)
@@ -40,5 +40,4 @@
 #include <errno.h>
 #include <fbfont/font-8x16.h>
-#include <io/chargrid.h>
 #include <fibril.h>
 #include <gfx/bitmap.h>
@@ -49,9 +48,11 @@
 #include <io/console.h>
 #include <io/pixelmap.h>
-#include <task.h>
+#include <macros.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <str_error.h>
 #include <str.h>
+#include <task.h>
 #include <ui/resource.h>
 #include <ui/ui.h>
@@ -71,5 +72,31 @@
 	(CONSOLE_CAP_STYLE | CONSOLE_CAP_INDEXED | CONSOLE_CAP_RGB)
 
+#define SCROLLBACK_MAX_LINES 1000
+#define MIN_WINDOW_COLS 8
+#define MIN_WINDOW_ROWS 4
+
 static LIST_INITIALIZE(terms);
+
+#define COLOR_BRIGHT 8
+
+static const pixel_t _basic_colors[16] = {
+	[COLOR_BLACK]       = PIXEL(255, 0, 0, 0),
+	[COLOR_RED]         = PIXEL(255, 170, 0, 0),
+	[COLOR_GREEN]       = PIXEL(255, 0, 170, 0),
+	[COLOR_YELLOW]      = PIXEL(255, 170, 85, 0),
+	[COLOR_BLUE]        = PIXEL(255, 0, 0, 170),
+	[COLOR_MAGENTA]     = PIXEL(255, 170, 0, 170),
+	[COLOR_CYAN]        = PIXEL(255, 0, 170, 170),
+	[COLOR_WHITE]       = PIXEL(255, 170, 170, 170),
+
+	[COLOR_BLACK   | COLOR_BRIGHT] = PIXEL(255, 85, 85, 85),
+	[COLOR_RED     | COLOR_BRIGHT] = PIXEL(255, 255, 85, 85),
+	[COLOR_GREEN   | COLOR_BRIGHT] = PIXEL(255, 85, 255, 85),
+	[COLOR_YELLOW  | COLOR_BRIGHT] = PIXEL(255, 255, 255, 85),
+	[COLOR_BLUE    | COLOR_BRIGHT] = PIXEL(255, 85, 85, 255),
+	[COLOR_MAGENTA | COLOR_BRIGHT] = PIXEL(255, 255, 85, 255),
+	[COLOR_CYAN    | COLOR_BRIGHT] = PIXEL(255, 85, 255, 255),
+	[COLOR_WHITE   | COLOR_BRIGHT] = PIXEL(255, 255, 255, 255),
+};
 
 static errno_t term_open(con_srvs_t *, con_srv_t *);
@@ -119,14 +146,20 @@
 static void terminal_close_event(ui_window_t *, void *);
 static void terminal_focus_event(ui_window_t *, void *, unsigned);
+static void terminal_resize_event(ui_window_t *, void *);
 static void terminal_kbd_event(ui_window_t *, void *, kbd_event_t *);
 static void terminal_pos_event(ui_window_t *, void *, pos_event_t *);
 static void terminal_unfocus_event(ui_window_t *, void *, unsigned);
+static void terminal_maximize_event(ui_window_t *, void *);
+static void terminal_unmaximize_event(ui_window_t *, void *);
 
 static ui_window_cb_t terminal_window_cb = {
 	.close = terminal_close_event,
 	.focus = terminal_focus_event,
+	.resize = terminal_resize_event,
 	.kbd = terminal_kbd_event,
 	.pos = terminal_pos_event,
-	.unfocus = terminal_unfocus_event
+	.unfocus = terminal_unfocus_event,
+	.maximize = terminal_maximize_event,
+	.unmaximize = terminal_unmaximize_event,
 };
 
@@ -144,57 +177,66 @@
 }
 
-static pixel_t color_table[16] = {
-	[COLOR_BLACK]       = PIXEL(255, 0, 0, 0),
-	[COLOR_BLUE]        = PIXEL(255, 0, 0, 170),
-	[COLOR_GREEN]       = PIXEL(255, 0, 170, 0),
-	[COLOR_CYAN]        = PIXEL(255, 0, 170, 170),
-	[COLOR_RED]         = PIXEL(255, 170, 0, 0),
-	[COLOR_MAGENTA]     = PIXEL(255, 170, 0, 170),
-	[COLOR_YELLOW]      = PIXEL(255, 170, 85, 0),
-	[COLOR_WHITE]       = PIXEL(255, 170, 170, 170),
-
-	[COLOR_BLACK + 8]   = PIXEL(255, 85, 85, 85),
-	[COLOR_BLUE + 8]    = PIXEL(255, 85, 85, 255),
-	[COLOR_GREEN + 8]   = PIXEL(255, 85, 255, 85),
-	[COLOR_CYAN + 8]    = PIXEL(255, 85, 255, 255),
-	[COLOR_RED + 8]     = PIXEL(255, 255, 85, 85),
-	[COLOR_MAGENTA + 8] = PIXEL(255, 255, 85, 255),
-	[COLOR_YELLOW + 8]  = PIXEL(255, 255, 255, 85),
-	[COLOR_WHITE + 8]   = PIXEL(255, 255, 255, 255),
-};
-
-static inline void attrs_rgb(char_attrs_t attrs, pixel_t *bgcolor, pixel_t *fgcolor)
-{
-	switch (attrs.type) {
+static pixel_t termui_color_to_pixel(termui_color_t c)
+{
+	uint8_t r, g, b;
+	termui_color_to_rgb(c, &r, &g, &b);
+	return PIXEL(255, r, g, b);
+}
+
+static termui_color_t termui_color_from_pixel(pixel_t pixel)
+{
+	return termui_color_from_rgb(RED(pixel), GREEN(pixel), BLUE(pixel));
+}
+
+static termui_cell_t charfield_to_termui_cell(terminal_t *term, const charfield_t *cf)
+{
+	termui_cell_t cell = { };
+
+	cell.glyph_idx = fb_font_glyph(cf->ch, NULL);
+
+	switch (cf->attrs.type) {
 	case CHAR_ATTR_STYLE:
-		switch (attrs.val.style) {
+		switch (cf->attrs.val.style) {
 		case STYLE_NORMAL:
-			*bgcolor = color_table[COLOR_WHITE + 8];
-			*fgcolor = color_table[COLOR_BLACK];
+			cell.bgcolor = term->default_bgcolor;
+			cell.fgcolor = term->default_fgcolor;
 			break;
 		case STYLE_EMPHASIS:
-			*bgcolor = color_table[COLOR_WHITE + 8];
-			*fgcolor = color_table[COLOR_RED + 8];
+			cell.bgcolor = term->emphasis_bgcolor;
+			cell.fgcolor = term->emphasis_fgcolor;
 			break;
 		case STYLE_INVERTED:
-			*bgcolor = color_table[COLOR_BLACK];
-			*fgcolor = color_table[COLOR_WHITE + 8];
+			cell.bgcolor = term->default_bgcolor;
+			cell.fgcolor = term->default_fgcolor;
+			cell.inverted = 1;
 			break;
 		case STYLE_SELECTED:
-			*bgcolor = color_table[COLOR_RED + 8];
-			*fgcolor = color_table[COLOR_WHITE + 8];
+			cell.bgcolor = term->selection_bgcolor;
+			cell.fgcolor = term->selection_fgcolor;
 			break;
 		}
 		break;
+
 	case CHAR_ATTR_INDEX:
-		*bgcolor = color_table[(attrs.val.index.bgcolor & 7)];
-		*fgcolor = color_table[(attrs.val.index.fgcolor & 7) |
-		    ((attrs.val.index.attr & CATTR_BRIGHT) ? 8 : 0)];
+		char_attr_index_t index = cf->attrs.val.index;
+
+		int bright = (index.attr & CATTR_BRIGHT) ? COLOR_BRIGHT : 0;
+		pixel_t bgcolor = _basic_colors[index.bgcolor | bright];
+		pixel_t fgcolor = _basic_colors[index.fgcolor | bright];
+		cell.bgcolor = termui_color_from_pixel(bgcolor);
+		cell.fgcolor = termui_color_from_pixel(fgcolor);
+
+		if (index.attr & CATTR_BLINK)
+			cell.blink = 1;
+
 		break;
+
 	case CHAR_ATTR_RGB:
-		*bgcolor = 0xff000000 | attrs.val.rgb.bgcolor;
-		*fgcolor = 0xff000000 | attrs.val.rgb.fgcolor;
+		cell.bgcolor = termui_color_from_pixel(cf->attrs.val.rgb.bgcolor);
+		cell.fgcolor = termui_color_from_pixel(cf->attrs.val.rgb.fgcolor);
 		break;
 	}
+
+	return cell;
 }
 
@@ -214,27 +256,33 @@
 }
 
-static void term_update_char(terminal_t *term, pixelmap_t *pixelmap,
-    sysarg_t col, sysarg_t row)
-{
-	charfield_t *field =
-	    chargrid_charfield_at(term->backbuf, col, row);
-
-	bool inverted = chargrid_cursor_at(term->backbuf, col, row);
-
-	sysarg_t bx = col * FONT_WIDTH;
-	sysarg_t by = row * FONT_SCANLINES;
-
-	pixel_t bgcolor = 0;
-	pixel_t fgcolor = 0;
-
-	if (inverted)
-		attrs_rgb(field->attrs, &fgcolor, &bgcolor);
-	else
-		attrs_rgb(field->attrs, &bgcolor, &fgcolor);
-
-	// FIXME: Glyph type should be actually uint32_t
-	//        for full UTF-32 coverage.
-
-	uint16_t glyph = fb_font_glyph(field->ch, NULL);
+static void term_draw_cell(terminal_t *term, pixelmap_t *pixelmap, int col, int row, const termui_cell_t *cell)
+{
+	termui_color_t bg = cell->bgcolor;
+	if (bg == TERMUI_COLOR_DEFAULT)
+		bg = term->default_bgcolor;
+
+	termui_color_t fg = cell->fgcolor;
+	if (fg == TERMUI_COLOR_DEFAULT)
+		fg = term->default_fgcolor;
+
+	pixel_t bgcolor = termui_color_to_pixel(bg);
+	pixel_t fgcolor = termui_color_to_pixel(fg);
+
+	int bx = col * FONT_WIDTH;
+	int by = row * FONT_SCANLINES;
+
+	// TODO: support bold/italic/underline/strike/blink styling
+
+	if (cell->inverted ^ cell->cursor) {
+		pixel_t tmp = bgcolor;
+		bgcolor = fgcolor;
+		fgcolor = tmp;
+	}
+
+	uint32_t glyph = cell->glyph_idx;
+	assert(glyph < FONT_GLYPHS);
+
+	if (glyph == 0)
+		glyph = fb_font_glyph(U' ', NULL);
 
 	for (unsigned int y = 0; y < FONT_SCANLINES; y++) {
@@ -248,190 +296,72 @@
 		}
 	}
+
 	term_update_region(term, bx, by, FONT_WIDTH, FONT_SCANLINES);
 }
 
-static bool term_update_scroll(terminal_t *term, pixelmap_t *pixelmap)
-{
-	sysarg_t top_row = chargrid_get_top_row(term->frontbuf);
-
-	if (term->top_row == top_row) {
-		return false;
-	}
-
-	term->top_row = top_row;
-
-	for (sysarg_t row = 0; row < term->rows; row++) {
-		for (sysarg_t col = 0; col < term->cols; col++) {
-			charfield_t *front_field =
-			    chargrid_charfield_at(term->frontbuf, col, row);
-			charfield_t *back_field =
-			    chargrid_charfield_at(term->backbuf, col, row);
-			bool update = false;
-
-			if (front_field->ch != back_field->ch) {
-				back_field->ch = front_field->ch;
-				update = true;
-			}
-
-			if (!attrs_same(front_field->attrs, back_field->attrs)) {
-				back_field->attrs = front_field->attrs;
-				update = true;
-			}
-
-			front_field->flags &= ~CHAR_FLAG_DIRTY;
-
-			if (update) {
-				term_update_char(term, pixelmap, col, row);
-			}
-		}
-	}
-
-	return true;
-}
-
-static bool term_update_cursor(terminal_t *term, pixelmap_t *pixelmap)
-{
-	bool update = false;
-
-	sysarg_t front_col;
-	sysarg_t front_row;
-	chargrid_get_cursor(term->frontbuf, &front_col, &front_row);
-
-	sysarg_t back_col;
-	sysarg_t back_row;
-	chargrid_get_cursor(term->backbuf, &back_col, &back_row);
-
-	bool front_visibility =
-	    chargrid_get_cursor_visibility(term->frontbuf) &&
-	    term->is_focused;
-	bool back_visibility =
-	    chargrid_get_cursor_visibility(term->backbuf);
-
-	if (front_visibility != back_visibility) {
-		chargrid_set_cursor_visibility(term->backbuf,
-		    front_visibility);
-		term_update_char(term, pixelmap, back_col, back_row);
-		update = true;
-	}
-
-	if ((front_col != back_col) || (front_row != back_row)) {
-		chargrid_set_cursor(term->backbuf, front_col, front_row);
-		term_update_char(term, pixelmap, back_col, back_row);
-		term_update_char(term, pixelmap, front_col, front_row);
-		update = true;
-	}
-
-	return update;
-}
-
-static void term_update(terminal_t *term)
-{
-	pixelmap_t pixelmap;
+static void term_render(terminal_t *term)
+{
+	gfx_coord2_t pos = { .x = 4, .y = 26 };
+	(void) gfx_bitmap_render(term->bmp, &term->update, &pos);
+
+	term->update.p0.x = 0;
+	term->update.p0.y = 0;
+	term->update.p1.x = 0;
+	term->update.p1.y = 0;
+}
+
+static void termui_refresh_cb(void *userdata)
+{
+	terminal_t *term = userdata;
+
+	termui_force_viewport_update(term->termui, 0, termui_get_rows(term->termui));
+}
+
+static void termui_scroll_cb(void *userdata, int delta)
+{
+	(void) delta;
+
+	// Until we have support for hardware accelerated scrolling, just redraw everything.
+	termui_refresh_cb(userdata);
+}
+
+static pixelmap_t term_get_pixelmap(terminal_t *term)
+{
+	pixelmap_t pixelmap = { };
 	gfx_bitmap_alloc_t alloc;
-	gfx_coord2_t pos;
-	errno_t rc;
-
-	rc = gfx_bitmap_get_alloc(term->bmp, &alloc);
-	if (rc != EOK) {
-		return;
-	}
-
-	fibril_mutex_lock(&term->mtx);
+
+	errno_t rc = gfx_bitmap_get_alloc(term->bmp, &alloc);
+	if (rc != EOK)
+		return pixelmap;
+
 	pixelmap.width = term->w;
 	pixelmap.height = term->h;
 	pixelmap.data = alloc.pixels;
-
-	bool update = false;
-
-	if (term_update_scroll(term, &pixelmap)) {
-		update = true;
-	} else {
-		for (sysarg_t y = 0; y < term->rows; y++) {
-			for (sysarg_t x = 0; x < term->cols; x++) {
-				charfield_t *front_field =
-				    chargrid_charfield_at(term->frontbuf, x, y);
-				charfield_t *back_field =
-				    chargrid_charfield_at(term->backbuf, x, y);
-				bool cupdate = false;
-
-				if ((front_field->flags & CHAR_FLAG_DIRTY) ==
-				    CHAR_FLAG_DIRTY) {
-					if (front_field->ch != back_field->ch) {
-						back_field->ch = front_field->ch;
-						cupdate = true;
-					}
-
-					if (!attrs_same(front_field->attrs,
-					    back_field->attrs)) {
-						back_field->attrs = front_field->attrs;
-						cupdate = true;
-					}
-
-					front_field->flags &= ~CHAR_FLAG_DIRTY;
-				}
-
-				if (cupdate) {
-					term_update_char(term, &pixelmap, x, y);
-					update = true;
-				}
-			}
-		}
-	}
-
-	if (term_update_cursor(term, &pixelmap))
-		update = true;
-
-	if (update) {
-		pos.x = 4;
-		pos.y = 26;
-		(void) gfx_bitmap_render(term->bmp, &term->update, &pos);
-
-		term->update.p0.x = 0;
-		term->update.p0.y = 0;
-		term->update.p1.x = 0;
-		term->update.p1.y = 0;
-	}
-
-	fibril_mutex_unlock(&term->mtx);
-}
-
-static void term_repaint(terminal_t *term)
-{
-	pixelmap_t pixelmap;
-	gfx_bitmap_alloc_t alloc;
-	errno_t rc;
-
-	rc = gfx_bitmap_get_alloc(term->bmp, &alloc);
-	if (rc != EOK) {
-		printf("Error getting bitmap allocation info.\n");
+	return pixelmap;
+}
+
+static void term_clear_bitmap(terminal_t *term, pixel_t color)
+{
+	pixelmap_t pixelmap = term_get_pixelmap(term);
+	if (pixelmap.data == NULL)
 		return;
-	}
-
-	fibril_mutex_lock(&term->mtx);
-
-	pixelmap.width = term->w;
-	pixelmap.height = term->h;
-	pixelmap.data = alloc.pixels;
-
-	if (!term_update_scroll(term, &pixelmap)) {
-		for (sysarg_t y = 0; y < term->rows; y++) {
-			for (sysarg_t x = 0; x < term->cols; x++) {
-				charfield_t *front_field =
-				    chargrid_charfield_at(term->frontbuf, x, y);
-				charfield_t *back_field =
-				    chargrid_charfield_at(term->backbuf, x, y);
-
-				back_field->ch = front_field->ch;
-				back_field->attrs = front_field->attrs;
-				front_field->flags &= ~CHAR_FLAG_DIRTY;
-
-				term_update_char(term, &pixelmap, x, y);
-			}
-		}
-	}
-
-	term_update_cursor(term, &pixelmap);
-
-	fibril_mutex_unlock(&term->mtx);
+
+	sysarg_t pixels = pixelmap.height * pixelmap.width;
+	for (sysarg_t i = 0; i < pixels; i++)
+		pixelmap.data[i] = color;
+
+	term_update_region(term, 0, 0, pixelmap.width, pixelmap.height);
+}
+
+static void termui_update_cb(void *userdata, int col, int row, const termui_cell_t *cell, int len)
+{
+	terminal_t *term = userdata;
+
+	pixelmap_t pixelmap = term_get_pixelmap(term);
+	if (pixelmap.data == NULL)
+		return;
+
+	for (int i = 0; i < len; i++)
+		term_draw_cell(term, &pixelmap, col + i, row, &cell[i]);
 }
 
@@ -497,28 +427,24 @@
 static void term_write_char(terminal_t *term, wchar_t ch)
 {
-	sysarg_t updated = 0;
-
-	fibril_mutex_lock(&term->mtx);
-
 	switch (ch) {
-	case '\n':
-		updated = chargrid_newline(term->frontbuf);
+	case L'\n':
+		termui_put_crlf(term->termui);
 		break;
-	case '\r':
+	case L'\r':
+		termui_put_cr(term->termui);
 		break;
-	case '\t':
-		updated = chargrid_tabstop(term->frontbuf, 8);
+	case L'\t':
+		termui_put_tab(term->termui);
 		break;
-	case '\b':
-		updated = chargrid_backspace(term->frontbuf);
+	case L'\b':
+		termui_put_backspace(term->termui);
 		break;
 	default:
-		updated = chargrid_putuchar(term->frontbuf, ch, true);
-	}
-
-	fibril_mutex_unlock(&term->mtx);
-
-	if (updated > 1)
-		term_update(term);
+		// TODO: For some languages, we might need support for combining
+		//       characters. Currently, we assume every unicode code point is
+		//       an individual printed character, which is not always the case.
+		termui_put_glyph(term->termui, fb_font_glyph(ch, NULL), 1);
+		break;
+	}
 }
 
@@ -526,4 +452,6 @@
 {
 	terminal_t *term = srv_to_terminal(srv);
+
+	fibril_mutex_lock(&term->mtx);
 
 	size_t off = 0;
@@ -531,6 +459,10 @@
 		term_write_char(term, str_decode(data, &off, size));
 
+	fibril_mutex_unlock(&term->mtx);
+
+	term_render(term);
 	gfx_update(term->gc);
 	*nwritten = size;
+
 	return EOK;
 }
@@ -540,5 +472,5 @@
 	terminal_t *term = srv_to_terminal(srv);
 
-	term_update(term);
+	term_render(term);
 	gfx_update(term->gc);
 }
@@ -549,8 +481,8 @@
 
 	fibril_mutex_lock(&term->mtx);
-	chargrid_clear(term->frontbuf);
-	fibril_mutex_unlock(&term->mtx);
-
-	term_update(term);
+	termui_clear_screen(term->termui);
+	fibril_mutex_unlock(&term->mtx);
+
+	term_render(term);
 	gfx_update(term->gc);
 }
@@ -561,8 +493,8 @@
 
 	fibril_mutex_lock(&term->mtx);
-	chargrid_set_cursor(term->frontbuf, col, row);
-	fibril_mutex_unlock(&term->mtx);
-
-	term_update(term);
+	termui_set_pos(term->termui, col, row);
+	fibril_mutex_unlock(&term->mtx);
+
+	term_render(term);
 	gfx_update(term->gc);
 }
@@ -573,6 +505,10 @@
 
 	fibril_mutex_lock(&term->mtx);
-	chargrid_get_cursor(term->frontbuf, col, row);
-	fibril_mutex_unlock(&term->mtx);
+	int irow, icol;
+	termui_get_pos(term->termui, &icol, &irow);
+	fibril_mutex_unlock(&term->mtx);
+
+	*col = icol;
+	*row = irow;
 
 	return EOK;
@@ -584,6 +520,6 @@
 
 	fibril_mutex_lock(&term->mtx);
-	*cols = term->cols;
-	*rows = term->rows;
+	*cols = termui_get_cols(term->termui);
+	*rows = termui_get_rows(term->termui);
 	fibril_mutex_unlock(&term->mtx);
 
@@ -603,6 +539,28 @@
 	terminal_t *term = srv_to_terminal(srv);
 
-	fibril_mutex_lock(&term->mtx);
-	chargrid_set_style(term->frontbuf, style);
+	termui_cell_t cellstyle = { };
+
+	switch (style) {
+	case STYLE_NORMAL:
+		cellstyle.bgcolor = term->default_bgcolor;
+		cellstyle.fgcolor = term->default_fgcolor;
+		break;
+	case STYLE_EMPHASIS:
+		cellstyle.bgcolor = term->emphasis_bgcolor;
+		cellstyle.fgcolor = term->emphasis_fgcolor;
+		break;
+	case STYLE_INVERTED:
+		cellstyle.bgcolor = term->default_bgcolor;
+		cellstyle.fgcolor = term->default_fgcolor;
+		cellstyle.inverted = 1;
+		break;
+	case STYLE_SELECTED:
+		cellstyle.bgcolor = term->selection_bgcolor;
+		cellstyle.fgcolor = term->selection_fgcolor;
+		break;
+	}
+
+	fibril_mutex_lock(&term->mtx);
+	termui_set_style(term->termui, cellstyle);
 	fibril_mutex_unlock(&term->mtx);
 }
@@ -613,6 +571,15 @@
 	terminal_t *term = srv_to_terminal(srv);
 
-	fibril_mutex_lock(&term->mtx);
-	chargrid_set_color(term->frontbuf, bgcolor, fgcolor, attr);
+	int bright = (attr & CATTR_BRIGHT) ? COLOR_BRIGHT : 0;
+
+	termui_cell_t cellstyle = { };
+	cellstyle.bgcolor = termui_color_from_pixel(_basic_colors[bgcolor | bright]);
+	cellstyle.fgcolor = termui_color_from_pixel(_basic_colors[fgcolor | bright]);
+
+	if (attr & CATTR_BLINK)
+		cellstyle.blink = 1;
+
+	fibril_mutex_lock(&term->mtx);
+	termui_set_style(term->termui, cellstyle);
 	fibril_mutex_unlock(&term->mtx);
 }
@@ -622,7 +589,11 @@
 {
 	terminal_t *term = srv_to_terminal(srv);
-
-	fibril_mutex_lock(&term->mtx);
-	chargrid_set_rgb_color(term->frontbuf, bgcolor, fgcolor);
+	termui_cell_t cellstyle = {
+		.bgcolor = termui_color_from_pixel(bgcolor),
+		.fgcolor = termui_color_from_pixel(fgcolor),
+	};
+
+	fibril_mutex_lock(&term->mtx);
+	termui_set_style(term->termui, cellstyle);
 	fibril_mutex_unlock(&term->mtx);
 }
@@ -633,8 +604,8 @@
 
 	fibril_mutex_lock(&term->mtx);
-	chargrid_set_cursor_visibility(term->frontbuf, visible);
-	fibril_mutex_unlock(&term->mtx);
-
-	term_update(term);
+	termui_set_cursor_visibility(term->termui, visible);
+	fibril_mutex_unlock(&term->mtx);
+
+	term_render(term);
 	gfx_update(term->gc);
 }
@@ -655,5 +626,5 @@
 	fibril_mutex_unlock(&term->mtx);
 
-	term_update(term);
+	term_render(term);
 	gfx_update(term->gc);
 	return EOK;
@@ -703,4 +674,8 @@
 	term->urows = rows;
 	term->ubuf = buf;
+
+	/* Scroll back to active screen. */
+	termui_history_scroll(term->termui, INT_MAX);
+
 	fibril_mutex_unlock(&term->mtx);
 
@@ -723,8 +698,14 @@
 	term->ubuf = NULL;
 
+	termui_wipe_screen(term->termui, 0);
+
+	fibril_mutex_unlock(&term->mtx);
+
+	/* Update terminal */
+	term_render(term);
+	gfx_update(term->gc);
+
 	if (buf != NULL)
 		as_area_destroy(buf);
-
-	fibril_mutex_unlock(&term->mtx);
 }
 
@@ -741,6 +722,4 @@
 {
 	terminal_t *term = srv_to_terminal(srv);
-	charfield_t *ch;
-	sysarg_t col, row;
 
 	fibril_mutex_lock(&term->mtx);
@@ -752,29 +731,24 @@
 
 	/* Make sure we have meaningful coordinates, within bounds */
-
-	if (c1 > term->ucols)
-		c1 = term->ucols;
-	if (c1 > term->cols)
-		c1 = term->cols;
-	if (c0 >= c1) {
+	c1 = min(c1, term->ucols);
+	c1 = min(c1, (sysarg_t) termui_get_cols(term->termui));
+	r1 = min(r1, term->urows);
+	r1 = min(r1, (sysarg_t) termui_get_rows(term->termui));
+
+	if (c0 >= c1 || r0 >= r1) {
 		fibril_mutex_unlock(&term->mtx);
 		return;
 	}
-	if (r1 > term->urows)
-		r1 = term->urows;
-	if (r1 > term->rows)
-		r1 = term->rows;
-	if (r0 >= r1) {
-		fibril_mutex_unlock(&term->mtx);
-		return;
-	}
 
 	/* Update front buffer from user buffer */
 
-	for (row = r0; row < r1; row++) {
-		for (col = c0; col < c1; col++) {
-			ch = chargrid_charfield_at(term->frontbuf, col, row);
-			*ch = term->ubuf[row * term->ucols + col];
+	for (sysarg_t row = r0; row < r1; row++) {
+		termui_cell_t *cells = termui_get_active_row(term->termui, row);
+
+		for (sysarg_t col = c0; col < c1; col++) {
+			cells[col] = charfield_to_termui_cell(term, &term->ubuf[row * term->ucols + col]);
 		}
+
+		termui_update_cb(term, c0, row, &cells[c0], c1 - c0);
 	}
 
@@ -782,22 +756,64 @@
 
 	/* Update terminal */
-	term_update(term);
+	term_render(term);
 	gfx_update(term->gc);
 }
 
-static void deinit_terminal(terminal_t *term)
+static errno_t terminal_window_resize(terminal_t *term)
+{
+	gfx_rect_t rect;
+	ui_window_get_app_rect(term->window, &rect);
+
+	int width = rect.p1.x - rect.p0.x;
+	int height = rect.p1.y - rect.p0.y;
+
+	if (!term->gc)
+		term->gc = ui_window_get_gc(term->window);
+	else
+		assert(term->gc == ui_window_get_gc(term->window));
+
+	if (!term->ui_res)
+		term->ui_res = ui_window_get_res(term->window);
+	else
+		assert(term->ui_res == ui_window_get_res(term->window));
+
+	gfx_bitmap_t *new_bmp;
+	gfx_bitmap_params_t params;
+	gfx_bitmap_params_init(&params);
+	params.rect.p0.x = 0;
+	params.rect.p0.y = 0;
+	params.rect.p1.x = width;
+	params.rect.p1.y = height;
+
+	errno_t rc = gfx_bitmap_create(term->gc, &params, NULL, &new_bmp);
+	if (rc != EOK) {
+		fprintf(stderr, "Error allocating new screen bitmap: %s\n", str_error(rc));
+		return rc;
+	}
+
+	if (term->bmp) {
+		rc = gfx_bitmap_destroy(term->bmp);
+		if (rc != EOK)
+			fprintf(stderr, "Error deallocating old screen bitmap: %s\n", str_error(rc));
+	}
+
+	term->bmp = new_bmp;
+	term->w = width;
+	term->h = height;
+
+	term_clear_bitmap(term, termui_color_to_pixel(term->default_bgcolor));
+
+	return EOK;
+}
+
+void terminal_destroy(terminal_t *term)
 {
 	list_remove(&term->link);
 
-	if (term->frontbuf)
-		chargrid_destroy(term->frontbuf);
-
-	if (term->backbuf)
-		chargrid_destroy(term->backbuf);
-}
-
-void terminal_destroy(terminal_t *term)
-{
-	deinit_terminal(term);
+	termui_destroy(term->termui);
+
+	if (term->ubuf)
+		as_area_destroy(term->ubuf);
+
 	free(term);
 }
@@ -833,6 +849,44 @@
 	(void)nfocus;
 	term->is_focused = true;
-	term_update(term);
+	term_render(term);
 	gfx_update(term->gc);
+}
+
+static void terminal_resize_handler(ui_window_t *window, void *arg)
+{
+	terminal_t *term = (terminal_t *) arg;
+
+	fibril_mutex_lock(&term->mtx);
+
+	errno_t rc = terminal_window_resize(term);
+	if (rc == EOK) {
+		(void) termui_resize(term->termui, term->w / FONT_WIDTH, term->h / FONT_SCANLINES, SCROLLBACK_MAX_LINES);
+		termui_refresh_cb(term);
+		term_render(term);
+		gfx_update(term->gc);
+
+		cons_event_t event = { .type = CEV_RESIZE };
+		terminal_queue_cons_event(term, &event);
+	}
+
+	fibril_mutex_unlock(&term->mtx);
+}
+
+static void terminal_resize_event(ui_window_t *window, void *arg)
+{
+	ui_window_def_resize(window);
+	terminal_resize_handler(window, arg);
+}
+
+static void terminal_maximize_event(ui_window_t *window, void *arg)
+{
+	ui_window_def_maximize(window);
+	terminal_resize_handler(window, arg);
+}
+
+static void terminal_unmaximize_event(ui_window_t *window, void *arg)
+{
+	ui_window_def_unmaximize(window);
+	terminal_resize_handler(window, arg);
 }
 
@@ -847,5 +901,21 @@
 	event.ev.key = *kbd_event;
 
-	terminal_queue_cons_event(term, &event);
+	const int PAGE_ROWS = (termui_get_rows(term->termui) * 2) / 3;
+
+	fibril_mutex_lock(&term->mtx);
+
+	if (!term->ubuf && kbd_event->type == KEY_PRESS &&
+	    (kbd_event->key == KC_PAGE_UP || kbd_event->key == KC_PAGE_DOWN)) {
+
+		termui_history_scroll(term->termui,
+		    (kbd_event->key == KC_PAGE_UP) ? -PAGE_ROWS : PAGE_ROWS);
+
+		term_render(term);
+		gfx_update(term->gc);
+	} else {
+		terminal_queue_cons_event(term, &event);
+	}
+
+	fibril_mutex_unlock(&term->mtx);
 }
 
@@ -856,18 +926,37 @@
 	terminal_t *term = (terminal_t *) arg;
 
+	switch (event->type) {
+	case POS_UPDATE:
+		return;
+
+	case POS_PRESS:
+	case POS_RELEASE:
+	case POS_DCLICK:
+	}
+
+	/* Ignore mouse events when we're in scrollback mode. */
+	if (termui_scrollback_is_active(term->termui))
+		return;
+
 	sysarg_t sx = -term->off.x;
 	sysarg_t sy = -term->off.y;
 
-	if (event->type == POS_PRESS || event->type == POS_RELEASE ||
-	    event->type == POS_DCLICK) {
-		cevent.type = CEV_POS;
-		cevent.ev.pos.type = event->type;
-		cevent.ev.pos.pos_id = event->pos_id;
-		cevent.ev.pos.btn_num = event->btn_num;
-
-		cevent.ev.pos.hpos = (event->hpos - sx) / FONT_WIDTH;
-		cevent.ev.pos.vpos = (event->vpos - sy) / FONT_SCANLINES;
+	if (event->hpos < sx || event->vpos < sy)
+		return;
+
+	cevent.type = CEV_POS;
+	cevent.ev.pos.type = event->type;
+	cevent.ev.pos.pos_id = event->pos_id;
+	cevent.ev.pos.btn_num = event->btn_num;
+
+	cevent.ev.pos.hpos = (event->hpos - sx) / FONT_WIDTH;
+	cevent.ev.pos.vpos = (event->vpos - sy) / FONT_SCANLINES;
+
+	/* Filter out events outside the terminal area. */
+	int cols = termui_get_cols(term->termui);
+	int rows = termui_get_rows(term->termui);
+
+	if (cevent.ev.pos.hpos < (sysarg_t) cols && cevent.ev.pos.vpos < (sysarg_t) rows)
 		terminal_queue_cons_event(term, &cevent);
-	}
 }
 
@@ -880,5 +969,5 @@
 	if (nfocus == 0) {
 		term->is_focused = false;
-		term_update(term);
+		term_render(term);
 		gfx_update(term->gc);
 	}
@@ -902,7 +991,47 @@
 
 	if (!atomic_flag_test_and_set(&term->refcnt))
-		chargrid_set_cursor_visibility(term->frontbuf, true);
+		termui_set_cursor_visibility(term->termui, true);
 
 	con_conn(icall, &term->srvs);
+}
+
+static errno_t term_init_window(terminal_t *term, const char *display_spec,
+    gfx_coord_t width, gfx_coord_t height,
+    gfx_coord_t min_width, gfx_coord_t min_height,
+    terminal_flags_t flags)
+{
+	gfx_rect_t min_rect = { { 0, 0 }, { min_width, min_height } };
+
+	ui_wnd_params_t wparams;
+	ui_wnd_params_init(&wparams);
+	wparams.caption = "Terminal";
+	wparams.style |= ui_wds_maximize_btn | ui_wds_resizable;
+	if ((flags & tf_topleft) != 0)
+		wparams.placement = ui_wnd_place_top_left;
+
+	errno_t rc = ui_create(display_spec, &term->ui);
+	if (rc != EOK) {
+		printf("Error creating UI on %s.\n", display_spec);
+		return rc;
+	}
+
+	/* Compute wrect such that application area corresponds to rect. */
+	gfx_rect_t wrect;
+	ui_wdecor_rect_from_app(term->ui, wparams.style, &min_rect, &wrect);
+	gfx_rect_rtranslate(&wrect.p0, &wrect, &wparams.rect);
+
+	rc = ui_window_create(term->ui, &wparams, &term->window);
+	if (rc != EOK)
+		return rc;
+
+	gfx_rect_t rect = { { 0, 0 }, { width, height } };
+	ui_wdecor_rect_from_app(term->ui, wparams.style, &rect, &rect);
+	term->off = rect.p0;
+	gfx_rect_rtranslate(&term->off, &rect, &wrect);
+
+	ui_window_resize(term->window, &wrect);
+	ui_window_set_cb(term->window, &terminal_window_cb, (void *) term);
+
+	return terminal_window_resize(term);
 }
 
@@ -911,13 +1040,9 @@
     terminal_t **rterm)
 {
-	terminal_t *term;
-	gfx_bitmap_params_t params;
-	ui_wnd_params_t wparams;
-	gfx_rect_t rect;
-	gfx_coord2_t off;
-	gfx_rect_t wrect;
+	printf("terminal_create(%zu, %zu)\n", width, height);
+
 	errno_t rc;
 
-	term = calloc(1, sizeof(terminal_t));
+	terminal_t *term = calloc(1, sizeof(terminal_t));
 	if (term == NULL) {
 		printf("Out of memory.\n");
@@ -932,81 +1057,31 @@
 	term->char_remains_len = 0;
 
-	term->w = width;
-	term->h = height;
-
-	term->cols = width / FONT_WIDTH;
-	term->rows = height / FONT_SCANLINES;
-
-	term->frontbuf = NULL;
-	term->backbuf = NULL;
-
-	term->frontbuf = chargrid_create(term->cols, term->rows,
-	    CHARGRID_FLAG_NONE);
-	if (!term->frontbuf) {
-		printf("Error creating front buffer.\n");
+	term->default_bgcolor = termui_color_from_pixel(_basic_colors[COLOR_WHITE | COLOR_BRIGHT]);
+	term->default_fgcolor = termui_color_from_pixel(_basic_colors[COLOR_BLACK]);
+
+	term->emphasis_bgcolor = termui_color_from_pixel(_basic_colors[COLOR_WHITE | COLOR_BRIGHT]);
+	term->emphasis_fgcolor = termui_color_from_pixel(_basic_colors[COLOR_RED | COLOR_BRIGHT]);
+
+	term->selection_bgcolor = termui_color_from_pixel(_basic_colors[COLOR_RED | COLOR_BRIGHT]);
+	term->selection_fgcolor = termui_color_from_pixel(_basic_colors[COLOR_WHITE | COLOR_BRIGHT]);
+
+	term->termui = termui_create(width / FONT_WIDTH, height / FONT_SCANLINES,
+	    SCROLLBACK_MAX_LINES);
+	if (!term->termui) {
+		printf("Error creating terminal UI.\n");
 		rc = ENOMEM;
 		goto error;
 	}
 
-	term->backbuf = chargrid_create(term->cols, term->rows,
-	    CHARGRID_FLAG_NONE);
-	if (!term->backbuf) {
-		printf("Error creating back buffer.\n");
-		rc = ENOMEM;
+	termui_set_refresh_cb(term->termui, termui_refresh_cb, term);
+	termui_set_scroll_cb(term->termui, termui_scroll_cb, term);
+	termui_set_update_cb(term->termui, termui_update_cb, term);
+
+	rc = term_init_window(term, display_spec, width, height,
+	    MIN_WINDOW_COLS * FONT_WIDTH, MIN_WINDOW_ROWS * FONT_SCANLINES, flags);
+	if (rc != EOK) {
+		printf("Error creating window (%s).\n", str_error(rc));
 		goto error;
 	}
-
-	rect.p0.x = 0;
-	rect.p0.y = 0;
-	rect.p1.x = width;
-	rect.p1.y = height;
-
-	ui_wnd_params_init(&wparams);
-	wparams.caption = "Terminal";
-	if ((flags & tf_topleft) != 0)
-		wparams.placement = ui_wnd_place_top_left;
-
-	rc = ui_create(display_spec, &term->ui);
-	if (rc != EOK) {
-		printf("Error creating UI on %s.\n", display_spec);
-		goto error;
-	}
-
-	/*
-	 * Compute window rectangle such that application area corresponds
-	 * to rect
-	 */
-	ui_wdecor_rect_from_app(term->ui, wparams.style, &rect, &wrect);
-	off = wrect.p0;
-	gfx_rect_rtranslate(&off, &wrect, &wparams.rect);
-
-	term->off = off;
-
-	rc = ui_window_create(term->ui, &wparams, &term->window);
-	if (rc != EOK) {
-		printf("Error creating window.\n");
-		goto error;
-	}
-
-	term->gc = ui_window_get_gc(term->window);
-	term->ui_res = ui_window_get_res(term->window);
-
-	ui_window_set_cb(term->window, &terminal_window_cb, (void *) term);
-
-	gfx_bitmap_params_init(&params);
-	params.rect.p0.x = 0;
-	params.rect.p0.y = 0;
-	params.rect.p1.x = width;
-	params.rect.p1.y = height;
-
-	rc = gfx_bitmap_create(term->gc, &params, NULL, &term->bmp);
-	if (rc != EOK) {
-		printf("Error allocating screen bitmap.\n");
-		goto error;
-	}
-
-	chargrid_clear(term->frontbuf);
-	chargrid_clear(term->backbuf);
-	term->top_row = 0;
 
 	async_set_fallback_port_handler(term_connection, NULL);
@@ -1046,10 +1121,5 @@
 	term->is_focused = true;
 
-	term->update.p0.x = 0;
-	term->update.p0.y = 0;
-	term->update.p1.x = 0;
-	term->update.p1.y = 0;
-
-	term_repaint(term);
+	termui_refresh_cb(term);
 
 	*rterm = term;
@@ -1064,8 +1134,6 @@
 	if (term->ui != NULL)
 		ui_destroy(term->ui);
-	if (term->frontbuf != NULL)
-		chargrid_destroy(term->frontbuf);
-	if (term->backbuf != NULL)
-		chargrid_destroy(term->backbuf);
+	if (term->termui != NULL)
+		termui_destroy(term->termui);
 	free(term);
 	return rc;
