Index: uspace/dist/src/c/demos/edit/build
===================================================================
--- uspace/dist/src/c/demos/edit/build	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/edit/build	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,10 @@
+cc -D__PCC__ -I/inc/c -E -o sheet.i sheet.c
+cc -D__PCC__ -I/inc/c -E -o edit.i edit.c
+
+cc -S -o sheet.s sheet.i
+cc -S -o edit.s edit.i
+
+as -o sheet.o sheet.s
+as -o edit.o edit.s
+
+ld -T /inc/_link.ld -o edit_ sheet.o edit.o /lib/libc.a /lib/libsoftint.a
Index: uspace/dist/src/c/demos/edit/clean
===================================================================
--- uspace/dist/src/c/demos/edit/clean	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/edit/clean	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,11 @@
+rm edit_
+
+rm edit.o
+rm sheet.o
+
+rm edit.s
+rm sheet.s
+
+rm edit.i
+rm sheet.i
+
Index: uspace/dist/src/c/demos/edit/edit.c
===================================================================
--- uspace/dist/src/c/demos/edit/edit.c	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/edit/edit.c	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,1167 @@
+/*
+ * Copyright (c) 2009 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup edit
+ * @brief Text editor.
+ * @{
+ */
+/**
+ * @file
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <vfs/vfs.h>
+#include <io/console.h>
+#include <io/style.h>
+#include <io/keycode.h>
+#include <errno.h>
+#include <align.h>
+#include <macros.h>
+#include <clipboard.h>
+#include <bool.h>
+
+#include "sheet.h"
+
+enum redraw_flags {
+	REDRAW_TEXT	= (1 << 0),
+	REDRAW_ROW	= (1 << 1),
+	REDRAW_STATUS	= (1 << 2),
+	REDRAW_CARET	= (1 << 3)
+};
+
+/** Pane
+ *
+ * A rectangular area of the screen used to edit a document. Different
+ * panes can be possibly used to edit the same document.
+ */
+typedef struct {
+	/* Pane dimensions */
+	int rows, columns;
+
+	/* Position of the visible area */
+	int sh_row, sh_column;
+
+	/** Bitmask of components that need redrawing */
+	enum redraw_flags rflags;
+
+	/** Current position of the caret */
+	tag_t caret_pos;
+
+	/** Start of selection */
+	tag_t sel_start;
+
+	/** 
+	 * Ideal column where the caret should try to get. This is used
+	 * for maintaining the same column during vertical movement.
+	 */
+	int ideal_column;
+} pane_t;
+
+/** Document
+ *
+ * Associates a sheet with a file where it can be saved to.
+ */
+typedef struct {
+	char *file_name;
+	sheet_t sh;
+} doc_t;
+
+static console_ctrl_t *con;
+static doc_t doc;
+static bool done;
+static pane_t pane;
+static bool cursor_visible;
+
+static sysarg_t scr_rows;
+static sysarg_t scr_columns;
+
+#define ROW_BUF_SIZE 4096
+#define BUF_SIZE 64
+#define TAB_WIDTH 8
+#define ED_INFTY 65536
+
+/** Maximum filename length that can be entered. */
+#define INFNAME_MAX_LEN 128
+
+static void cursor_show(void);
+static void cursor_hide(void);
+static void cursor_setvis(bool visible);
+
+static void key_handle_unmod(kbd_event_t const *ev);
+static void key_handle_ctrl(kbd_event_t const *ev);
+static void key_handle_shift(kbd_event_t const *ev);
+static void key_handle_movement(unsigned int key, bool shift);
+
+static int file_save(char const *fname);
+static void file_save_as(void);
+static int file_insert(char *fname);
+static int file_save_range(char const *fname, spt_t const *spos,
+    spt_t const *epos);
+static char *filename_prompt(char const *prompt, char const *init_value);
+static char *range_get_str(spt_t const *spos, spt_t const *epos);
+
+static void pane_text_display(void);
+static void pane_row_display(void);
+static void pane_row_range_display(int r0, int r1);
+static void pane_status_display(void);
+static void pane_caret_display(void);
+
+static void insert_char(wchar_t c);
+static void delete_char_before(void);
+static void delete_char_after(void);
+static void caret_update(void);
+static void caret_move(int drow, int dcolumn, enum dir_spec align_dir);
+
+static bool selection_active(void);
+static void selection_sel_all(void);
+static void selection_get_points(spt_t *pa, spt_t *pb);
+static void selection_delete(void);
+static void selection_copy(void);
+static void insert_clipboard_data(void);
+
+static void pt_get_sof(spt_t *pt);
+static void pt_get_eof(spt_t *pt);
+static int tag_cmp(tag_t const *a, tag_t const *b);
+static int spt_cmp(spt_t const *a, spt_t const *b);
+static int coord_cmp(coord_t const *a, coord_t const *b);
+
+static void status_display(char const *str);
+
+
+int main(int argc, char *argv[])
+{
+	kbd_event_t ev;
+	coord_t coord;
+	bool new_file;
+
+	spt_t pt;
+
+	con = console_init(stdin, stdout);
+	console_clear(con);
+
+	console_get_size(con, &scr_columns, &scr_rows);
+
+	pane.rows = scr_rows - 1;
+	pane.columns = scr_columns;
+	pane.sh_row = 1;
+	pane.sh_column = 1;
+
+	/* Start with an empty sheet. */
+	sheet_init(&doc.sh);
+
+	/* Place caret at the beginning of file. */
+	coord.row = coord.column = 1;
+	sheet_get_cell_pt(&doc.sh, &coord, dir_before, &pt);
+	sheet_place_tag(&doc.sh, &pt, &pane.caret_pos);
+	pane.ideal_column = coord.column;
+
+	if (argc == 2) {
+		doc.file_name = str_dup(argv[1]);
+	} else if (argc > 1) {
+		printf("Invalid arguments.\n");
+		return -2;
+	} else {
+		doc.file_name = NULL;
+	}
+
+	new_file = false;
+
+	if (doc.file_name == NULL || file_insert(doc.file_name) != EOK)
+		new_file = true;
+
+	/* Move to beginning of file. */
+	caret_move(-ED_INFTY, -ED_INFTY, dir_before);
+
+	/* Place selection start tag. */
+	tag_get_pt(&pane.caret_pos, &pt);
+	sheet_place_tag(&doc.sh, &pt, &pane.sel_start);
+
+	/* Initial display */
+	cursor_visible = true;
+
+	cursor_hide();
+	console_clear(con);
+	pane_text_display();
+	pane_status_display();
+	if (new_file && doc.file_name != NULL)
+		status_display("File not found. Starting empty file.");
+	pane_caret_display();
+	cursor_show();
+
+	done = false;
+
+	while (!done) {
+		console_get_kbd_event(con, &ev);
+		pane.rflags = 0;
+
+		if (ev.type == KEY_PRESS) {
+			/* Handle key press. */
+			if (((ev.mods & KM_ALT) == 0) &&
+			    ((ev.mods & KM_SHIFT) == 0) &&
+			     (ev.mods & KM_CTRL) != 0) {
+				key_handle_ctrl(&ev);
+			} else if (((ev.mods & KM_ALT) == 0) &&
+			    ((ev.mods & KM_CTRL) == 0) &&
+			     (ev.mods & KM_SHIFT) != 0) {
+				key_handle_shift(&ev);
+			} else if ((ev.mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
+				key_handle_unmod(&ev);
+			}
+		}
+
+		/* Redraw as necessary. */
+
+		cursor_hide();
+
+		if (pane.rflags & REDRAW_TEXT)
+			pane_text_display();
+		if (pane.rflags & REDRAW_ROW)
+			pane_row_display();
+		if (pane.rflags & REDRAW_STATUS)
+			pane_status_display();
+		if (pane.rflags & REDRAW_CARET)
+			pane_caret_display();
+
+		cursor_show();
+	}
+
+	console_clear(con);
+
+	return 0;
+}
+
+static void cursor_show(void)
+{
+	cursor_setvis(true);
+}
+
+static void cursor_hide(void)
+{
+	cursor_setvis(false);
+}
+
+static void cursor_setvis(bool visible)
+{
+	if (cursor_visible != visible) {
+		console_cursor_visibility(con, visible);
+		cursor_visible = visible;
+	}
+}
+
+/** Handle key without modifier. */
+static void key_handle_unmod(kbd_event_t const *ev)
+{
+	switch (ev->key) {
+	case KC_ENTER:
+		selection_delete();
+		insert_char('\n');
+		caret_update();
+		break;
+	case KC_LEFT:
+	case KC_RIGHT:
+	case KC_UP:
+	case KC_DOWN:
+	case KC_HOME:
+	case KC_END:
+	case KC_PAGE_UP:
+	case KC_PAGE_DOWN:
+		key_handle_movement(ev->key, false);
+		break;
+	case KC_BACKSPACE:
+		if (selection_active())
+			selection_delete();
+		else
+			delete_char_before();
+		caret_update();
+		break;
+	case KC_DELETE:
+		if (selection_active())
+			selection_delete();
+		else
+			delete_char_after();
+		caret_update();
+		break;
+	default:
+		if (ev->c >= 32 || ev->c == '\t') {
+			selection_delete();
+			insert_char(ev->c);
+			caret_update();
+		}
+		break;
+	}
+}
+
+/** Handle Shift-key combination. */
+static void key_handle_shift(kbd_event_t const *ev)
+{
+	switch (ev->key) {
+	case KC_LEFT:
+	case KC_RIGHT:
+	case KC_UP:
+	case KC_DOWN:
+	case KC_HOME:
+	case KC_END:
+	case KC_PAGE_UP:
+	case KC_PAGE_DOWN:
+		key_handle_movement(ev->key, true);
+		break;
+	default:
+		if (ev->c >= 32 || ev->c == '\t') {
+			selection_delete();
+			insert_char(ev->c);
+			caret_update();
+		}
+		break;
+	}
+}
+
+/** Handle Ctrl-key combination. */
+static void key_handle_ctrl(kbd_event_t const *ev)
+{
+	switch (ev->key) {
+	case KC_Q:
+		done = true;
+		break;
+	case KC_S:
+		if (doc.file_name != NULL)
+			file_save(doc.file_name);
+		else
+			file_save_as();
+		break;
+	case KC_E:
+		file_save_as();
+		break;
+	case KC_C:
+		selection_copy();
+		break;
+	case KC_V:
+		selection_delete();
+		insert_clipboard_data();
+		pane.rflags |= REDRAW_TEXT;
+		caret_update();
+		break;
+	case KC_X:
+		selection_copy();
+		selection_delete();
+		pane.rflags |= REDRAW_TEXT;
+		caret_update();
+		break;
+	case KC_A:
+		selection_sel_all();
+		break;
+	default:
+		break;
+	}
+}
+
+static void key_handle_movement(unsigned int key, bool select)
+{
+	spt_t pt;
+	spt_t caret_pt;
+	coord_t c_old, c_new;
+	bool had_sel;
+
+	/* Check if we had selection before. */
+	tag_get_pt(&pane.caret_pos, &caret_pt);
+	tag_get_pt(&pane.sel_start, &pt);
+	had_sel = !spt_equal(&caret_pt, &pt);
+
+	switch (key) {
+	case KC_LEFT:
+		caret_move(0, -1, dir_before);
+		break;
+	case KC_RIGHT:
+		caret_move(0, 0, dir_after);
+		break;
+	case KC_UP:
+		caret_move(-1, 0, dir_before);
+		break;
+	case KC_DOWN:
+		caret_move(+1, 0, dir_before);
+		break;
+	case KC_HOME:
+		caret_move(0, -ED_INFTY, dir_before);
+		break;
+	case KC_END:
+		caret_move(0, +ED_INFTY, dir_before);
+		break;
+	case KC_PAGE_UP:
+		caret_move(-pane.rows, 0, dir_before);
+		break;
+	case KC_PAGE_DOWN:
+		caret_move(+pane.rows, 0, dir_before);
+		break;
+	default:
+		break;
+	}
+
+	if (select == false) {
+		/* Move sel_start to the same point as caret. */
+		sheet_remove_tag(&doc.sh, &pane.sel_start);
+		tag_get_pt(&pane.caret_pos, &pt);
+		sheet_place_tag(&doc.sh, &pt, &pane.sel_start);
+	}
+
+	if (select) {
+		tag_get_pt(&pane.caret_pos, &pt);
+		spt_get_coord(&caret_pt, &c_old);
+		spt_get_coord(&pt, &c_new);
+
+		if (c_old.row == c_new.row)
+			pane.rflags |= REDRAW_ROW;
+		else
+			pane.rflags |= REDRAW_TEXT;
+
+	} else if (had_sel == true) {
+		/* Redraw because text was unselected. */
+		pane.rflags |= REDRAW_TEXT;
+	}
+}
+
+/** Save the document. */
+static int file_save(char const *fname)
+{
+	spt_t sp, ep;
+	int rc;
+
+	status_display("Saving...");
+	pt_get_sof(&sp);
+	pt_get_eof(&ep);
+
+	rc = file_save_range(fname, &sp, &ep);
+
+	switch (rc) {
+	case EINVAL:
+		status_display("Error opening file!");
+		break;
+	case EIO:
+		status_display("Error writing data!");
+		break;
+	default:
+		status_display("File saved.");
+		break;
+	}
+
+	return rc;
+}
+
+/** Change document name and save. */
+static void file_save_as(void)
+{
+	const char *old_fname = (doc.file_name != NULL) ? doc.file_name : "";
+	char *fname;
+	
+	fname = filename_prompt("Save As", old_fname);
+	if (fname == NULL) {
+		status_display("Save cancelled.");
+		return;
+	}
+
+	int rc = file_save(fname);
+	if (rc != EOK)
+		return;
+
+	if (doc.file_name != NULL)
+		free(doc.file_name);
+	doc.file_name = fname;
+}
+
+/** Ask for a file name. */
+static char *filename_prompt(char const *prompt, char const *init_value)
+{
+	kbd_event_t ev;
+	char *str;
+	wchar_t buffer[INFNAME_MAX_LEN + 1];
+	int max_len;
+	int nc;
+	bool done;
+
+	asprintf(&str, "%s: %s", prompt, init_value);
+	status_display(str);
+	console_set_pos(con, 1 + str_length(str), scr_rows - 1);
+	free(str);
+
+	console_set_style(con, STYLE_INVERTED);
+
+	max_len = min(INFNAME_MAX_LEN, scr_columns - 4 - str_length(prompt));
+	str_to_wstr(buffer, max_len + 1, init_value);
+	nc = wstr_length(buffer);
+	done = false;
+
+	while (!done) {
+		console_get_kbd_event(con, &ev);
+
+		if (ev.type == KEY_PRESS) {
+			/* Handle key press. */
+			if (((ev.mods & KM_ALT) == 0) &&
+			     (ev.mods & KM_CTRL) != 0) {
+				;
+			} else if ((ev.mods & (KM_CTRL | KM_ALT)) == 0) {
+				switch (ev.key) {
+				case KC_ESCAPE:
+					return NULL;
+				case KC_BACKSPACE:
+					if (nc > 0) {
+						putchar('\b');
+						console_flush(con);
+						--nc;
+					}
+					break;
+				case KC_ENTER:
+					done = true;
+					break;
+				default:
+					if (ev.c >= 32 && nc < max_len) {
+						putchar(ev.c);
+						console_flush(con);
+						buffer[nc++] = ev.c;
+					}
+					break;
+				}
+			}
+		}
+	}
+
+	buffer[nc] = '\0';
+	str = wstr_to_astr(buffer);
+
+	console_set_style(con, STYLE_NORMAL);
+
+	return str;
+}
+
+/** Insert file at caret position.
+ *
+ * Reads in the contents of a file and inserts them at the current position
+ * of the caret.
+ */
+static int file_insert(char *fname)
+{
+	FILE *f;
+	wchar_t c;
+	char buf[BUF_SIZE];
+	int bcnt;
+	int n_read;
+	size_t off;
+
+	f = fopen(fname, "rt");
+	if (f == NULL)
+		return EINVAL;
+
+	bcnt = 0;
+
+	while (true) {
+		if (bcnt < STR_BOUNDS(1)) {
+			n_read = fread(buf + bcnt, 1, BUF_SIZE - bcnt, f);
+			bcnt += n_read;
+		}
+
+		off = 0;
+		c = str_decode(buf, &off, bcnt);
+		if (c == '\0')
+			break;
+
+		bcnt -= off;
+		memcpy(buf, buf + off, bcnt);
+
+		insert_char(c);
+	}
+
+	fclose(f);
+
+	return EOK;
+}
+
+/** Save a range of text into a file. */
+static int file_save_range(char const *fname, spt_t const *spos,
+    spt_t const *epos)
+{
+	FILE *f;
+	char buf[BUF_SIZE];
+	spt_t sp, bep;
+	size_t bytes, n_written;
+
+	f = fopen(fname, "wt");
+	if (f == NULL)
+		return EINVAL;
+
+	sp = *spos;
+
+	do {
+		sheet_copy_out(&doc.sh, &sp, epos, buf, BUF_SIZE, &bep);
+		bytes = str_size(buf);
+
+		n_written = fwrite(buf, 1, bytes, f);
+		if (n_written != bytes) {
+			return EIO;
+		}
+
+		sp = bep;
+	} while (!spt_equal(&bep, epos));
+
+	if (fclose(f) != EOK)
+		return EIO;
+
+	return EOK;
+}
+
+/** Return contents of range as a new string. */
+static char *range_get_str(spt_t const *spos, spt_t const *epos)
+{
+	char *buf;
+	spt_t sp, bep;
+	size_t bytes;
+	size_t buf_size, bpos;
+
+	buf_size = 1;
+
+	buf = malloc(buf_size);
+	if (buf == NULL)
+		return NULL;
+
+	bpos = 0;
+	sp = *spos;
+
+	while (true) {
+		sheet_copy_out(&doc.sh, &sp, epos, &buf[bpos], buf_size - bpos,
+		    &bep);
+		bytes = str_size(&buf[bpos]);
+		bpos += bytes;
+		sp = bep;
+
+		if (spt_equal(&bep, epos))
+			break;
+
+		buf_size *= 2;
+		buf = realloc(buf, buf_size);
+		if (buf == NULL)
+			return NULL;
+	}
+
+	return buf;
+}
+
+static void pane_text_display(void)
+{
+	int sh_rows, rows;
+
+	sheet_get_num_rows(&doc.sh, &sh_rows);
+	rows = min(sh_rows - pane.sh_row + 1, pane.rows);
+
+	/* Draw rows from the sheet. */
+
+	console_set_pos(con, 0, 0);
+	pane_row_range_display(0, rows);
+
+	/* Clear the remaining rows if file is short. */
+	
+	int i;
+	sysarg_t j;
+	for (i = rows; i < pane.rows; ++i) {
+		console_set_pos(con, 0, i);
+		for (j = 0; j < scr_columns; ++j)
+			putchar(' ');
+		console_flush(con);
+	}
+
+	pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
+	pane.rflags &= ~REDRAW_ROW;
+}
+
+/** Display just the row where the caret is. */
+static void pane_row_display(void)
+{
+	spt_t caret_pt;
+	coord_t coord;
+	int ridx;
+
+	tag_get_pt(&pane.caret_pos, &caret_pt);
+	spt_get_coord(&caret_pt, &coord);
+
+	ridx = coord.row - pane.sh_row;
+	pane_row_range_display(ridx, ridx + 1);
+	pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
+}
+
+static void pane_row_range_display(int r0, int r1)
+{
+	int i, j, fill;
+	spt_t rb, re, dep, pt;
+	coord_t rbc, rec;
+	char row_buf[ROW_BUF_SIZE];
+	wchar_t c;
+	size_t pos, size;
+	int s_column;
+	coord_t csel_start, csel_end, ctmp;
+
+	/* Determine selection start and end. */
+
+	tag_get_pt(&pane.sel_start, &pt);
+	spt_get_coord(&pt, &csel_start);
+
+	tag_get_pt(&pane.caret_pos, &pt);
+	spt_get_coord(&pt, &csel_end);
+
+	if (coord_cmp(&csel_start, &csel_end) > 0) {
+		ctmp = csel_start;
+		csel_start = csel_end;
+		csel_end = ctmp;
+	}
+
+	/* Draw rows from the sheet. */
+
+	console_set_pos(con, 0, 0);
+	for (i = r0; i < r1; ++i) {
+		/* Starting point for row display */
+		rbc.row = pane.sh_row + i;
+		rbc.column = pane.sh_column;
+		sheet_get_cell_pt(&doc.sh, &rbc, dir_before, &rb);
+
+		/* Ending point for row display */
+		rec.row = pane.sh_row + i;
+		rec.column = pane.sh_column + pane.columns;
+		sheet_get_cell_pt(&doc.sh, &rec, dir_before, &re);
+
+		/* Copy the text of the row to the buffer. */
+		sheet_copy_out(&doc.sh, &rb, &re, row_buf, ROW_BUF_SIZE, &dep);
+
+		/* Display text from the buffer. */
+
+		if (coord_cmp(&csel_start, &rbc) <= 0 &&
+		    coord_cmp(&rbc, &csel_end) < 0) {
+			console_flush(con);
+			console_set_style(con, STYLE_SELECTED);
+			console_flush(con);
+		}
+
+		console_set_pos(con, 0, i);
+		size = str_size(row_buf);
+		pos = 0;
+		s_column = pane.sh_column;
+		while (pos < size) {
+			if ((csel_start.row == rbc.row) && (csel_start.column == s_column)) {
+				console_flush(con);
+				console_set_style(con, STYLE_SELECTED);
+				console_flush(con);
+			}
+	
+			if ((csel_end.row == rbc.row) && (csel_end.column == s_column)) {
+				console_flush(con);
+				console_set_style(con, STYLE_NORMAL);
+				console_flush(con);
+			}
+	
+			c = str_decode(row_buf, &pos, size);
+			if (c != '\t') {
+				printf("%lc", (wint_t) c);
+				s_column += 1;
+			} else {
+				fill = 1 + ALIGN_UP(s_column, TAB_WIDTH)
+				    - s_column;
+
+				for (j = 0; j < fill; ++j)
+					putchar(' ');
+				s_column += fill;
+			}
+		}
+
+		if ((csel_end.row == rbc.row) && (csel_end.column == s_column)) {
+			console_flush(con);
+			console_set_style(con, STYLE_NORMAL);
+			console_flush(con);
+		}
+
+		/* Fill until the end of display area. */
+
+		if (str_length(row_buf) < (unsigned) scr_columns)
+			fill = scr_columns - str_length(row_buf);
+		else
+			fill = 0;
+
+		for (j = 0; j < fill; ++j)
+			putchar(' ');
+		console_flush(con);
+		console_set_style(con, STYLE_NORMAL);
+	}
+
+	pane.rflags |= REDRAW_CARET;
+}
+
+/** Display pane status in the status line. */
+static void pane_status_display(void)
+{
+	spt_t caret_pt;
+	coord_t coord;
+
+	tag_get_pt(&pane.caret_pos, &caret_pt);
+	spt_get_coord(&caret_pt, &coord);
+
+	const char *fname = (doc.file_name != NULL) ? doc.file_name : "<unnamed>";
+
+	console_set_pos(con, 0, scr_rows - 1);
+	console_set_style(con, STYLE_INVERTED);
+	int n = printf(" %d, %d: File '%s'. Ctrl-Q Quit  Ctrl-S Save  "
+	    "Ctrl-E Save As", coord.row, coord.column, fname);
+	
+	int pos = scr_columns - 1 - n;
+	printf("%*s", pos, "");
+	console_flush(con);
+	console_set_style(con, STYLE_NORMAL);
+
+	pane.rflags |= REDRAW_CARET;
+}
+
+/** Set cursor to reflect position of the caret. */
+static void pane_caret_display(void)
+{
+	spt_t caret_pt;
+	coord_t coord;
+
+	tag_get_pt(&pane.caret_pos, &caret_pt);
+
+	spt_get_coord(&caret_pt, &coord);
+	console_set_pos(con, coord.column - pane.sh_column,
+	    coord.row - pane.sh_row);
+}
+
+/** Insert a character at caret position. */
+static void insert_char(wchar_t c)
+{
+	spt_t pt;
+	char cbuf[STR_BOUNDS(1) + 1];
+	size_t offs;
+
+	tag_get_pt(&pane.caret_pos, &pt);
+
+	offs = 0;
+	chr_encode(c, cbuf, &offs, STR_BOUNDS(1) + 1);
+	cbuf[offs] = '\0';
+
+	(void) sheet_insert(&doc.sh, &pt, dir_before, cbuf);
+
+	pane.rflags |= REDRAW_ROW;
+	if (c == '\n')
+		pane.rflags |= REDRAW_TEXT;
+}
+
+/** Delete the character before the caret. */
+static void delete_char_before(void)
+{
+	spt_t sp, ep;
+	coord_t coord;
+
+	tag_get_pt(&pane.caret_pos, &ep);
+	spt_get_coord(&ep, &coord);
+
+	coord.column -= 1;
+	sheet_get_cell_pt(&doc.sh, &coord, dir_before, &sp);
+
+	(void) sheet_delete(&doc.sh, &sp, &ep);
+
+	pane.rflags |= REDRAW_ROW;
+	if (coord.column < 1)
+		pane.rflags |= REDRAW_TEXT;
+}
+
+/** Delete the character after the caret. */
+static void delete_char_after(void)
+{
+	spt_t sp, ep;
+	coord_t sc, ec;
+
+	tag_get_pt(&pane.caret_pos, &sp);
+	spt_get_coord(&sp, &sc);
+
+	sheet_get_cell_pt(&doc.sh, &sc, dir_after, &ep);
+	spt_get_coord(&ep, &ec);
+
+	(void) sheet_delete(&doc.sh, &sp, &ep);
+
+	pane.rflags |= REDRAW_ROW;
+	if (ec.row != sc.row)
+		pane.rflags |= REDRAW_TEXT;
+}
+
+/** Scroll pane after caret has moved.
+ *
+ * After modifying the position of the caret, this is called to scroll
+ * the pane to ensure that the caret is in the visible area.
+ */
+static void caret_update(void)
+{
+	spt_t pt;
+	coord_t coord;
+
+	tag_get_pt(&pane.caret_pos, &pt);
+	spt_get_coord(&pt, &coord);
+
+	/* Scroll pane vertically. */
+
+	if (coord.row < pane.sh_row) {
+		pane.sh_row = coord.row;
+		pane.rflags |= REDRAW_TEXT;
+	}
+
+	if (coord.row > pane.sh_row + pane.rows - 1) {
+		pane.sh_row = coord.row - pane.rows + 1;
+		pane.rflags |= REDRAW_TEXT;
+	}
+
+	/* Scroll pane horizontally. */
+
+	if (coord.column < pane.sh_column) {
+		pane.sh_column = coord.column;
+		pane.rflags |= REDRAW_TEXT;
+	}
+
+	if (coord.column > pane.sh_column + pane.columns - 1) {
+		pane.sh_column = coord.column - pane.columns + 1;
+		pane.rflags |= REDRAW_TEXT;
+	}
+
+	pane.rflags |= (REDRAW_CARET | REDRAW_STATUS);
+}
+
+/** Change the caret position.
+ *
+ * Moves caret relatively to the current position. Looking at the first
+ * character cell after the caret and moving by @a drow and @a dcolumn, we get
+ * to a new character cell, and thus a new character. Then we either go to the
+ * point before the the character or after it, depending on @a align_dir.
+ */
+static void caret_move(int drow, int dcolumn, enum dir_spec align_dir)
+{
+	spt_t pt;
+	coord_t coord;
+	int num_rows;
+	bool pure_vertical;
+
+	tag_get_pt(&pane.caret_pos, &pt);
+	spt_get_coord(&pt, &coord);
+	coord.row += drow; coord.column += dcolumn;
+
+	/* Clamp coordinates. */
+	if (drow < 0 && coord.row < 1) coord.row = 1;
+	if (dcolumn < 0 && coord.column < 1) coord.column = 1;
+	if (drow > 0) {
+		sheet_get_num_rows(&doc.sh, &num_rows);
+		if (coord.row > num_rows) coord.row = num_rows;
+	}
+
+	/* For purely vertical movement try attaining @c ideal_column. */
+	pure_vertical = (dcolumn == 0 && align_dir == dir_before);
+	if (pure_vertical)
+		coord.column = pane.ideal_column;
+
+	/*
+	 * Select the point before or after the character at the designated
+	 * coordinates. The character can be wider than one cell (e.g. tab).
+	 */
+	sheet_get_cell_pt(&doc.sh, &coord, align_dir, &pt);
+	sheet_remove_tag(&doc.sh, &pane.caret_pos);
+	sheet_place_tag(&doc.sh, &pt, &pane.caret_pos);
+
+	/* For non-vertical movement set the new value for @c ideal_column. */
+	if (!pure_vertical) {
+		spt_get_coord(&pt, &coord);
+		pane.ideal_column = coord.column;
+	}
+
+	caret_update();
+}
+
+/** Check for non-empty selection. */
+static bool selection_active(void)
+{
+	return (tag_cmp(&pane.caret_pos, &pane.sel_start) != 0);
+}
+
+static void selection_get_points(spt_t *pa, spt_t *pb)
+{
+	spt_t pt;
+
+	tag_get_pt(&pane.sel_start, pa);
+	tag_get_pt(&pane.caret_pos, pb);
+
+	if (spt_cmp(pa, pb) > 0) {
+		pt = *pa;
+		*pa = *pb;
+		*pb = pt;
+	}
+}
+
+/** Delete selected text. */
+static void selection_delete(void)
+{
+	spt_t pa, pb;
+	coord_t ca, cb;
+	int rel;
+
+	tag_get_pt(&pane.sel_start, &pa);
+	tag_get_pt(&pane.caret_pos, &pb);
+	spt_get_coord(&pa, &ca);
+	spt_get_coord(&pb, &cb);
+	rel = coord_cmp(&ca, &cb);
+
+	if (rel == 0)
+		return;
+
+	if (rel < 0)
+		sheet_delete(&doc.sh, &pa, &pb);
+	else
+		sheet_delete(&doc.sh, &pb, &pa);
+
+	if (ca.row == cb.row)
+		pane.rflags |= REDRAW_ROW;
+	else
+		pane.rflags |= REDRAW_TEXT;
+}
+
+static void selection_sel_all(void)
+{
+	spt_t spt, ept;
+
+	pt_get_sof(&spt);
+	pt_get_eof(&ept);
+	sheet_remove_tag(&doc.sh, &pane.sel_start);
+	sheet_place_tag(&doc.sh, &spt, &pane.sel_start);
+	sheet_remove_tag(&doc.sh, &pane.caret_pos);
+	sheet_place_tag(&doc.sh, &ept, &pane.caret_pos);
+
+	pane.rflags |= REDRAW_TEXT;
+	caret_update();
+}
+
+static void selection_copy(void)
+{
+	spt_t pa, pb;
+	char *str;
+
+	selection_get_points(&pa, &pb);
+	str = range_get_str(&pa, &pb);
+	if (str == NULL || clipboard_put_str(str) != EOK) {
+		status_display("Copying to clipboard failed!");
+	}
+	free(str);
+}
+
+static void insert_clipboard_data(void)
+{
+	char *str;
+	size_t off;
+	wchar_t c;
+	int rc;
+
+	rc = clipboard_get_str(&str);
+	if (rc != EOK || str == NULL)
+		return;
+
+	off = 0;
+
+	while (true) {
+		c = str_decode(str, &off, STR_NO_LIMIT);
+		if (c == '\0')
+			break;
+
+		insert_char(c);
+	}
+
+	free(str);
+}
+
+/** Get start-of-file s-point. */
+static void pt_get_sof(spt_t *pt)
+{
+	coord_t coord;
+
+	coord.row = coord.column = 1;
+	sheet_get_cell_pt(&doc.sh, &coord, dir_before, pt);
+}
+
+/** Get end-of-file s-point. */
+static void pt_get_eof(spt_t *pt)
+{
+	coord_t coord;
+	int num_rows;
+
+	sheet_get_num_rows(&doc.sh, &num_rows);
+	coord.row = num_rows + 1;
+	coord.column = 1;
+
+	sheet_get_cell_pt(&doc.sh, &coord, dir_after, pt);
+}
+
+/** Compare tags. */
+static int tag_cmp(tag_t const *a, tag_t const *b)
+{
+	spt_t pa, pb;
+
+	tag_get_pt(a, &pa);
+	tag_get_pt(b, &pb);
+
+	return spt_cmp(&pa, &pb);
+}
+
+/** Compare s-points. */
+static int spt_cmp(spt_t const *a, spt_t const *b)
+{
+	coord_t ca, cb;
+
+	spt_get_coord(a, &ca);
+	spt_get_coord(b, &cb);
+
+	return coord_cmp(&ca, &cb);
+}
+
+/** Compare coordinats. */
+static int coord_cmp(coord_t const *a, coord_t const *b)
+{
+	if (a->row - b->row != 0)
+		return a->row - b->row;
+
+	return a->column - b->column;
+}
+
+/** Display text in the status line. */
+static void status_display(char const *str)
+{
+	console_set_pos(con, 0, scr_rows - 1);
+	console_set_style(con, STYLE_INVERTED);
+	
+	int pos = -(scr_columns - 3);
+	printf(" %*s ", pos, str);
+	console_flush(con);
+	console_set_style(con, STYLE_NORMAL);
+
+	pane.rflags |= REDRAW_CARET;
+}
+
+/** @}
+ */
Index: uspace/dist/src/c/demos/edit/sheet.c
===================================================================
--- uspace/dist/src/c/demos/edit/sheet.c	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/edit/sheet.c	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) 2009 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup edit
+ * @{
+ */
+/**
+ * @file
+ * @brief Prototype implementation of Sheet data structure.
+ *
+ * The sheet is an abstract data structure representing a piece of text.
+ * On top of this data structure we can implement a text editor. It is
+ * possible to implement the sheet such that the editor can make small
+ * changes to large files or files containing long lines efficiently.
+ *
+ * The sheet structure allows basic operations of text insertion, deletion,
+ * retrieval and mapping of coordinates to position in the file and vice
+ * versa. The text that is inserted or deleted can contain tabs and newlines
+ * which are interpreted and properly acted upon.
+ *
+ * This is a trivial implementation with poor efficiency with O(N+n)
+ * insertion and deletion and O(N) mapping (in both directions), where
+ * N is the size of the file and n is the size of the inserted/deleted text.
+ */
+
+#include <stdlib.h>
+#include <str.h>
+#include <errno.h>
+#include <adt/list.h>
+#include <align.h>
+#include <macros.h>
+
+#include "sheet.h"
+
+enum {
+	TAB_WIDTH	= 8,
+
+	/** Initial  of dat buffer in bytes */
+	INITIAL_SIZE	= 32
+};
+
+/** Initialize an empty sheet. */
+int sheet_init(sheet_t *sh)
+{
+	sh->dbuf_size = INITIAL_SIZE;
+	sh->text_size = 0;
+
+	sh->data = malloc(sh->dbuf_size);
+	if (sh->data == NULL)
+		return ENOMEM;
+
+	list_initialize(&sh->tags);
+
+	return EOK;
+}
+
+/** Insert text into sheet.
+ *
+ * @param sh	Sheet to insert to.
+ * @param pos	Point where to insert.
+ * @param dir	Whether to insert before or after the point (affects tags).
+ * @param str	The text to insert (printable characters, tabs, newlines).
+ *
+ * @return	EOK on success or negative error code.
+ *
+ * @note	@a dir affects which way tags that were placed on @a pos will
+ * 		move. If @a dir is @c dir_before, the tags will move forward
+ *		and vice versa.
+ */
+int sheet_insert(sheet_t *sh, spt_t *pos, enum dir_spec dir, char *str)
+{
+	char *ipp;
+	size_t sz;
+	tag_t *tag;
+	char *newp;
+
+	sz = str_size(str);
+	if (sh->text_size + sz > sh->dbuf_size) {
+		/* Enlarge data buffer. */
+		newp = realloc(sh->data, sh->dbuf_size * 2);
+		if (newp == NULL)
+			return ELIMIT;
+
+		sh->data = newp;
+		sh->dbuf_size = sh->dbuf_size * 2;
+	}
+
+	ipp = sh->data + pos->b_off;
+
+	/* Copy data. */
+	memmove(ipp + sz, ipp, sh->text_size - pos->b_off);
+	memcpy(ipp, str, sz);
+	sh->text_size += sz;
+
+	/* Adjust tags. */
+
+	list_foreach(sh->tags, link) {
+		tag = list_get_instance(link, tag_t, link);
+
+		if (tag->b_off > pos->b_off)
+			tag->b_off += sz;
+		else if (tag->b_off == pos->b_off && dir == dir_before)
+			tag->b_off += sz;
+	}
+
+	return EOK;
+}
+
+/** Delete text from sheet.
+ *
+ * Deletes the range of text between two points from the sheet.
+ *
+ * @param sh	Sheet to delete from.
+ * @param spos	Starting point.
+ * @param epos	Ending point.
+ *
+ * @return	EOK on success or negative error code.
+ **/
+int sheet_delete(sheet_t *sh, spt_t *spos, spt_t *epos)
+{
+	char *spp;
+	size_t sz;
+	tag_t *tag;
+	char *newp;
+	size_t shrink_size;
+
+	spp = sh->data + spos->b_off;
+	sz = epos->b_off - spos->b_off;
+
+	memmove(spp, spp + sz, sh->text_size - (spos->b_off + sz));
+	sh->text_size -= sz;
+
+	/* Adjust tags. */
+	list_foreach(sh->tags, link) {
+		tag = list_get_instance(link, tag_t, link);
+
+		if (tag->b_off >= epos->b_off)
+			tag->b_off -= sz;
+		else if (tag->b_off >= spos->b_off)
+			tag->b_off = spos->b_off;
+	}
+
+	/* See if we should free up some memory. */
+	shrink_size = max(sh->dbuf_size / 4, INITIAL_SIZE);
+	if (sh->text_size <= shrink_size && sh->dbuf_size > INITIAL_SIZE) {
+		/* Shrink data buffer. */
+		newp = realloc(sh->data, shrink_size);
+		if (newp == NULL) {
+			/* Failed to shrink buffer... no matter. */
+			return EOK;
+		}
+
+		sh->data = newp;
+		sh->dbuf_size = shrink_size;
+	}
+
+	return EOK;
+}
+
+/** Read text from sheet. */
+void sheet_copy_out(sheet_t *sh, spt_t const *spos, spt_t const *epos,
+    char *buf, size_t bufsize, spt_t *fpos)
+{
+	char *spp;
+	size_t range_sz;
+	size_t copy_sz;
+	size_t off, prev;
+	wchar_t c;
+
+	spp = sh->data + spos->b_off;
+	range_sz = epos->b_off - spos->b_off;
+	copy_sz = (range_sz < bufsize - 1) ? range_sz : bufsize - 1;
+
+	prev = off = 0;
+	do {
+		prev = off;
+		c = str_decode(spp, &off, copy_sz);
+	} while (c != '\0');
+
+	/* Crop copy_sz down to the last full character. */
+	copy_sz = prev;
+
+	memcpy(buf, spp, copy_sz);
+	buf[copy_sz] = '\0';
+
+	fpos->b_off = spos->b_off + copy_sz;
+	fpos->sh = sh;
+}
+
+/** Get point preceding or following character cell. */
+void sheet_get_cell_pt(sheet_t *sh, coord_t const *coord, enum dir_spec dir,
+    spt_t *pt)
+{
+	size_t cur_pos, prev_pos;
+	wchar_t c;
+	coord_t cc;
+
+	cc.row = cc.column = 1;
+	cur_pos = prev_pos = 0;
+	while (true) {
+		if (prev_pos >= sh->text_size) {
+			/* Cannot advance any further. */
+			break;
+		}
+
+		if ((cc.row >= coord->row && cc.column > coord->column) ||
+		    cc.row > coord->row) {
+			/* We are past the requested coordinates. */
+			break;
+		}
+
+		prev_pos = cur_pos;
+
+		c = str_decode(sh->data, &cur_pos, sh->text_size);
+		if (c == '\n') {
+			++cc.row;
+			cc.column = 1;
+		} else if (c == '\t') {
+			cc.column = 1 + ALIGN_UP(cc.column, TAB_WIDTH);
+		} else {
+			++cc.column;
+		}
+	}
+
+	pt->sh = sh;
+	pt->b_off = (dir == dir_before) ? prev_pos : cur_pos;
+}
+
+/** Get the number of character cells a row occupies. */
+void sheet_get_row_width(sheet_t *sh, int row, int *length)
+{
+	coord_t coord;
+	spt_t pt;
+
+	/* Especially nasty hack */
+	coord.row = row;
+	coord.column = 65536;
+	
+	sheet_get_cell_pt(sh, &coord, dir_before, &pt);
+	spt_get_coord(&pt, &coord);
+	*length = coord.column - 1;
+}
+
+/** Get the number of rows in a sheet. */
+void sheet_get_num_rows(sheet_t *sh, int *rows)
+{
+	int cnt;
+	size_t bo;
+
+	cnt = 1;
+	for (bo = 0; bo < sh->dbuf_size; ++bo) {
+		if (sh->data[bo] == '\n')
+			cnt += 1;
+	}
+
+	*rows = cnt;
+}
+
+/** Get the coordinates of an s-point. */
+void spt_get_coord(spt_t const *pos, coord_t *coord)
+{
+	size_t off;
+	coord_t cc;
+	wchar_t c;
+	sheet_t *sh;
+
+	sh = pos->sh;
+	cc.row = cc.column = 1;
+
+	off = 0;
+	while (off < pos->b_off && off < sh->text_size) {
+		c = str_decode(sh->data, &off, sh->text_size);
+		if (c == '\n') {
+			++cc.row;
+			cc.column = 1;
+		} else if (c == '\t') {
+			cc.column = 1 + ALIGN_UP(cc.column, TAB_WIDTH);
+		} else {
+			++cc.column;
+		}
+	}
+
+	*coord = cc;
+}
+
+/** Test if two s-points are equal. */
+bool spt_equal(spt_t const *a, spt_t const *b)
+{
+	return a->b_off == b->b_off;
+}
+
+/** Place a tag on the specified s-point. */
+void sheet_place_tag(sheet_t *sh, spt_t const *pt, tag_t *tag)
+{
+	tag->b_off = pt->b_off;
+	tag->sh = sh;
+	list_append(&tag->link, &sh->tags);
+}
+
+/** Remove a tag from the sheet. */
+void sheet_remove_tag(sheet_t *sh, tag_t *tag)
+{
+	list_remove(&tag->link);
+}
+
+/** Get s-point on which the tag is located right now. */
+void tag_get_pt(tag_t const *tag, spt_t *pt)
+{
+	pt->b_off = tag->b_off;
+	pt->sh = tag->sh;
+}
+
+/** @}
+ */
Index: uspace/dist/src/c/demos/edit/sheet.h
===================================================================
--- uspace/dist/src/c/demos/edit/sheet.h	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/edit/sheet.h	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2009 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup edit
+ * @{
+ */
+/**
+ * @file
+ */
+
+#ifndef SHEET_H__
+#define SHEET_H__
+
+#include <adt/list.h>
+#include <sys/types.h>
+#include <bool.h>
+
+/** Direction (in linear space) */
+enum dir_spec {
+	/** Before specified point */
+	dir_before,
+	/** After specified point */
+	dir_after
+};
+
+/** Sheet */
+typedef struct {
+	/* Note: This structure is opaque for the user. */
+
+	size_t text_size;
+	size_t dbuf_size;
+	char *data;
+
+	list_t tags;
+} sheet_t;
+
+/** Character cell coordinates
+ *
+ * These specify a character cell. The first cell is (1,1).
+ */
+typedef struct {
+	int row;
+	int column;
+} coord_t;
+
+/** S-point
+ *
+ * An s-point specifies the boundary between two successive characters
+ * in the linear file space (including the beginning of file or the end
+ * of file. An s-point only remains valid as long as no modifications
+ * (insertions/deletions) are performed on the sheet.
+ */
+typedef struct {
+	/* Note: This structure is opaque for the user. */
+	sheet_t *sh;
+	size_t b_off;
+} spt_t;
+
+/** Tag
+ *
+ * A tag is similar to an s-point, but remains valid over modifications
+ * to the sheet. A tag tends to 'stay put'. Any tag must be properly
+ * removed from the sheet before it is deallocated by the user.
+ */
+typedef struct {
+	/* Note: This structure is opaque for the user. */
+
+	/** Link to list of all tags in the sheet (see sheet_t.tags) */
+	link_t link;
+	sheet_t *sh;
+	size_t b_off;
+} tag_t;
+
+extern int sheet_init(sheet_t *);
+extern int sheet_insert(sheet_t *, spt_t *, enum dir_spec, char *);
+extern int sheet_delete(sheet_t *, spt_t *, spt_t *);
+extern void sheet_copy_out(sheet_t *, spt_t const *, spt_t const *, char *,
+    size_t, spt_t *);
+extern void sheet_get_cell_pt(sheet_t *, coord_t const *, enum dir_spec,
+    spt_t *);
+extern void sheet_get_row_width(sheet_t *, int, int *);
+extern void sheet_get_num_rows(sheet_t *, int *);
+extern void spt_get_coord(spt_t const *, coord_t *);
+extern bool spt_equal(spt_t const *, spt_t const *);
+
+extern void sheet_place_tag(sheet_t *, spt_t const *, tag_t *);
+extern void sheet_remove_tag(sheet_t *, tag_t *);
+extern void tag_get_pt(tag_t const *, spt_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/dist/src/c/demos/hello/build
===================================================================
--- uspace/dist/src/c/demos/hello/build	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/hello/build	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,4 @@
+cc -E -o hello.i hello.c
+cc -S -o hello.s hello.i
+as -o hello.o hello.s
+ld -T /inc/_link.ld -o hello hello.o /lib/libc.a /lib/libsoftint.a
Index: uspace/dist/src/c/demos/hello/clean
===================================================================
--- uspace/dist/src/c/demos/hello/clean	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/hello/clean	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,4 @@
+rm hello
+rm hello.o
+rm hello.s
+rm hello.i
Index: uspace/dist/src/c/demos/hello/hello.c
===================================================================
--- uspace/dist/src/c/demos/hello/hello.c	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/hello/hello.c	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,16 @@
+extern int putchar(char);
+
+#define TERMINATOR '!'
+
+int main(void) {
+	/* Prints "hello" to the standard output. */
+	putchar('h');
+	putchar('e');
+	putchar('l');
+	putchar('l');
+	putchar('o');
+	putchar(TERMINATOR);
+	putchar('\n');
+	return 0;
+}
+
Index: uspace/dist/src/c/demos/tetris/build
===================================================================
--- uspace/dist/src/c/demos/tetris/build	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/tetris/build	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,16 @@
+cc -D__PCC__ -I/inc/c -E -o scores.i scores.c
+cc -D__PCC__ -I/inc/c -E -o screen.i screen.c
+cc -D__PCC__ -I/inc/c -E -o shapes.i shapes.c
+cc -D__PCC__ -I/inc/c -E -o tetris.i tetris.c
+
+cc -S -o scores.s scores.i
+cc -S -o screen.s screen.i
+cc -S -o shapes.s shapes.i
+cc -S -o tetris.s tetris.i
+
+as -o scores.o scores.s
+as -o screen.o screen.s
+as -o shapes.o shapes.s
+as -o tetris.o tetris.s
+
+ld -T /inc/_link.ld -o tetris_ scores.o screen.o shapes.o tetris.o /lib/libc.a /lib/libsoftint.a
Index: uspace/dist/src/c/demos/tetris/clean
===================================================================
--- uspace/dist/src/c/demos/tetris/clean	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/tetris/clean	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,16 @@
+rm tetris_
+
+rm scores.o
+rm screen.o
+rm shapes.o
+rm tetris.o
+
+rm scores.s
+rm screen.s
+rm shapes.s
+rm tetris.s
+
+rm scores.i
+rm screen.i
+rm shapes.i
+rm tetris.i
Index: uspace/dist/src/c/demos/tetris/scores.c
===================================================================
--- uspace/dist/src/c/demos/tetris/scores.c	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/tetris/scores.c	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2011 Martin Decky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** Attributations
+ *
+ * scores.c 8.1 (Berkeley) 5/31/93
+ * NetBSD: scores.c,v 1.2 1995/04/22 07:42:38 cgd
+ * OpenBSD: scores.c,v 1.11 2006/04/20 03:25:36 ray
+ *
+ * Based upon BSD Tetris
+ *
+ * Copyright (c) 1992, 1993
+ *      The Regents of the University of California.
+ *      Distributed under BSD license.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek and Darren F. Provine.
+ *
+ */
+
+/** @addtogroup tetris
+ * @{
+ */
+/** @file
+ */
+
+/*
+ * Score code for Tetris, by Darren Provine (kilroy@gboro.glassboro.edu)
+ * modified 22 January 1992, to limit the number of entries any one
+ * person has.
+ *
+ * Major whacks since then.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <str.h>
+#include <io/console.h>
+#include <io/keycode.h>
+#include <vfs/vfs.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <err.h>
+#include <time.h>
+#include "screen.h"
+#include "tetris.h"
+#include "scores.h"
+
+/*
+ * Within this code, we can hang onto one extra "high score", leaving
+ * room for our current score (whether or not it is high).
+ *
+ * We also sometimes keep tabs on the "highest" score on each level.
+ * As long as the scores are kept sorted, this is simply the first one at
+ * that level.
+ */
+
+#define NUMSPOTS  (MAXHISCORES + 1)
+#define NLEVELS   (MAXLEVEL + 1)
+
+static struct highscore scores[NUMSPOTS];
+
+/** Copy from hiscore table score with index src to dest
+ *
+ */
+static void copyhiscore(int dest, int src)
+{
+	str_cpy(scores[dest].hs_name, STR_BOUNDS(MAXLOGNAME) + 1,
+	    scores[src].hs_name);
+	scores[dest].hs_score = scores[src].hs_score;
+	scores[dest].hs_level = scores[src].hs_level;
+}
+
+void showscores(int firstgame)
+{
+	int i;
+	
+	clear_screen();
+	moveto(10, 0);
+	printf("\tRank \tLevel \tName\t                     points\n");
+	printf("\t========================================================\n");
+	
+	for (i = 0; i < NUMSPOTS - 1; i++)
+		printf("\t%6d %6d %-16s %20d\n",
+		    i + 1, scores[i].hs_level, scores[i].hs_name, scores[i].hs_score);
+	
+	if (!firstgame) {
+		printf("\t========================================================\n");
+		printf("\t  Last %6d %-16s %20d\n",
+		    scores[NUMSPOTS - 1].hs_level, scores[NUMSPOTS - 1].hs_name, scores[NUMSPOTS - 1].hs_score);
+	}
+	
+	printf("\n\n\n\n\tPress any key to return to main menu.");
+	getchar();
+}
+
+void insertscore(int score, int level)
+{
+	int i;
+	int j;
+	size_t off;
+	kbd_event_t ev;
+	
+	clear_screen();
+	moveto(10, 10);
+	puts("Insert your name: ");
+	str_cpy(scores[NUMSPOTS - 1].hs_name, STR_BOUNDS(MAXLOGNAME) + 1,
+	    "Player");
+	i = 6;
+	off = 6;
+	
+	moveto(10 , 28);
+	printf("%s%.*s", scores[NUMSPOTS - 1].hs_name, MAXLOGNAME-i,
+	    "........................................");
+	
+	while (1) {
+		console_flush(console);
+		if (!console_get_kbd_event(console, &ev))
+			exit(1);
+		
+		if (ev.type == KEY_RELEASE)
+			continue;
+		
+		if (ev.key == KC_ENTER || ev.key == KC_NENTER)
+			break;
+		
+		if (ev.key == KC_BACKSPACE) {
+			if (i > 0) {
+				wchar_t uc;
+				
+				--i;
+				while (off > 0) {
+					--off;
+					size_t otmp = off;
+					uc = str_decode(scores[NUMSPOTS - 1].hs_name,
+					    &otmp, STR_BOUNDS(MAXLOGNAME) + 1);
+					if (uc != U_SPECIAL)
+						break;
+				}
+				
+				scores[NUMSPOTS - 1].hs_name[off] = '\0';
+			}
+		} else if (ev.c != '\0') {
+			if (i < (MAXLOGNAME - 1)) {
+				if (chr_encode(ev.c, scores[NUMSPOTS - 1].hs_name,
+				    &off, STR_BOUNDS(MAXLOGNAME) + 1) == EOK) {
+					++i;
+				}
+				scores[NUMSPOTS - 1].hs_name[off] = '\0';
+			}
+		}
+		
+		moveto(10, 28);
+		printf("%s%.*s", scores[NUMSPOTS - 1].hs_name, MAXLOGNAME - i,
+		    "........................................");
+	}
+	
+	scores[NUMSPOTS - 1].hs_score = score;
+	scores[NUMSPOTS - 1].hs_level = level;
+	
+	i = NUMSPOTS - 1;
+	while ((i > 0) && (scores[i - 1].hs_score < score))
+		i--;
+	
+	for (j = NUMSPOTS - 2; j > i; j--)
+		copyhiscore(j, j-1);
+	
+	copyhiscore(i, NUMSPOTS - 1);
+}
+
+void initscores(void)
+{
+	int i;
+	for (i = 0; i < NUMSPOTS; i++) {
+		str_cpy(scores[i].hs_name, STR_BOUNDS(MAXLOGNAME) + 1, "HelenOS Team");
+		scores[i].hs_score = (NUMSPOTS - i) * 200;
+		scores[i].hs_level = (i + 1 > MAXLEVEL ? MAXLEVEL : i + 1);
+	}
+}
+
+int loadscores(void)
+{
+	FILE *f;
+	size_t cnt;
+	int rc;
+
+	f = fopen("/data/tetris.sco", "rb");
+	if (f == NULL)
+		return ENOENT;
+
+	cnt = fread(scores, sizeof(struct highscore), NUMSPOTS, f);
+	rc = fclose(f);
+
+	if (cnt != NUMSPOTS || rc != 0)
+		return EIO;
+
+	return EOK;
+}
+
+void savescores(void)
+{
+	FILE *f;
+	size_t cnt;
+	int rc;
+
+	f = fopen("/data/tetris.sco", "wb");
+	cnt = fwrite(scores, sizeof(struct highscore), NUMSPOTS, f);
+	rc = fclose(f);
+
+	if (cnt != NUMSPOTS || rc != 0)
+		printf("Error saving score table\n");
+}
+
+/** @}
+ */
Index: uspace/dist/src/c/demos/tetris/scores.h
===================================================================
--- uspace/dist/src/c/demos/tetris/scores.h	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/tetris/scores.h	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2011 Martin Decky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** Attributations
+ *
+ * scores.h 8.1 (Berkeley) 5/31/93
+ * NetBSD: scores.h,v 1.2 1995/04/22 07:42:40 cgd
+ * OpenBSD: scores.h,v 1.5 2003/06/03 03:01:41 millert
+ *
+ * Based upon BSD Tetris
+ *
+ * Copyright (c) 1992, 1993
+ *      The Regents of the University of California.
+ *      Distributed under BSD license.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek and Darren F. Provine.
+ *
+ */
+
+/** @addtogroup tetris
+ * @{
+ */
+/** @file
+ */
+
+/*
+ * Tetris scores.
+ */
+
+#include <sys/time.h>
+#include <str.h>
+
+#define MAXLOGNAME   16
+#define MAXHISCORES  10
+#define MAXSCORES    9  /* maximum high score entries per person */
+#define EXPIRATION   (5L * 365 * 24 * 60 * 60)
+
+struct highscore {
+	char hs_name[STR_BOUNDS(MAXLOGNAME) + 1];  /* login name */
+	int hs_score;                              /* raw score */
+	int hs_level;                              /* play level */
+	time_t hs_time;                            /* time at game end */
+};
+
+extern void showscores(int);
+extern void initscores(void);
+extern void insertscore(int score, int level);
+extern int loadscores(void);
+extern void savescores(void);
+
+/** @}
+ */
Index: uspace/dist/src/c/demos/tetris/screen.c
===================================================================
--- uspace/dist/src/c/demos/tetris/screen.c	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/tetris/screen.c	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,413 @@
+/*
+ * Copyright (c) 2011 Martin Decky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** Attributations
+ *
+ * screen.c 8.1 (Berkeley) 5/31/93
+ * NetBSD: screen.c,v 1.4 1995/04/29 01:11:36 mycroft
+ * OpenBSD: screen.c,v 1.13 2006/04/20 03:25:36 ray
+ *
+ * Based upon BSD Tetris
+ *
+ * Copyright (c) 1992, 1993
+ *      The Regents of the University of California.
+ *      Distributed under BSD license.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek and Darren F. Provine.
+ *
+ */
+
+/** @addtogroup tetris
+ * @{
+ */
+/** @file
+ */
+
+/*
+ * Tetris screen control.
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <str.h>
+#include <unistd.h>
+#include <vfs/vfs.h>
+#include <async.h>
+#include <bool.h>
+#include <io/console.h>
+#include <io/style.h>
+#include "screen.h"
+#include "tetris.h"
+
+#define STOP  (B_COLS - 3)
+
+static cell curscreen[B_SIZE];  /* non-zero => standout (or otherwise marked) */
+static int curscore;
+static int isset;               /* true => terminal is in game mode */
+
+static bool use_color;          /* true => use colors */
+
+static const struct shape *lastshape;
+
+static suseconds_t timeleft = 0;
+
+console_ctrl_t *console;
+
+
+/*
+ * putstr() is for unpadded strings (either as in termcap(5) or
+ * simply literal strings);
+ */
+static inline void putstr(const char *s)
+{
+	while (*s)
+		putchar(*(s++));
+}
+
+static void start_standout(uint32_t color)
+{
+	console_flush(console);
+	console_set_rgb_color(console, 0xffffff,
+	    use_color ? color : 0x000000);
+}
+
+static void resume_normal(void)
+{
+	console_flush(console);
+	console_set_style(console, STYLE_NORMAL);
+}
+
+void clear_screen(void)
+{
+	console_clear(console);
+	moveto(0, 0);
+}
+
+/*
+ * Clear the screen, forgetting the current contents in the process.
+ */
+void scr_clear(void)
+{
+	resume_normal();
+	console_clear(console);
+	curscore = -1;
+	memset(curscreen, 0, sizeof(curscreen));
+}
+
+/*
+ * Set up screen
+ */
+void scr_init(void)
+{
+	console_cursor_visibility(console, 0);
+	resume_normal();
+	scr_clear();
+}
+
+void moveto(sysarg_t r, sysarg_t c)
+{
+	console_flush(console);
+	console_set_pos(console, c, r);
+}
+
+winsize_t winsize;
+
+static int get_display_size(winsize_t *ws)
+{
+	return console_get_size(console, &ws->ws_col, &ws->ws_row);
+}
+
+static bool get_display_color_sup(void)
+{
+	sysarg_t ccap;
+	int rc = console_get_color_cap(console, &ccap);
+	
+	if (rc != 0)
+		return false;
+	
+	return (ccap >= CONSOLE_CCAP_RGB);
+}
+
+/*
+ * Set up screen mode.
+ */
+void scr_set(void)
+{
+	winsize_t ws;
+	
+	Rows = 0;
+	Cols = 0;
+	
+	if (get_display_size(&ws) == 0) {
+		Rows = ws.ws_row;
+		Cols = ws.ws_col;
+	}
+
+	use_color = get_display_color_sup();
+	
+	if ((Rows < MINROWS) || (Cols < MINCOLS)) {
+		char smallscr[55];
+		
+		snprintf(smallscr, sizeof(smallscr),
+		    "the screen is too small (must be at least %dx%d)",
+		    MINROWS, MINCOLS);
+		stop(smallscr);
+	}
+	isset = 1;
+	
+	scr_clear();
+}
+
+/*
+ * End screen mode.
+ */
+void scr_end(void)
+{
+	console_cursor_visibility(console, 1);
+}
+
+void stop(const char *why)
+{
+	if (isset)
+		scr_end();
+	
+	errx(1, "aborting: %s", why);
+}
+
+/*
+ * Update the screen.
+ */
+void scr_update(void)
+{
+	cell *bp;
+	cell *sp;
+	cell so;
+	cell cur_so = 0;
+	int i;
+	int j;
+	int ccol;
+	
+	/* Always leave cursor after last displayed point */
+	curscreen[D_LAST * B_COLS - 1] = -1;
+	
+	if (score != curscore) {
+		moveto(0, 0);
+		printf("Score: %d", score);
+		curscore = score;
+	}
+	
+	/* Draw preview of next pattern */
+	if ((showpreview) && (nextshape != lastshape)) {
+		int i;
+		static int r = 5, c = 2;
+		int tr, tc, t;
+		
+		lastshape = nextshape;
+		
+		/* Clean */
+		resume_normal();
+		moveto(r - 1, c - 1);
+		putstr("          ");
+		moveto(r, c - 1);
+		putstr("          ");
+		moveto(r + 1, c - 1);
+		putstr("          ");
+		moveto(r + 2, c - 1);
+		putstr("          ");
+		
+		moveto(r - 3, c - 2);
+		putstr("Next shape:");
+		
+		/* Draw */
+		start_standout(nextshape->color);
+		moveto(r, 2 * c);
+		putstr("  ");
+		for (i = 0; i < 3; i++) {
+			t = c + r * B_COLS;
+			t += nextshape->off[i];
+			
+			tr = t / B_COLS;
+			tc = t % B_COLS;
+			
+			moveto(tr, 2*tc);
+			putstr("  ");
+		}
+		resume_normal();
+	}
+	
+	bp = &board[D_FIRST * B_COLS];
+	sp = &curscreen[D_FIRST * B_COLS];
+	for (j = D_FIRST; j < D_LAST; j++) {
+		ccol = -1;
+		for (i = 0; i < B_COLS; bp++, sp++, i++) {
+			if (*sp == (so = *bp))
+				continue;
+			
+			*sp = so;
+			if (i != ccol) {
+				if (cur_so) {
+					resume_normal();
+					cur_so = 0;
+				}
+				moveto(RTOD(j), CTOD(i));
+			}
+			
+			if (so != cur_so) {
+				if (so)
+					start_standout(so);
+				else
+					resume_normal();
+				cur_so = so;
+			}
+			putstr("  ");
+			
+			ccol = i + 1;
+			/*
+			 * Look ahead a bit, to avoid extra motion if
+			 * we will be redrawing the cell after the next.
+			 * Motion probably takes four or more characters,
+			 * so we save even if we rewrite two cells
+			 * `unnecessarily'.  Skip it all, though, if
+			 * the next cell is a different color.
+			 */
+			
+			if ((i > STOP) || (sp[1] != bp[1]) || (so != bp[1]))
+				continue;
+			
+			if (sp[2] != bp[2])
+				sp[1] = -1;
+			else if ((i < STOP) && (so == bp[2]) && (sp[3] != bp[3])) {
+				sp[2] = -1;
+				sp[1] = -1;
+			}
+		}
+	}
+	
+	if (cur_so)
+		resume_normal();
+	
+	console_flush(console);
+}
+
+/*
+ * Write a message (set != 0), or clear the same message (set == 0).
+ * (We need its length in case we have to overwrite with blanks.)
+ */
+void scr_msg(char *s, bool set)
+{
+	int l = str_size(s);
+	
+	moveto(Rows - 2, ((Cols - l) >> 1) - 1);
+	
+	if (set)
+		putstr(s);
+	else
+		while (--l >= 0)
+			(void) putchar(' ');
+}
+
+/** Sleep for the current turn time
+ *
+ * Eat any input that might be available.
+ *
+ */
+void tsleep(void)
+{
+	suseconds_t timeout = fallrate;
+	
+	while (timeout > 0) {
+		kbd_event_t event;
+		
+		if (!console_get_kbd_event_timeout(console, &event, &timeout))
+			break;
+	}
+}
+
+/** Get char with timeout
+ *
+ */
+int tgetchar(void)
+{
+	/*
+	 * Reset timeleft to fallrate whenever it is not positive
+	 * and increase speed.
+	 */
+	
+	if (timeleft <= 0) {
+		faster();
+		timeleft = fallrate;
+	}
+	
+	/*
+	 * Wait to see if there is any input. If so, take it and
+	 * update timeleft so that the next call to tgetchar()
+	 * will not wait as long. If there is no input,
+	 * make timeleft zero and return -1.
+	 */
+	
+	wchar_t c = 0;
+	
+	while (c == 0) {
+		kbd_event_t event;
+		
+		if (!console_get_kbd_event_timeout(console, &event, &timeleft)) {
+			timeleft = 0;
+			return -1;
+		}
+		
+		if (event.type == KEY_PRESS)
+			c = event.c;
+	}
+	
+	return (int) c;
+}
+
+/** Get char without timeout
+ *
+ */
+int twait(void)
+{
+	wchar_t c = 0;
+	
+	while (c == 0) {
+		kbd_event_t event;
+		
+		if (!console_get_kbd_event(console, &event))
+			return -1;
+		
+		if (event.type == KEY_PRESS)
+			c = event.c;
+	}
+	
+	return (int) c;
+}
+
+/** @}
+ */
Index: uspace/dist/src/c/demos/tetris/screen.h
===================================================================
--- uspace/dist/src/c/demos/tetris/screen.h	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/tetris/screen.h	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2011 Martin Decky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** Attributations
+ *
+ * screen.h 8.1 (Berkeley) 5/31/93
+ * NetBSD: screen.h,v 1.2 1995/04/22 07:42:42 cgd
+ * OpenBSD: screen.h,v 1.5 2003/06/03 03:01:41 millert
+ *
+ * Based upon BSD Tetris
+ *
+ * Copyright (c) 1992, 1993
+ *      The Regents of the University of California.
+ *      Distributed under BSD license.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek and Darren F. Provine.
+ *
+ */
+
+/** @addtogroup tetris
+ * @{
+ */
+/** @file
+ */
+
+/*
+ * putpad() is for padded strings with count = 1.
+ */
+#define putpad(s)  tputs(s, 1, put)
+
+#include <sys/types.h>
+#include <io/console.h>
+#include <async.h>
+#include <bool.h>
+
+typedef struct {
+	sysarg_t ws_row;
+	sysarg_t ws_col;
+} winsize_t;
+
+extern console_ctrl_t *console;
+extern winsize_t winsize;
+
+extern void moveto(sysarg_t r, sysarg_t c);
+extern void clear_screen(void);
+
+extern int put(int);
+extern void scr_clear(void);
+extern void scr_end(void);
+extern void scr_init(void);
+extern void scr_msg(char *, bool);
+extern void scr_set(void);
+extern void scr_update(void);
+
+extern void tsleep(void);
+extern int tgetchar(void);
+extern int twait(void);
+
+/** @}
+ */
Index: uspace/dist/src/c/demos/tetris/shapes.c
===================================================================
--- uspace/dist/src/c/demos/tetris/shapes.c	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/tetris/shapes.c	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2011 Martin Decky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** Attributations
+ *
+ * shapes.c 8.1 (Berkeley) 5/31/93
+ * NetBSD: shapes.c,v 1.2 1995/04/22 07:42:44 cgd
+ * OpenBSD: shapes.c,v 1.8 2004/07/10 07:26:24 deraadt
+ *
+ * Based upon BSD Tetris
+ *
+ * Copyright (c) 1992, 1993
+ *      The Regents of the University of California.
+ *      Distributed under BSD license.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek and Darren F. Provine.
+ *
+ */
+
+/** @addtogroup tetris
+ * @{
+ */
+/** @file
+ */
+
+/*
+ * Tetris shapes and related routines.
+ *
+ * Note that the first 7 are `well known'.
+ */
+
+#include <unistd.h>
+#include "tetris.h"
+
+#define TL  (-B_COLS - 1)  /* top left */
+#define TC  (-B_COLS)      /* top center */
+#define TR  (-B_COLS + 1)  /* top right */
+#define ML  -1             /* middle left */
+#define MR  1              /* middle right */
+#define BL  (B_COLS - 1)   /* bottom left */
+#define BC  B_COLS         /* bottom center */
+#define BR  (B_COLS + 1)   /* bottom right */
+
+const struct shape shapes[] = {
+	/*  0 */  {  7,  7, { TL, TC, MR }, 0xff042d},
+	/*  1 */  {  8,  8, { TC, TR, ML }, 0xff9304},
+	/*  2 */  {  9, 11, { ML, MR, BC }, 0xbeff04},
+	/*  3 */  {  3,  3, { TL, TC, ML }, 0x63ff04},
+	/*  4 */  { 12, 14, { ML, BL, MR }, 0xce04ff},
+	/*  5 */  { 15, 17, { ML, BR, MR }, 0xff04cf},
+	/*  6 */  { 18, 18, { ML, MR, 2  }, 0x7604ff},  /* sticks out */
+	/*  7 */  {  0,  0, { TC, ML, BL }, 0xff042d},
+	/*  8 */  {  1,  1, { TC, MR, BR }, 0xff9304},
+	/*  9 */  { 10,  2, { TC, MR, BC }, 0xbeff04},
+	/* 10 */  { 11,  9, { TC, ML, MR }, 0xbeff04},
+	/* 11 */  {  2, 10, { TC, ML, BC }, 0xbeff04},
+	/* 12 */  { 13,  4, { TC, BC, BR }, 0xce04ff},
+	/* 13 */  { 14, 12, { TR, ML, MR }, 0xce04ff},
+	/* 14 */  {  4, 13, { TL, TC, BC }, 0xce04ff},
+	/* 15 */  { 16,  5, { TR, TC, BC }, 0xff04cf},
+	/* 16 */  { 17, 15, { TL, MR, ML }, 0xff04cf},
+	/* 17 */  {  5, 16, { TC, BC, BL }, 0xff04cf},
+	/* 18 */  {  6,  6, { TC, BC, 2 * B_COLS }, 0x7604ff}  /* sticks out */
+};
+
+/*
+ * Return true iff the given shape fits in the given position,
+ * taking the current board into account.
+ */
+int fits_in(const struct shape *shape, int pos)
+{
+	const int *o = shape->off;
+	
+	if ((board[pos]) || (board[pos + *o++]) || (board[pos + *o++]) ||
+	    (board[pos + *o]))
+		return 0;
+	
+	return 1;
+}
+
+/*
+ * Write the given shape into the current board, turning it on
+ * if `onoff' is 1, and off if `onoff' is 0.
+ */
+void place(const struct shape *shape, int pos, int onoff)
+{
+	const int *o = shape->off;
+	
+	board[pos] = onoff ? shape->color : 0x000000;
+	board[pos + *o++] = onoff ? shape->color : 0x000000;
+	board[pos + *o++] = onoff ? shape->color : 0x000000;
+	board[pos + *o] = onoff ? shape->color : 0x000000;
+}
+
+/** @}
+ */
Index: uspace/dist/src/c/demos/tetris/tetris.c
===================================================================
--- uspace/dist/src/c/demos/tetris/tetris.c	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/tetris/tetris.c	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,450 @@
+/*
+ * Copyright (c) 2011 Martin Decky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** Attributations
+ *
+ * tetris.c 8.1 (Berkeley) 5/31/93
+ * NetBSD: tetris.c,v 1.2 1995/04/22 07:42:47 cgd
+ * OpenBSD: tetris.c,v 1.21 2006/04/20 03:24:12 ray
+ *
+ * Based upon BSD Tetris
+ *
+ * Copyright (c) 1992, 1993
+ *      The Regents of the University of California.
+ *      Distributed under BSD license.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek and Darren F. Provine.
+ *
+ */
+
+/** @addtogroup tetris Tetris
+ * @brief Tetris ported from OpenBSD
+ * @{
+ */
+/** @file
+ */
+
+static const char copyright[] =
+	"@(#) Copyright (c) 1992, 1993\n"
+	"\tThe Regents of the University of California.  All rights reserved.\n";
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <str.h>
+#include <unistd.h>
+#include <getopt.h>
+#include "scores.h"
+#include "screen.h"
+#include "tetris.h"
+
+cell board[B_SIZE];
+
+int Rows;
+int Cols;
+
+const struct shape *curshape;
+const struct shape *nextshape;
+
+long fallrate;
+int score;
+char key_msg[100];
+int showpreview;
+int classic;
+
+static void elide(void);
+static void setup_board(void);
+static const struct shape *randshape(void);
+
+static void usage(void);
+
+static int firstgame = 1;
+
+/*
+ * Set up the initial board. The bottom display row is completely set,
+ * along with another (hidden) row underneath that. Also, the left and
+ * right edges are set.
+ */
+static void setup_board(void)
+{
+	int i;
+	cell *p = board;
+	
+	for (i = B_SIZE; i; i--)
+		*p++ = (i <= (2 * B_COLS) || (i % B_COLS) < 2) ? 0x0000ff : 0x000000;
+}
+
+/*
+ * Elide any full active rows.
+ */
+static void elide(void)
+{
+	int rows = 0;
+	int i;
+	int j;
+	int base;
+	cell *p;
+	
+	for (i = A_FIRST; i < A_LAST; i++) {
+		base = i * B_COLS + 1;
+		p = &board[base];
+		for (j = B_COLS - 2; *p++ != 0;) {
+			if (--j <= 0) {
+				/* This row is to be elided */
+				rows++;
+				memset(&board[base], 0, sizeof(cell) * (B_COLS - 2));
+				
+				scr_update();
+				tsleep();
+				
+				while (--base != 0)
+					board[base + B_COLS] = board[base];
+				
+				scr_update();
+				tsleep();
+				
+				break;
+			}
+		}
+	}
+	
+	switch (rows) {
+	case 1:
+		score += 10;
+		break;
+	case 2:
+		score += 30;
+		break;
+	case 3:
+		score += 70;
+		break;
+	case 4:
+		score += 150;
+		break;
+	default:
+		break;
+	}
+}
+
+const struct shape *randshape(void)
+{
+	const struct shape *tmp = &shapes[random() % 7];
+	int i;
+	int j = random() % 4;
+	
+	for (i = 0; i < j; i++)
+		tmp = &shapes[classic ? tmp->rotc : tmp->rot];
+	
+	return (tmp);
+}
+
+static void srandomdev(void)
+{
+	struct timeval tv;
+	
+	gettimeofday(&tv, NULL);
+	srandom(tv.tv_sec + tv.tv_usec / 100000);
+}
+
+static void tetris_menu_draw(int level) 
+{
+	clear_screen();
+	moveto(5, 10);
+	puts("Tetris\n\n");
+	
+	moveto(8, 10);
+	printf("Level = %d (press keys 1 - 9 to change)", level);
+	moveto(9, 10);
+	printf("Preview is %s (press 'p' to change)", (showpreview ? "on ": "off"));
+	moveto(12, 10);
+	printf("Press 'h' to show hiscore table.");
+	moveto(13, 10);
+	printf("Press 's' to start game.");
+	moveto(14, 10);
+	printf("Press 'q' to quit game.");
+	moveto(20, 10);
+	printf("In game controls:");
+	moveto(21, 0);
+	puts(key_msg);
+}
+
+static int tetris_menu(int *level)
+{
+	tetris_menu_draw(*level);
+	while (1) {
+		int i = getchar();
+		
+		switch(i) {
+			case 'p':
+				showpreview = !showpreview;
+				moveto(9, 21);
+				if (showpreview)
+					printf("on ");
+				else
+					printf("off");
+				break;
+			case 'h':
+				loadscores();
+				showscores(firstgame);
+				tetris_menu_draw(*level);
+				break;
+			case 's':
+				firstgame = 0;
+				return 1;
+			case 'q':
+				return 0;
+			case '1':
+			case '2':
+			case '3':
+			case '4':
+			case '5':
+			case '6':
+			case '7':
+			case '8':
+			case '9':
+				*level = i - '0';
+				moveto(8, 18);
+				printf("%d", *level);
+				break;
+		}
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	int pos;
+	int c;
+	const char *keys;
+	int level = 2;
+	char key_write[6][10];
+	int i;
+	int j;
+	int ch;
+	
+	console = console_init(stdin, stdout);
+	
+	keys = "jkl pq";
+	
+	classic = 0;
+	showpreview = 1;
+	
+	while ((ch = getopt(argc, argv, "ck:ps")) != -1)
+		switch(ch) {
+		case 'c':
+			/*
+			 * this means:
+			 *  - rotate the other way
+			 *  - no reverse video
+			 */
+			classic = 1;
+			break;
+		case 'k':
+			if (str_size(keys = optarg) != 6)
+				usage();
+			break;
+		case 'p':
+			showpreview = 1;
+			break;
+		case 's':
+			showscores(0);
+			exit(0);
+		default:
+			usage();
+		}
+	
+	argc -= optind;
+	argv += optind;
+	
+	if (argc)
+		usage();
+	
+	for (i = 0; i <= 5; i++) {
+		for (j = i + 1; j <= 5; j++) {
+			if (keys[i] == keys[j])
+				errx(1, "%s", "duplicate command keys specified.");
+		}
+		
+		if (keys[i] == ' ')
+			str_cpy(key_write[i], sizeof(key_write[i]), "<space>");
+		else {
+			key_write[i][0] = keys[i];
+			key_write[i][1] = '\0';
+		}
+	}
+	
+	snprintf(key_msg, sizeof(key_msg),
+	    "%s - left   %s - rotate   %s - right   %s - drop   %s - pause   %s - quit",
+	    key_write[0], key_write[1], key_write[2], key_write[3],
+	    key_write[4], key_write[5]);
+	
+	scr_init();
+	if (loadscores() != EOK)
+		initscores();
+
+	while (tetris_menu(&level)) {
+		fallrate = 1000000 / level;
+		
+		scr_clear();
+		setup_board();
+		
+		srandomdev();
+		scr_set();
+		
+		pos = A_FIRST * B_COLS + (B_COLS / 2) - 1;
+		nextshape = randshape();
+		curshape = randshape();
+		
+		scr_msg(key_msg, 1);
+		
+		while (1) {
+			place(curshape, pos, 1);
+			scr_update();
+			place(curshape, pos, 0);
+			c = tgetchar();
+			if (c < 0) {
+				/*
+				 * Timeout.  Move down if possible.
+				 */
+				if (fits_in(curshape, pos + B_COLS)) {
+					pos += B_COLS;
+					continue;
+				}
+				
+				/*
+				 * Put up the current shape `permanently',
+				 * bump score, and elide any full rows.
+				 */
+				place(curshape, pos, 1);
+				score++;
+				elide();
+				
+				/*
+				 * Choose a new shape.  If it does not fit,
+				 * the game is over.
+				 */
+				curshape = nextshape;
+				nextshape = randshape();
+				pos = A_FIRST * B_COLS + (B_COLS / 2) - 1;
+				
+				if (!fits_in(curshape, pos))
+					break;
+				
+				continue;
+			}
+			
+			/*
+			 * Handle command keys.
+			 */
+			if (c == keys[5]) {
+				/* quit */
+				break;
+			}
+			
+			if (c == keys[4]) {
+				static char msg[] =
+				    "paused - press RETURN to continue";
+				
+				place(curshape, pos, 1);
+				do {
+					scr_update();
+					scr_msg(key_msg, 0);
+					scr_msg(msg, 1);
+					console_flush(console);
+				} while (!twait());
+				
+				scr_msg(msg, 0);
+				scr_msg(key_msg, 1);
+				place(curshape, pos, 0);
+				continue;
+			}
+			
+			if (c == keys[0]) {
+				/* move left */
+				if (fits_in(curshape, pos - 1))
+					pos--;
+				continue;
+			}
+			
+			if (c == keys[1]) {
+				/* turn */
+				const struct shape *new =
+				    &shapes[classic ? curshape->rotc : curshape->rot];
+				
+				if (fits_in(new, pos))
+					curshape = new;
+				continue;
+			}
+			
+			if (c == keys[2]) {
+				/* move right */
+				if (fits_in(curshape, pos + 1))
+					pos++;
+				continue;
+			}
+			
+			if (c == keys[3]) {
+				/* move to bottom */
+				while (fits_in(curshape, pos + B_COLS)) {
+					pos += B_COLS;
+					score++;
+				}
+				continue;
+			}
+			
+			if (c == '\f') {
+				scr_clear();
+				scr_msg(key_msg, 1);
+			}
+		}
+		
+		scr_clear();
+		loadscores();
+		insertscore(score, level);
+		savescores();
+		score = 0;
+	}
+	
+	scr_clear();
+	printf("\nGame over.\n");
+	scr_end();
+	
+	return 0;
+}
+
+void usage(void)
+{
+	fprintf(stderr, "usage: tetris [-ps] [-k keys]\n");
+	exit(1);
+}
+
+/** @}
+ */
Index: uspace/dist/src/c/demos/tetris/tetris.h
===================================================================
--- uspace/dist/src/c/demos/tetris/tetris.h	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/tetris/tetris.h	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2011 Martin Decky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** Attributations
+ *
+ * tetris.h 8.1 (Berkeley) 5/31/93
+ * NetBSD: tetris.h,v 1.2 1995/04/22 07:42:48 cgd
+ * OpenBSD: tetris.h,v 1.9 2003/06/03 03:01:41 millert
+ *
+ * Based upon BSD Tetris
+ *
+ * Copyright (c) 1992, 1993
+ *      The Regents of the University of California.
+ *      Distributed under BSD license.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek and Darren F. Provine.
+ *
+ */
+
+/** @addtogroup tetris
+ * @{
+ */
+/** @file
+ */
+
+/*
+ * Definitions for Tetris.
+ */
+
+/*
+ * The display (`board') is composed of 23 rows of 12 columns of characters
+ * (numbered 0..22 and 0..11), stored in a single array for convenience.
+ * Columns 1 to 10 of rows 1 to 20 are the actual playing area, where
+ * shapes appear.  Columns 0 and 11 are always occupied, as are all
+ * columns of rows 21 and 22.  Rows 0 and 22 exist as boundary areas
+ * so that regions `outside' the visible area can be examined without
+ * worrying about addressing problems.
+ */
+
+/* The board */
+#define B_COLS  12
+#define B_ROWS  23
+#define B_SIZE  (B_ROWS * B_COLS)
+
+typedef uint32_t cell;
+
+extern cell board[B_SIZE];  /* 1 => occupied, 0 => empty */
+
+/* The displayed area (rows) */
+#define D_FIRST  1
+#define D_LAST   22
+
+/* The active area (rows) */
+#define A_FIRST  1
+#define A_LAST   21
+
+/*
+ * Minimum display size.
+ */
+#define MINROWS  23
+#define MINCOLS  40
+
+/* Current screen size */
+extern int Rows;
+extern int Cols;
+
+/*
+ * Translations from board coordinates to display coordinates.
+ * As with board coordinates, display coordiates are zero origin.
+ */
+#define RTOD(x)  ((x) - 1)
+#define CTOD(x)  ((x) * 2 + (((Cols - 2 * B_COLS) >> 1) - 1))
+
+/*
+ * A `shape' is the fundamental thing that makes up the game.  There
+ * are 7 basic shapes, each consisting of four `blots':
+ *
+ *      X.X       X.X           X.X
+ *        X.X   X.X     X.X.X   X.X     X.X.X   X.X.X   X.X.X.X
+ *                        X             X           X
+ *
+ *          0     1       2       3       4       5       6
+ *
+ * Except for 3 and 6, the center of each shape is one of the blots.
+ * This blot is designated (0, 0).  The other three blots can then be
+ * described as offsets from the center.  Shape 3 is the same under
+ * rotation, so its center is effectively irrelevant; it has been chosen
+ * so that it `sticks out' upward and leftward.  Except for shape 6,
+ * all the blots are contained in a box going from (-1, -1) to (+1, +1);
+ * shape 6's center `wobbles' as it rotates, so that while it `sticks out'
+ * rightward, its rotation---a vertical line---`sticks out' downward.
+ * The containment box has to include the offset (2, 0), making the overall
+ * containment box range from offset (-1, -1) to (+2, +1).  (This is why
+ * there is only one row above, but two rows below, the display area.)
+ *
+ * The game works by choosing one of these shapes at random and putting
+ * its center at the middle of the first display row (row 1, column 5).
+ * The shape is moved steadily downward until it collides with something:
+ * either  another shape, or the bottom of the board.  When the shape can
+ * no longer be moved downwards, it is merged into the current board.
+ * At this time, any completely filled rows are elided, and blots above
+ * these rows move down to make more room.  A new random shape is again
+ * introduced at the top of the board, and the whole process repeats.
+ * The game ends when the new shape will not fit at (1, 5).
+ *
+ * While the shapes are falling, the user can rotate them counterclockwise
+ * 90 degrees (in addition to moving them left or right), provided that the
+ * rotation puts the blots in empty spaces.  The table of shapes is set up
+ * so that each shape contains the index of the new shape obtained by
+ * rotating the current shape.  Due to symmetry, each shape has exactly
+ * 1, 2, or 4 rotations total; the first 7 entries in the table represent
+ * the primary shapes, and the remaining 12 represent their various
+ * rotated forms.
+ */
+struct shape {
+	int rot;     /* index of rotated version of this shape */
+	int rotc;    /* -- " -- in classic version  */
+	int off[3];  /* offsets to other blots if center is at (0,0) */
+	uint32_t color;
+};
+
+extern const struct shape shapes[];
+
+extern const struct shape *curshape;
+extern const struct shape *nextshape;
+
+/*
+ * Shapes fall at a rate faster than once per second.
+ *
+ * The initial rate is determined by dividing 1 million microseconds
+ * by the game `level'.  (This is at most 1 million, or one second.)
+ * Each time the fall-rate is used, it is decreased a little bit,
+ * depending on its current value, via the `faster' macro below.
+ * The value eventually reaches a limit, and things stop going faster,
+ * but by then the game is utterly impossible.
+ */
+extern long fallrate;  /* less than 1 million; smaller => faster */
+
+#define faster()  (fallrate -= fallrate / 3000)
+
+/*
+ * Game level must be between 1 and 9.  This controls the initial fall rate
+ * and affects scoring.
+ */
+#define MINLEVEL  1
+#define MAXLEVEL  9
+
+/*
+ * Scoring is as follows:
+ *
+ * When the shape comes to rest, and is integrated into the board,
+ * we score one point.  If the shape is high up (at a low-numbered row),
+ * and the user hits the space bar, the shape plummets all the way down,
+ * and we score a point for each row it falls (plus one more as soon as
+ * we find that it is at rest and integrate it---until then, it can
+ * still be moved or rotated).
+ *
+ * If previewing has been turned on, the score is multiplied by PRE_PENALTY.
+ */
+#define PRE_PENALTY  0.75
+
+extern int score;  /* The obvious thing */
+
+extern char key_msg[100];
+extern int showpreview;
+extern int classic;
+
+extern int fits_in(const struct shape *, int);
+extern void place(const struct shape *, int, int);
+extern void stop(const char *);
+
+/** @}
+ */
Index: uspace/dist/src/c/demos/top/build
===================================================================
--- uspace/dist/src/c/demos/top/build	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/top/build	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,10 @@
+cc -D__PCC__ -I/inc/c -E -o screen.i screen.c
+cc -D__PCC__ -I/inc/c -E -o top.i top.c
+
+cc -S -o screen.s screen.i
+cc -S -o top.s top.i
+
+as -o screen.o screen.s
+as -o top.o top.s
+
+ld -T /inc/_link.ld -o top_ screen.o top.o /lib/libc.a /lib/libsoftint.a
Index: uspace/dist/src/c/demos/top/clean
===================================================================
--- uspace/dist/src/c/demos/top/clean	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/top/clean	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,11 @@
+rm top_
+
+rm top.o
+rm screen.o
+
+rm top.s
+rm screen.s
+
+rm top.i
+rm screen.i
+
Index: uspace/dist/src/c/demos/top/screen.c
===================================================================
--- uspace/dist/src/c/demos/top/screen.c	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/top/screen.c	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,570 @@
+/*
+ * Copyright (c) 2010 Stanislav Kozina
+ * Copyright (c) 2010 Martin Decky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup top
+ * @brief Top utility.
+ * @{
+ */
+/**
+ * @file
+ */
+
+#include <stdio.h>
+#include <io/console.h>
+#include <io/style.h>
+#include <vfs/vfs.h>
+#include <stdarg.h>
+#include <stats.h>
+#include <inttypes.h>
+#include "screen.h"
+#include "top.h"
+
+#define USEC_COUNT  1000000
+
+static sysarg_t warn_col = 0;
+static sysarg_t warn_row = 0;
+static suseconds_t timeleft = 0;
+
+console_ctrl_t *console;
+
+static void screen_style_normal(void)
+{
+	console_flush(console);
+	console_set_style(console, STYLE_NORMAL);
+}
+
+static void screen_style_inverted(void)
+{
+	console_flush(console);
+	console_set_style(console, STYLE_INVERTED);
+}
+
+static void screen_moveto(sysarg_t col, sysarg_t row)
+{
+	console_flush(console);
+	console_set_pos(console, col, row);
+}
+
+static void screen_get_pos(sysarg_t *col, sysarg_t *row)
+{
+	console_flush(console);
+	console_get_pos(console, col, row);
+}
+
+static void screen_get_size(sysarg_t *col, sysarg_t *row)
+{
+	console_flush(console);
+	console_get_size(console, col, row);
+}
+
+static void screen_restart(bool clear)
+{
+	screen_style_normal();
+	
+	if (clear) {
+		console_flush(console);
+		console_clear(console);
+	}
+	
+	screen_moveto(0, 0);
+}
+
+static void screen_newline(void)
+{
+	sysarg_t cols;
+	sysarg_t rows;
+	screen_get_size(&cols, &rows);
+	
+	sysarg_t c;
+	sysarg_t r;
+	screen_get_pos(&c, &r);
+	
+	sysarg_t i;
+	for (i = c + 1; i < cols; i++)
+		puts(" ");
+	
+	if (r + 1 < rows)
+		puts("\n");
+}
+
+void screen_init(void)
+{
+	console = console_init(stdin, stdout);
+	
+	console_flush(console);
+	console_cursor_visibility(console, false);
+	
+	screen_restart(true);
+}
+
+void screen_done(void)
+{
+	screen_restart(true);
+	
+	console_flush(console);
+	console_cursor_visibility(console, true);
+}
+
+static void print_percent(fixed_float ffloat, unsigned int precision)
+{
+	printf("%3" PRIu64 ".", ffloat.upper / ffloat.lower);
+	
+	unsigned int i;
+	uint64_t rest = (ffloat.upper % ffloat.lower) * 10;
+	for (i = 0; i < precision; i++) {
+		printf("%" PRIu64, rest / ffloat.lower);
+		rest = (rest % ffloat.lower) * 10;
+	}
+	
+	printf("%%");
+}
+
+static void print_string(const char *str)
+{
+	sysarg_t cols;
+	sysarg_t rows;
+	screen_get_size(&cols, &rows);
+	
+	sysarg_t c;
+	sysarg_t r;
+	screen_get_pos(&c, &r);
+	
+	if (c < cols) {
+		int pos = cols - c - 1;
+		printf("%.*s", pos, str);
+	}
+}
+
+static inline void print_global_head(data_t *data)
+{
+	printf("top - %02lu:%02lu:%02lu up "
+	    "%" PRIun " days, %02" PRIun ":%02" PRIun ":%02" PRIun ", "
+	    "load average:",
+	    data->hours, data->minutes, data->seconds,
+	    data->udays, data->uhours, data->uminutes, data->useconds);
+	
+	size_t i;
+	for (i = 0; i < data->load_count; i++) {
+		puts(" ");
+		stats_print_load_fragment(data->load[i], 2);
+	}
+	
+	screen_newline();
+}
+
+static inline void print_task_summary(data_t *data)
+{
+	printf("tasks: %zu total", data->tasks_count);
+	screen_newline();
+}
+
+static inline void print_thread_summary(data_t *data)
+{
+	size_t total = 0;
+	size_t running = 0;
+	size_t ready = 0;
+	size_t sleeping = 0;
+	size_t lingering = 0;
+	size_t other = 0;
+	size_t invalid = 0;
+	
+	size_t i;
+	for (i = 0; i < data->threads_count; i++) {
+		total++;
+		
+		switch (data->threads[i].state) {
+		case Running:
+			running++;
+			break;
+		case Ready:
+			ready++;
+			break;
+		case Sleeping:
+			sleeping++;
+			break;
+		case Lingering:
+			lingering++;
+			break;
+		case Entering:
+		case Exiting:
+			other++;
+			break;
+		default:
+			invalid++;
+		}
+	}
+	
+	printf("threads: %zu total, %zu running, %zu ready, "
+	    "%zu sleeping, %zu lingering, %zu other, %zu invalid",
+	    total, running, ready, sleeping, lingering, other, invalid);
+	screen_newline();
+}
+
+static inline void print_cpu_info(data_t *data)
+{
+	size_t i;
+	for (i = 0; i < data->cpus_count; i++) {
+		if (data->cpus[i].active) {
+			uint64_t busy;
+			uint64_t idle;
+			char busy_suffix;
+			char idle_suffix;
+			
+			order_suffix(data->cpus[i].busy_cycles, &busy, &busy_suffix);
+			order_suffix(data->cpus[i].idle_cycles, &idle, &idle_suffix);
+			
+			printf("cpu%u (%4" PRIu16 " MHz): busy cycles: "
+			    "%" PRIu64 "%c, idle cycles: %" PRIu64 "%c",
+			    data->cpus[i].id, data->cpus[i].frequency_mhz,
+			    busy, busy_suffix, idle, idle_suffix);
+			puts(", idle: ");
+			print_percent(data->cpus_perc[i].idle, 2);
+			puts(", busy: ");
+			print_percent(data->cpus_perc[i].busy, 2);
+		} else
+			printf("cpu%u inactive", data->cpus[i].id);
+		
+		screen_newline();
+	}
+}
+
+static inline void print_physmem_info(data_t *data)
+{
+	uint64_t total;
+	uint64_t unavail;
+	uint64_t used;
+	uint64_t free;
+	const char *total_suffix;
+	const char *unavail_suffix;
+	const char *used_suffix;
+	const char *free_suffix;
+	
+	bin_order_suffix(data->physmem->total, &total, &total_suffix, false);
+	bin_order_suffix(data->physmem->unavail, &unavail, &unavail_suffix, false);
+	bin_order_suffix(data->physmem->used, &used, &used_suffix, false);
+	bin_order_suffix(data->physmem->free, &free, &free_suffix, false);
+	
+	printf("memory: %" PRIu64 "%s total, %" PRIu64 "%s unavail, %"
+	    PRIu64 "%s used, %" PRIu64 "%s free", total, total_suffix,
+	    unavail, unavail_suffix, used, used_suffix, free, free_suffix);
+	screen_newline();
+}
+
+static inline void print_tasks_head(void)
+{
+	screen_style_inverted();
+	printf("[taskid] [thrds] [resident] [%%resi] [virtual] [%%virt]"
+	    " [%%user] [%%kern] [name");
+	screen_newline();
+	screen_style_normal();
+}
+
+static inline void print_tasks(data_t *data)
+{
+	sysarg_t cols;
+	sysarg_t rows;
+	screen_get_size(&cols, &rows);
+	
+	sysarg_t col;
+	sysarg_t row;
+	screen_get_pos(&col, &row);
+	
+	size_t i;
+	for (i = 0; (i < data->tasks_count) && (row < rows); i++, row++) {
+		stats_task_t *task = data->tasks + data->tasks_map[i];
+		perc_task_t *perc = data->tasks_perc + data->tasks_map[i];
+		
+		uint64_t resmem;
+		const char *resmem_suffix;
+		bin_order_suffix(task->resmem, &resmem, &resmem_suffix, true);
+		
+		uint64_t virtmem;
+		const char *virtmem_suffix;
+		bin_order_suffix(task->virtmem, &virtmem, &virtmem_suffix, true);
+		
+		printf("%-8" PRIu64 " %7zu %7" PRIu64 "%s ",
+		    task->task_id, task->threads, resmem, resmem_suffix);
+		print_percent(perc->resmem, 2);
+		printf(" %6" PRIu64 "%s ", virtmem, virtmem_suffix);
+		print_percent(perc->virtmem, 2);
+		puts(" ");
+		print_percent(perc->ucycles, 2);
+		puts(" ");
+		print_percent(perc->kcycles, 2);
+		puts(" ");
+		print_string(task->name);
+		
+		screen_newline();
+	}
+	
+	while (row < rows) {
+		screen_newline();
+		row++;
+	}
+}
+
+static inline void print_ipc_head(void)
+{
+	screen_style_inverted();
+	printf("[taskid] [cls snt] [cls rcv] [ans snt]"
+	    " [ans rcv] [irq rcv] [forward] [name");
+	screen_newline();
+	screen_style_normal();
+}
+
+static inline void print_ipc(data_t *data)
+{
+	sysarg_t cols;
+	sysarg_t rows;
+	screen_get_size(&cols, &rows);
+	
+	sysarg_t col;
+	sysarg_t row;
+	screen_get_pos(&col, &row);
+	
+	size_t i;
+	for (i = 0; (i < data->tasks_count) && (row < rows); i++, row++) {
+		uint64_t call_sent;
+		uint64_t call_received;
+		uint64_t answer_sent;
+		uint64_t answer_received;
+		uint64_t irq_notif_received;
+		uint64_t forwarded;
+		
+		char call_sent_suffix;
+		char call_received_suffix;
+		char answer_sent_suffix;
+		char answer_received_suffix;
+		char irq_notif_received_suffix;
+		char forwarded_suffix;
+		
+		order_suffix(data->tasks[i].ipc_info.call_sent, &call_sent,
+		    &call_sent_suffix);
+		order_suffix(data->tasks[i].ipc_info.call_received,
+		    &call_received, &call_received_suffix);
+		order_suffix(data->tasks[i].ipc_info.answer_sent,
+		    &answer_sent, &answer_sent_suffix);
+		order_suffix(data->tasks[i].ipc_info.answer_received,
+		    &answer_received, &answer_received_suffix);
+		order_suffix(data->tasks[i].ipc_info.irq_notif_received,
+		    &irq_notif_received, &irq_notif_received_suffix);
+		order_suffix(data->tasks[i].ipc_info.forwarded, &forwarded,
+		    &forwarded_suffix);
+		
+		printf("%-8" PRIu64 " %8" PRIu64 "%c %8" PRIu64 "%c"
+		     " %8" PRIu64 "%c %8" PRIu64 "%c %8" PRIu64 "%c"
+		     " %8" PRIu64 "%c ", data->tasks[i].task_id,
+		     call_sent, call_sent_suffix,
+		     call_received, call_received_suffix,
+		     answer_sent, answer_sent_suffix,
+		     answer_received, answer_received_suffix,
+		     irq_notif_received, irq_notif_received_suffix,
+		     forwarded, forwarded_suffix);
+		print_string(data->tasks[i].name);
+		
+		screen_newline();
+	}
+	
+	while (row < rows) {
+		screen_newline();
+		row++;
+	}
+}
+
+static inline void print_excs_head(void)
+{
+	screen_style_inverted();
+	printf("[exc   ] [count   ] [%%count] [cycles  ] [%%cycles] [description");
+	screen_newline();
+	screen_style_normal();
+}
+
+static inline void print_excs(data_t *data)
+{
+	sysarg_t cols;
+	sysarg_t rows;
+	screen_get_size(&cols, &rows);
+	
+	sysarg_t col;
+	sysarg_t row;
+	screen_get_pos(&col, &row);
+	
+	size_t i;
+	for (i = 0; (i < data->exceptions_count) && (row < rows); i++) {
+		/* Filter-out cold exceptions if not instructed otherwise */
+		if ((!excs_all) && (!data->exceptions[i].hot))
+			continue;
+		
+		uint64_t count;
+		uint64_t cycles;
+		
+		char count_suffix;
+		char cycles_suffix;
+		
+		order_suffix(data->exceptions[i].count, &count, &count_suffix);
+		order_suffix(data->exceptions[i].cycles, &cycles, &cycles_suffix);
+		
+		printf("%-8u %9" PRIu64 "%c  ",
+		     data->exceptions[i].id, count, count_suffix);
+		print_percent(data->exceptions_perc[i].count, 2);
+		printf(" %9" PRIu64 "%c   ", cycles, cycles_suffix);
+		print_percent(data->exceptions_perc[i].cycles, 2);
+		puts(" ");
+		print_string(data->exceptions[i].desc);
+		
+		screen_newline();
+		row++;
+	}
+	
+	while (row < rows) {
+		screen_newline();
+		row++;
+	}
+}
+
+static void print_help(void)
+{
+	sysarg_t cols;
+	sysarg_t rows;
+	screen_get_size(&cols, &rows);
+	
+	sysarg_t col;
+	sysarg_t row;
+	screen_get_pos(&col, &row);
+	
+	screen_newline();
+	
+	printf("Operation modes:");
+	screen_newline();
+	
+	printf(" t .. tasks statistics");
+	screen_newline();
+	
+	printf(" i .. IPC statistics");
+	screen_newline();
+	
+	printf(" e .. exceptions statistics");
+	screen_newline();
+	
+	printf("      a .. toggle display of all/hot exceptions");
+	screen_newline();
+	
+	row += 6;
+	
+	while (row < rows) {
+		screen_newline();
+		row++;
+	}
+}
+
+void print_data(data_t *data)
+{
+	screen_restart(false);
+	print_global_head(data);
+	print_task_summary(data);
+	print_thread_summary(data);
+	print_cpu_info(data);
+	print_physmem_info(data);
+	
+	/* Empty row for warnings */
+	screen_get_pos(&warn_col, &warn_row);
+	screen_newline();
+	
+	switch (op_mode) {
+	case OP_TASKS:
+		print_tasks_head();
+		print_tasks(data);
+		break;
+	case OP_IPC:
+		print_ipc_head();
+		print_ipc(data);
+		break;
+	case OP_EXCS:
+		print_excs_head();
+		print_excs(data);
+		break;
+	case OP_HELP:
+		print_tasks_head();
+		print_help();
+	}
+	
+	console_flush(console);
+}
+
+void print_warning(const char *fmt, ...)
+{
+	screen_moveto(warn_col, warn_row);
+	
+	va_list args;
+	va_start(args, fmt);
+	vprintf(fmt, args);
+	va_end(args);
+	
+	screen_newline();
+	console_flush(console);
+}
+
+/** Get char with timeout
+ *
+ */
+int tgetchar(unsigned int sec)
+{
+	/*
+	 * Reset timeleft whenever it is not positive.
+	 */
+	
+	if (timeleft <= 0)
+		timeleft = sec * USEC_COUNT;
+	
+	/*
+	 * Wait to see if there is any input. If so, take it and
+	 * update timeleft so that the next call to tgetchar()
+	 * will not wait as long. If there is no input,
+	 * make timeleft zero and return -1.
+	 */
+	
+	wchar_t c = 0;
+	
+	while (c == 0) {
+		kbd_event_t event;
+		
+		if (!console_get_kbd_event_timeout(console, &event, &timeleft)) {
+			timeleft = 0;
+			return -1;
+		}
+		
+		if (event.type == KEY_PRESS)
+			c = event.c;
+	}
+	
+	return (int) c;
+}
+
+/** @}
+ */
Index: uspace/dist/src/c/demos/top/screen.h
===================================================================
--- uspace/dist/src/c/demos/top/screen.h	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/top/screen.h	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2010 Stanislav Kozina
+ * Copyright (c) 2010 Martin Decky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup top
+ * @{
+ */
+
+#ifndef TOP_SCREEN_H_
+#define TOP_SCREEN_H_
+
+#include <io/console.h>
+#include "top.h"
+
+extern console_ctrl_t *console;
+
+extern void screen_init(void);
+extern void screen_done(void);
+extern void print_data(data_t *);
+extern void print_warning(const char *, ...);
+
+extern int tgetchar(unsigned int);
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/dist/src/c/demos/top/top.c
===================================================================
--- uspace/dist/src/c/demos/top/top.c	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/top/top.c	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,440 @@
+/*
+ * Copyright (c) 2010 Stanislav Kozina
+ * Copyright (c) 2010 Martin Decky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup top
+ * @brief Top utility.
+ * @{
+ */
+/**
+ * @file
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <task.h>
+#include <thread.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <sort.h>
+#include "screen.h"
+#include "top.h"
+
+#define NAME  "top"
+
+#define UPDATE_INTERVAL  1
+
+#define DAY     86400
+#define HOUR    3600
+#define MINUTE  60
+
+op_mode_t op_mode = OP_TASKS;
+sort_mode_t sort_mode = SORT_TASK_CYCLES;
+bool excs_all = false;
+
+static const char *read_data(data_t *target)
+{
+	/* Initialize data */
+	target->load = NULL;
+	target->cpus = NULL;
+	target->cpus_perc = NULL;
+	target->tasks = NULL;
+	target->tasks_perc = NULL;
+	target->tasks_map = NULL;
+	target->threads = NULL;
+	target->exceptions = NULL;
+	target->exceptions_perc = NULL;
+	target->physmem = NULL;
+	target->ucycles_diff = NULL;
+	target->kcycles_diff = NULL;
+	target->ecycles_diff = NULL;
+	target->ecount_diff = NULL;
+	
+	/* Get current time */
+	struct timeval time;
+	if (gettimeofday(&time, NULL) != EOK)
+		return "Cannot get time of day";
+	
+	target->hours = (time.tv_sec % DAY) / HOUR;
+	target->minutes = (time.tv_sec % HOUR) / MINUTE;
+	target->seconds = time.tv_sec % MINUTE;
+	
+	/* Get uptime */
+	sysarg_t uptime = stats_get_uptime();
+	target->udays = uptime / DAY;
+	target->uhours = (uptime % DAY) / HOUR;
+	target->uminutes = (uptime % HOUR) / MINUTE;
+	target->useconds = uptime % MINUTE;
+	
+	/* Get load */
+	target->load = stats_get_load(&(target->load_count));
+	if (target->load == NULL)
+		return "Cannot get system load";
+	
+	/* Get CPUs */
+	target->cpus = stats_get_cpus(&(target->cpus_count));
+	if (target->cpus == NULL)
+		return "Cannot get CPUs";
+	
+	target->cpus_perc =
+	    (perc_cpu_t *) calloc(target->cpus_count, sizeof(perc_cpu_t));
+	if (target->cpus_perc == NULL)
+		return "Not enough memory for CPU utilization";
+	
+	/* Get tasks */
+	target->tasks = stats_get_tasks(&(target->tasks_count));
+	if (target->tasks == NULL)
+		return "Cannot get tasks";
+	
+	target->tasks_perc =
+	    (perc_task_t *) calloc(target->tasks_count, sizeof(perc_task_t));
+	if (target->tasks_perc == NULL)
+		return "Not enough memory for task utilization";
+	
+	target->tasks_map =
+	    (size_t *) calloc(target->tasks_count, sizeof(size_t));
+	if (target->tasks_map == NULL)
+		return "Not enough memory for task map";
+	
+	/* Get threads */
+	target->threads = stats_get_threads(&(target->threads_count));
+	if (target->threads == NULL)
+		return "Cannot get threads";
+	
+	/* Get Exceptions */
+	target->exceptions = stats_get_exceptions(&(target->exceptions_count));
+	if (target->exceptions == NULL)
+		return "Cannot get exceptions";
+	
+	target->exceptions_perc =
+	    (perc_exc_t *) calloc(target->exceptions_count, sizeof(perc_exc_t));
+	if (target->exceptions_perc == NULL)
+		return "Not enough memory for exception utilization";
+	
+	/* Get physical memory */
+	target->physmem = stats_get_physmem();
+	if (target->physmem == NULL)
+		return "Cannot get physical memory";
+	
+	target->ucycles_diff = calloc(target->tasks_count,
+	    sizeof(uint64_t));
+	if (target->ucycles_diff == NULL)
+		return "Not enough memory for user utilization";
+	
+	/* Allocate memory for computed values */
+	target->kcycles_diff = calloc(target->tasks_count,
+	    sizeof(uint64_t));
+	if (target->kcycles_diff == NULL)
+		return "Not enough memory for kernel utilization";
+	
+	target->ecycles_diff = calloc(target->exceptions_count,
+	    sizeof(uint64_t));
+	if (target->ecycles_diff == NULL)
+		return "Not enough memory for exception cycles utilization";
+	
+	target->ecount_diff = calloc(target->exceptions_count,
+	    sizeof(uint64_t));
+	if (target->ecount_diff == NULL)
+		return "Not enough memory for exception count utilization";
+	
+	return NULL;
+}
+
+/** Computes percentage differencies from old_data to new_data
+ *
+ * @param old_data Pointer to old data strucutre.
+ * @param new_data Pointer to actual data where percetages are stored.
+ *
+ */
+static void compute_percentages(data_t *old_data, data_t *new_data)
+{
+	/* For each CPU: Compute total cycles and divide it between
+	   user and kernel */
+	
+	size_t i;
+	for (i = 0; i < new_data->cpus_count; i++) {
+		uint64_t idle =
+		    new_data->cpus[i].idle_cycles - old_data->cpus[i].idle_cycles;
+		uint64_t busy =
+		    new_data->cpus[i].busy_cycles - old_data->cpus[i].busy_cycles;
+		uint64_t sum = idle + busy;
+		
+		FRACTION_TO_FLOAT(new_data->cpus_perc[i].idle, idle * 100, sum);
+		FRACTION_TO_FLOAT(new_data->cpus_perc[i].busy, busy * 100, sum);
+	}
+	
+	/* For all tasks compute sum and differencies of all cycles */
+	
+	uint64_t virtmem_total = 0;
+	uint64_t resmem_total = 0;
+	uint64_t ucycles_total = 0;
+	uint64_t kcycles_total = 0;
+	
+	for (i = 0; i < new_data->tasks_count; i++) {
+		/* Match task with the previous instance */
+		
+		bool found = false;
+		size_t j;
+		for (j = 0; j < old_data->tasks_count; j++) {
+			if (new_data->tasks[i].task_id == old_data->tasks[j].task_id) {
+				found = true;
+				break;
+			}
+		}
+		
+		if (!found) {
+			/* This is newly borned task, ignore it */
+			new_data->ucycles_diff[i] = 0;
+			new_data->kcycles_diff[i] = 0;
+			continue;
+		}
+		
+		new_data->ucycles_diff[i] =
+		    new_data->tasks[i].ucycles - old_data->tasks[j].ucycles;
+		new_data->kcycles_diff[i] =
+		    new_data->tasks[i].kcycles - old_data->tasks[j].kcycles;
+		
+		virtmem_total += new_data->tasks[i].virtmem;
+		resmem_total += new_data->tasks[i].resmem;
+		ucycles_total += new_data->ucycles_diff[i];
+		kcycles_total += new_data->kcycles_diff[i];
+	}
+	
+	/* For each task compute percential change */
+	
+	for (i = 0; i < new_data->tasks_count; i++) {
+		FRACTION_TO_FLOAT(new_data->tasks_perc[i].virtmem,
+		    new_data->tasks[i].virtmem * 100, virtmem_total);
+		FRACTION_TO_FLOAT(new_data->tasks_perc[i].resmem,
+		    new_data->tasks[i].resmem * 100, resmem_total);
+		FRACTION_TO_FLOAT(new_data->tasks_perc[i].ucycles,
+		    new_data->ucycles_diff[i] * 100, ucycles_total);
+		FRACTION_TO_FLOAT(new_data->tasks_perc[i].kcycles,
+		    new_data->kcycles_diff[i] * 100, kcycles_total);
+	}
+	
+	/* For all exceptions compute sum and differencies of cycles */
+	
+	uint64_t ecycles_total = 0;
+	uint64_t ecount_total = 0;
+	
+	for (i = 0; i < new_data->exceptions_count; i++) {
+		/*
+		 * March exception with the previous instance.
+		 * This is quite paranoid since exceptions do not
+		 * usually disappear, but it does not hurt.
+		 */
+		
+		bool found = false;
+		size_t j;
+		for (j = 0; j < old_data->exceptions_count; j++) {
+			if (new_data->exceptions[i].id == old_data->exceptions[j].id) {
+				found = true;
+				break;
+			}
+		}
+		
+		if (!found) {
+			/* This is a new exception, ignore it */
+			new_data->ecycles_diff[i] = 0;
+			new_data->ecount_diff[i] = 0;
+			continue;
+		}
+		
+		new_data->ecycles_diff[i] =
+		    new_data->exceptions[i].cycles - old_data->exceptions[j].cycles;
+		new_data->ecount_diff[i] =
+		    new_data->exceptions[i].count - old_data->exceptions[i].count;
+		
+		ecycles_total += new_data->ecycles_diff[i];
+		ecount_total += new_data->ecount_diff[i];
+	}
+	
+	/* For each exception compute percential change */
+	
+	for (i = 0; i < new_data->exceptions_count; i++) {
+		FRACTION_TO_FLOAT(new_data->exceptions_perc[i].cycles,
+		    new_data->ecycles_diff[i] * 100, ecycles_total);
+		FRACTION_TO_FLOAT(new_data->exceptions_perc[i].count,
+		    new_data->ecount_diff[i] * 100, ecount_total);
+	}
+}
+
+static int cmp_data(void *a, void *b, void *arg)
+{
+	size_t ia = *((size_t *) a);
+	size_t ib = *((size_t *) b);
+	data_t *data = (data_t *) arg;
+	
+	uint64_t acycles = data->ucycles_diff[ia] + data->kcycles_diff[ia];
+	uint64_t bcycles = data->ucycles_diff[ib] + data->kcycles_diff[ib];
+	
+	if (acycles > bcycles)
+		return -1;
+	
+	if (acycles < bcycles)
+		return 1;
+	
+	return 0;
+}
+
+static void sort_data(data_t *data)
+{
+	size_t i;
+	
+	for (i = 0; i < data->tasks_count; i++)
+		data->tasks_map[i] = i;
+	
+	qsort((void *) data->tasks_map, data->tasks_count,
+	    sizeof(size_t), cmp_data, (void *) data);
+}
+
+static void free_data(data_t *target)
+{
+	if (target->load != NULL)
+		free(target->load);
+	
+	if (target->cpus != NULL)
+		free(target->cpus);
+	
+	if (target->cpus_perc != NULL)
+		free(target->cpus_perc);
+	
+	if (target->tasks != NULL)
+		free(target->tasks);
+	
+	if (target->tasks_perc != NULL)
+		free(target->tasks_perc);
+	
+	if (target->threads != NULL)
+		free(target->threads);
+	
+	if (target->exceptions != NULL)
+		free(target->exceptions);
+	
+	if (target->exceptions_perc != NULL)
+		free(target->exceptions_perc);
+	
+	if (target->physmem != NULL)
+		free(target->physmem);
+	
+	if (target->ucycles_diff != NULL)
+		free(target->ucycles_diff);
+	
+	if (target->kcycles_diff != NULL)
+		free(target->kcycles_diff);
+	
+	if (target->ecycles_diff != NULL)
+		free(target->ecycles_diff);
+	
+	if (target->ecount_diff != NULL)
+		free(target->ecount_diff);
+}
+
+int main(int argc, char *argv[])
+{
+	data_t data;
+	data_t data_prev;
+	const char *ret = NULL;
+	
+	screen_init();
+	printf("Reading initial data...\n");
+	
+	if ((ret = read_data(&data_prev)) != NULL)
+		goto out;
+	
+	/* Compute some rubbish to have initialised values */
+	compute_percentages(&data_prev, &data_prev);
+	
+	/* And paint screen until death */
+	while (true) {
+		int c = tgetchar(UPDATE_INTERVAL);
+		if (c < 0) {
+			if ((ret = read_data(&data)) != NULL) {
+				free_data(&data);
+				goto out;
+			}
+			
+			compute_percentages(&data_prev, &data);
+			sort_data(&data);
+			print_data(&data);
+			free_data(&data_prev);
+			data_prev = data;
+			
+			continue;
+		}
+		
+		switch (c) {
+			case 't':
+				print_warning("Showing task statistics");
+				op_mode = OP_TASKS;
+				break;
+			case 'i':
+				print_warning("Showing IPC statistics");
+				op_mode = OP_IPC;
+				break;
+			case 'e':
+				print_warning("Showing exception statistics");
+				op_mode = OP_EXCS;
+				break;
+			case 'h':
+				print_warning("Showing help");
+				op_mode = OP_HELP;
+				break;
+			case 'q':
+				goto out;
+			case 'a':
+				if (op_mode == OP_EXCS) {
+					excs_all = !excs_all;
+					if (excs_all)
+						print_warning("Showing all exceptions");
+					else
+						print_warning("Showing only hot exceptions");
+					break;
+				}
+			default:
+				print_warning("Unknown command \"%c\", use \"h\" for help", c);
+				break;
+		}
+	}
+	
+out:
+	screen_done();
+	free_data(&data_prev);
+	
+	if (ret != NULL) {
+		fprintf(stderr, "%s: %s\n", NAME, ret);
+		return 1;
+	}
+	
+	return 0;
+}
+
+/** @}
+ */
Index: uspace/dist/src/c/demos/top/top.h
===================================================================
--- uspace/dist/src/c/demos/top/top.h	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
+++ uspace/dist/src/c/demos/top/top.h	(revision e462909e04b1c7a6f3480de93f06d41fa58ba016)
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2010 Stanislav Kozina
+ * Copyright (c) 2010 Martin Decky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup top
+ * @{
+ */
+
+#ifndef TOP_TOP_H_
+#define TOP_TOP_H_
+
+#include <task.h>
+#include <stats.h>
+#include <time.h>
+
+#define FRACTION_TO_FLOAT(float, a, b) \
+	do { \
+		if ((b) != 0) { \
+			(float).upper = (a); \
+			(float).lower = (b); \
+		} else { \
+			(float).upper = 0; \
+			(float).lower = 1; \
+		} \
+	} while (0)
+
+typedef enum {
+	OP_TASKS,
+	OP_IPC,
+	OP_EXCS,
+	OP_HELP
+} op_mode_t;
+
+typedef enum {
+	SORT_TASK_CYCLES
+} sort_mode_t;
+
+extern op_mode_t op_mode;
+extern sort_mode_t sort_mode;
+extern bool excs_all;
+
+typedef struct {
+	uint64_t upper;
+	uint64_t lower;
+} fixed_float;
+
+typedef struct {
+	fixed_float idle;
+	fixed_float busy;
+} perc_cpu_t;
+
+typedef struct {
+	fixed_float virtmem;
+	fixed_float resmem;
+	fixed_float ucycles;
+	fixed_float kcycles;
+} perc_task_t;
+
+typedef struct {
+	fixed_float cycles;
+	fixed_float count;
+} perc_exc_t;
+
+typedef struct {
+	time_t hours;
+	time_t minutes;
+	time_t seconds;
+	
+	sysarg_t udays;
+	sysarg_t uhours;
+	sysarg_t uminutes;
+	sysarg_t useconds;
+	
+	size_t load_count;
+	load_t *load;
+	
+	size_t cpus_count;
+	stats_cpu_t *cpus;
+	perc_cpu_t *cpus_perc;
+	
+	size_t tasks_count;
+	stats_task_t *tasks;
+	perc_task_t *tasks_perc;
+	size_t *tasks_map;
+	
+	size_t threads_count;
+	stats_thread_t *threads;
+	
+	size_t exceptions_count;
+	stats_exc_t *exceptions;
+	perc_exc_t *exceptions_perc;
+	
+	stats_physmem_t *physmem;
+	
+	uint64_t *ucycles_diff;
+	uint64_t *kcycles_diff;
+	uint64_t *ecycles_diff;
+	uint64_t *ecount_diff;
+} data_t;
+
+#endif
+
+/**
+ * @}
+ */
