Index: uspace/srv/hid/remcons/remcons.c
===================================================================
--- uspace/srv/hid/remcons/remcons.c	(revision 513237966a1ea4b66980df2deb5b151213666dae)
+++ uspace/srv/hid/remcons/remcons.c	(revision 47d060dc4e7035ebfea2fa54953c74fd8e0f402a)
@@ -144,4 +144,10 @@
 };
 
+static void remcons_telnet_ws_update(void *, unsigned, unsigned);
+
+static telnet_cb_t remcons_telnet_cb = {
+	.ws_update = remcons_telnet_ws_update
+};
+
 static loc_srv_t *remcons_srv;
 static bool no_ctl;
@@ -260,6 +266,6 @@
 
 	if (remcons->enable_ctl) {
-		*cols = 80;
-		*rows = 25;
+		*cols = remcons->vt->cols;
+		*rows = remcons->vt->rows;
 	} else {
 		*cols = 100;
@@ -352,11 +358,31 @@
 {
 	remcons_event_t *event = malloc(sizeof(remcons_event_t));
-	assert(event);
+	if (event == NULL) {
+		fprintf(stderr, "Out of memory.\n");
+		return NULL;
+	}
 
 	link_initialize(&event->link);
-	event->kbd.type = type;
-	event->kbd.mods = mods;
-	event->kbd.key = key;
-	event->kbd.c = c;
+	event->cev.type = CEV_KEY;
+	event->cev.ev.key.type = type;
+	event->cev.ev.key.mods = mods;
+	event->cev.ev.key.key = key;
+	event->cev.ev.key.c = c;
+
+	return event;
+}
+
+/** Creates new console resize event.
+ */
+static remcons_event_t *new_resize_event(void)
+{
+	remcons_event_t *event = malloc(sizeof(remcons_event_t));
+	if (event == NULL) {
+		fprintf(stderr, "Out of memory.\n");
+		return NULL;
+	}
+
+	link_initialize(&event->link);
+	event->cev.type = CEV_RESIZE;
 
 	return event;
@@ -385,7 +411,5 @@
 	remcons_event_t *tmp = list_get_instance(link, remcons_event_t, link);
 
-	event->type = CEV_KEY;
-	event->ev.key = tmp->kbd;
-
+	*event = tmp->cev;
 	free(tmp);
 
@@ -590,9 +614,35 @@
 
 	remcons_event_t *down = new_kbd_event(KEY_PRESS, mods, key, c);
+	if (down == NULL)
+		return;
+
 	remcons_event_t *up = new_kbd_event(KEY_RELEASE, mods, key, c);
-	assert(down);
-	assert(up);
+	if (up == NULL) {
+		free(down);
+		return;
+	}
+
 	list_append(&down->link, &remcons->in_events);
 	list_append(&up->link, &remcons->in_events);
+}
+
+/** Window size update callback.
+ *
+ * @param arg Argument (remcons_t *)
+ * @param cols New number of columns
+ * @param rows New number of rows
+ */
+static void remcons_telnet_ws_update(void *arg, unsigned cols, unsigned rows)
+{
+	remcons_t *remcons = (remcons_t *)arg;
+
+	vt100_resize(remcons->vt, cols, rows);
+	telnet_user_resize(remcons->user, cols, rows);
+
+	remcons_event_t *resize = new_resize_event();
+	if (resize == NULL)
+		return;
+
+	list_append(&resize->link, &remcons->in_events);
 }
 
@@ -605,8 +655,19 @@
 {
 	char_attrs_t attrs;
-	remcons_t *remcons = calloc(1, sizeof(remcons_t));
-	assert(remcons != NULL); // XXX
-	telnet_user_t *user = telnet_user_create(conn);
-	assert(user);
+	remcons_t *remcons = NULL;
+	telnet_user_t *user = NULL;
+
+	remcons = calloc(1, sizeof(remcons_t));
+	if (remcons == NULL) {
+		fprintf(stderr, "Out of memory.\n");
+		goto error;
+	}
+
+	user = telnet_user_create(conn, &remcons_telnet_cb,
+	    (void *)remcons);
+	if (user == NULL) {
+		fprintf(stderr, "Out of memory.\n");
+		goto error;
+	}
 
 	remcons->enable_ctl = !no_ctl;
@@ -626,5 +687,9 @@
 
 	remcons->vt = vt100_create((void *)remcons, 80, 25, &remcons_vt_cb);
-	assert(remcons->vt != NULL); // XXX
+	if (remcons->vt == NULL) {
+		fprintf(stderr, "Error creating VT100 driver instance.\n");
+		goto error;
+	}
+
 	remcons->vt->enable_rgb = remcons->enable_rgb;
 
@@ -649,5 +714,5 @@
 		telnet_user_error(user, "Unable to register %s with loc: %s.",
 		    user->service_name, str_error(rc));
-		return;
+		goto error;
 	}
 
@@ -656,5 +721,8 @@
 
 	fid_t spawn_fibril = fibril_create(spawn_task_fibril, user);
-	assert(spawn_fibril);
+	if (spawn_fibril == 0) {
+		fprintf(stderr, "Failed creating fibril.\n");
+		goto error;
+	}
 	fibril_add_ready(spawn_fibril);
 
@@ -685,4 +753,14 @@
 	telnet_user_log(user, "Destroying...");
 	telnet_user_destroy(user);
+	return;
+error:
+	if (user != NULL && user->service_id != 0)
+		loc_service_unregister(remcons_srv, user->service_id);
+	if (user != NULL)
+		free(user);
+	if (remcons != NULL && remcons->vt != NULL)
+		vt100_destroy(remcons->vt);
+	if (remcons != NULL)
+		free(remcons);
 }
 
Index: uspace/srv/hid/remcons/remcons.h
===================================================================
--- uspace/srv/hid/remcons/remcons.h	(revision 513237966a1ea4b66980df2deb5b151213666dae)
+++ uspace/srv/hid/remcons/remcons.h	(revision 47d060dc4e7035ebfea2fa54953c74fd8e0f402a)
@@ -38,5 +38,5 @@
 
 #include <adt/list.h>
-#include <io/kbd_event.h>
+#include <io/cons_event.h>
 #include <stdbool.h>
 #include <vt/vt100.h>
@@ -64,5 +64,5 @@
 typedef struct {
 	link_t link;		/**< link to list of events */
-	kbd_event_t kbd;	/**< keyboard event */
+	cons_event_t cev;	/**< console event */
 } remcons_event_t;
 
Index: uspace/srv/hid/remcons/telnet.h
===================================================================
--- uspace/srv/hid/remcons/telnet.h	(revision 513237966a1ea4b66980df2deb5b151213666dae)
+++ uspace/srv/hid/remcons/telnet.h	(revision 47d060dc4e7035ebfea2fa54953c74fd8e0f402a)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2012 Vojtech Horky
  * All rights reserved.
@@ -44,4 +45,6 @@
  */
 
+#define TELNET_SE 240
+#define TELNET_SB 250
 #define TELNET_IAC 255
 
@@ -55,4 +58,5 @@
 #define TELNET_ECHO 1
 #define TELNET_SUPPRESS_GO_AHEAD 3
+#define TELNET_NAWS 31
 #define TELNET_LINEMODE 34
 
Index: uspace/srv/hid/remcons/user.c
===================================================================
--- uspace/srv/hid/remcons/user.c	(revision 513237966a1ea4b66980df2deb5b151213666dae)
+++ uspace/srv/hid/remcons/user.c	(revision 47d060dc4e7035ebfea2fa54953c74fd8e0f402a)
@@ -58,10 +58,16 @@
 static LIST_INITIALIZE(users);
 
+static errno_t telnet_user_send_raw_locked(telnet_user_t *, const void *,
+    size_t);
+static errno_t telnet_user_flush_locked(telnet_user_t *);
+
 /** Create new telnet user.
  *
  * @param conn Incoming connection.
+ * @param cb Callback functions
+ * @param arg Argument to callback functions
  * @return New telnet user or NULL when out of memory.
  */
-telnet_user_t *telnet_user_create(tcp_conn_t *conn)
+telnet_user_t *telnet_user_create(tcp_conn_t *conn, telnet_cb_t *cb, void *arg)
 {
 	static int telnet_user_id_counter = 0;
@@ -72,4 +78,6 @@
 	}
 
+	user->cb = cb;
+	user->arg = arg;
 	user->id = ++telnet_user_id_counter;
 
@@ -216,5 +224,6 @@
  * @return EOK on success or an error code
  */
-static errno_t telnet_user_recv_next_byte_locked(telnet_user_t *user, char *byte)
+static errno_t telnet_user_recv_next_byte_locked(telnet_user_t *user,
+    uint8_t *byte)
 {
 	errno_t rc;
@@ -227,5 +236,5 @@
 	}
 
-	*byte = user->socket_buffer[user->socket_buffer_pos++];
+	*byte = (uint8_t)user->socket_buffer[user->socket_buffer_pos++];
 	return EOK;
 }
@@ -241,5 +250,117 @@
 }
 
-/** Process telnet command (currently only print to screen).
+static errno_t telnet_user_send_opt(telnet_user_t *user, telnet_cmd_t cmd,
+    telnet_cmd_t opt)
+{
+	uint8_t cmdb[3];
+
+	cmdb[0] = TELNET_IAC;
+	cmdb[1] = cmd;
+	cmdb[2] = opt;
+
+	return telnet_user_send_raw_locked(user, (char *)cmdb, sizeof(cmdb));
+}
+
+/** Process telnet WILL NAWS command.
+ *
+ * @param user Telnet user structure.
+ * @param cmd Telnet command.
+ */
+static void process_telnet_will_naws(telnet_user_t *user)
+{
+	telnet_user_log(user, "WILL NAWS");
+	/* Send DO NAWS */
+	(void) telnet_user_send_opt(user, TELNET_DO, TELNET_NAWS);
+	(void) telnet_user_flush_locked(user);
+}
+
+/** Process telnet SB NAWS command.
+ *
+ * @param user Telnet user structure.
+ * @param cmd Telnet command.
+ */
+static void process_telnet_sb_naws(telnet_user_t *user)
+{
+	uint8_t chi, clo;
+	uint8_t rhi, rlo;
+	uint16_t cols;
+	uint16_t rows;
+	uint8_t iac;
+	uint8_t se;
+	errno_t rc;
+
+	telnet_user_log(user, "SB NAWS...");
+
+	rc = telnet_user_recv_next_byte_locked(user, &chi);
+	if (rc != EOK)
+		return;
+	rc = telnet_user_recv_next_byte_locked(user, &clo);
+	if (rc != EOK)
+		return;
+
+	rc = telnet_user_recv_next_byte_locked(user, &rhi);
+	if (rc != EOK)
+		return;
+	rc = telnet_user_recv_next_byte_locked(user, &rlo);
+	if (rc != EOK)
+		return;
+
+	rc = telnet_user_recv_next_byte_locked(user, &iac);
+	if (rc != EOK)
+		return;
+	rc = telnet_user_recv_next_byte_locked(user, &se);
+	if (rc != EOK)
+		return;
+
+	cols = (chi << 8) | clo;
+	rows = (rhi << 8) | rlo;
+
+	telnet_user_log(user, "cols=%u rows=%u\n", cols, rows);
+
+	if (cols < 1 || rows < 1) {
+		telnet_user_log(user, "Ignoring invalid window size update.");
+		return;
+	}
+
+	user->cb->ws_update(user->arg, cols, rows);
+}
+
+/** Process telnet WILL command.
+ *
+ * @param user Telnet user structure.
+ * @param opt Option code.
+ */
+static void process_telnet_will(telnet_user_t *user, telnet_cmd_t opt)
+{
+	telnet_user_log(user, "WILL");
+	switch (opt) {
+	case TELNET_NAWS:
+		process_telnet_will_naws(user);
+		return;
+	}
+
+	telnet_user_log(user, "Ignoring telnet command %u %u %u.",
+	    TELNET_IAC, TELNET_WILL, opt);
+}
+
+/** Process telnet SB command.
+ *
+ * @param user Telnet user structure.
+ * @param opt Option code.
+ */
+static void process_telnet_sb(telnet_user_t *user, telnet_cmd_t opt)
+{
+	telnet_user_log(user, "SB");
+	switch (opt) {
+	case TELNET_NAWS:
+		process_telnet_sb_naws(user);
+		return;
+	}
+
+	telnet_user_log(user, "Ignoring telnet command %u %u %u.",
+	    TELNET_IAC, TELNET_SB, opt);
+}
+
+/** Process telnet command.
  *
  * @param user Telnet user structure.
@@ -250,4 +371,13 @@
     telnet_cmd_t option_code, telnet_cmd_t cmd)
 {
+	switch (option_code) {
+	case TELNET_SB:
+		process_telnet_sb(user, cmd);
+		return;
+	case TELNET_WILL:
+		process_telnet_will(user, cmd);
+		return;
+	}
+
 	if (option_code != 0) {
 		telnet_user_log(user, "Ignoring telnet command %u %u %u.",
@@ -277,5 +407,5 @@
 
 	do {
-		char next_byte = 0;
+		uint8_t next_byte = 0;
 		bool inside_telnet_command = false;
 
@@ -290,5 +420,5 @@
 				return rc;
 			}
-			uint8_t byte = (uint8_t) next_byte;
+			uint8_t byte = next_byte;
 
 			/* Skip telnet commands. */
@@ -296,5 +426,6 @@
 				inside_telnet_command = false;
 				next_byte = 0;
-				if (TELNET_IS_OPTION_CODE(byte)) {
+				if (TELNET_IS_OPTION_CODE(byte) ||
+				    byte == TELNET_SB) {
 					telnet_option_code = byte;
 					inside_telnet_command = true;
@@ -430,4 +561,16 @@
 }
 
+static errno_t telnet_user_flush_locked(telnet_user_t *user)
+{
+	errno_t rc;
+
+	rc = tcp_conn_send(user->conn, user->send_buf, user->send_buf_used);
+	if (rc != EOK)
+		return rc;
+
+	user->send_buf_used = 0;
+	return EOK;
+}
+
 errno_t telnet_user_flush(telnet_user_t *user)
 {
@@ -435,14 +578,7 @@
 
 	fibril_mutex_lock(&user->guard);
-	rc = tcp_conn_send(user->conn, user->send_buf, user->send_buf_used);
-
-	if (rc != EOK) {
-		fibril_mutex_unlock(&user->guard);
-		return rc;
-	}
-
-	user->send_buf_used = 0;
+	rc = telnet_user_flush_locked(user);
 	fibril_mutex_unlock(&user->guard);
-	return EOK;
+	return rc;
 }
 
@@ -467,4 +603,20 @@
 }
 
+/** Resize telnet session.
+ *
+ * @param user Telnet user
+ * @param cols New number of columns
+ * @param rows New number of rows
+ */
+void telnet_user_resize(telnet_user_t *user, unsigned cols, unsigned rows)
+{
+	user->cols = cols;
+	user->rows = rows;
+	if ((unsigned)user->cursor_x > cols - 1)
+		user->cursor_x = cols - 1;
+	if ((unsigned)user->cursor_y > rows - 1)
+		user->cursor_y = rows - 1;
+}
+
 /**
  * @}
Index: uspace/srv/hid/remcons/user.h
===================================================================
--- uspace/srv/hid/remcons/user.h	(revision 513237966a1ea4b66980df2deb5b151213666dae)
+++ uspace/srv/hid/remcons/user.h	(revision 47d060dc4e7035ebfea2fa54953c74fd8e0f402a)
@@ -45,8 +45,17 @@
 #define SEND_BUF_SIZE 512
 
+/** Telnet callbacks */
+typedef struct {
+	void (*ws_update)(void *, unsigned, unsigned);
+} telnet_cb_t;
+
 /** Representation of a connected (human) user. */
 typedef struct {
 	/** Mutex guarding the whole structure. */
 	fibril_mutex_t guard;
+	/** Callback functions */
+	telnet_cb_t *cb;
+	/** Argument to callback functions */
+	void *arg;
 
 	/** Internal id, used for creating locfs entries. */
@@ -87,5 +96,5 @@
 } telnet_user_t;
 
-extern telnet_user_t *telnet_user_create(tcp_conn_t *);
+extern telnet_user_t *telnet_user_create(tcp_conn_t *, telnet_cb_t *, void *);
 extern void telnet_user_add(telnet_user_t *);
 extern void telnet_user_destroy(telnet_user_t *);
@@ -99,4 +108,5 @@
 extern errno_t telnet_user_recv(telnet_user_t *, void *, size_t, size_t *);
 extern void telnet_user_update_cursor_x(telnet_user_t *, int);
+extern void telnet_user_resize(telnet_user_t *, unsigned, unsigned);
 
 /** Print informational message about connected user. */
