diff --git a/src/newifc/alltests.c b/src/newifc/alltests.c
index d05957c4d1ea13133b35e1e28eeb51b9a9c83af3..b1ef0c1c38b2736a66a0bd3b7d1b84bbab533a70 100644
--- a/src/newifc/alltests.c
+++ b/src/newifc/alltests.c
@@ -2,13 +2,15 @@
 
 #include "CuTest.h"
 
-CuSuite* root_window_get_test_suite();
+void test_root_window(CuTest *ct);
+void test_label(CuTest *ct);
 
 void RunAllTests(void) {
 	CuString *output = CuStringNew();
 	CuSuite* suite = CuSuiteNew();
 
-	CuSuiteAddSuite(suite, root_window_get_test_suite());
+	SUITE_ADD_TEST(suite, test_root_window);
+	SUITE_ADD_TEST(suite, test_label);
 
 	CuSuiteRun(suite);
 	CuSuiteSummary(suite, output);
diff --git a/src/newifc/genapi.c b/src/newifc/genapi.c
index 815cb7dfd0820b9fb66b0b13ad3de16ebc657281..635eea4243693576bcfa50003461ffd192b50342 100644
--- a/src/newifc/genapi.c
+++ b/src/newifc/genapi.c
@@ -17,6 +17,7 @@ struct objtype_info {
 const struct objtype_info
 objtypes[] = {
 	{"root_window", 0},
+	{"label", 1},
 };
 
 enum attribute_types {
@@ -58,36 +59,41 @@ struct attribute_info {
 	enum attribute_types type;
 	enum attribute_impl impl;
 	int read_only;
+	int no_event;
 };
 
 const struct attribute_info
 attributes[] = {
-	{"bottomchild", NI_attr_type_NewIfcObj, attr_impl_global, 1},
-	{"child_height", NI_attr_type_uint16_t, attr_impl_global, 1},
-	{"child_width", NI_attr_type_uint16_t, attr_impl_global, 1},
-	{"child_xpos", NI_attr_type_uint16_t, attr_impl_global, 1},
-	{"child_ypos", NI_attr_type_uint16_t, attr_impl_global, 1},
-	{"fill_character", NI_attr_type_uint32_t, attr_impl_global, 0},
-	{"fill_character_colour", NI_attr_type_uint32_t, attr_impl_global, 0},
-	{"fill_colour", NI_attr_type_uint32_t, attr_impl_global, 0},
-	{"fill_font", NI_attr_type_uint8_t, attr_impl_global, 0},
-	{"focus", NI_attr_type_bool, attr_impl_global_custom_setter, 0},
-	{"height", NI_attr_type_uint16_t, attr_impl_global, 0},
-	{"hidden", NI_attr_type_bool, attr_impl_global, 1},
-	{"higherpeer", NI_attr_type_NewIfcObj, attr_impl_global, 1},
-	{"last_error", NI_attr_type_NI_err, attr_impl_global, 1},
-	{"locked", NI_attr_type_bool, attr_impl_root, 0},
-	{"locked_by_me", NI_attr_type_bool, attr_impl_root, 1},
-	{"lowerpeer", NI_attr_type_NewIfcObj, attr_impl_global, 1},
-	{"min_height", NI_attr_type_uint16_t, attr_impl_global, 1},
-	{"min_width", NI_attr_type_uint16_t, attr_impl_global, 1},
-	{"parent", NI_attr_type_NewIfcObj, attr_impl_global, 1},
-	{"root", NI_attr_type_NewIfcObj, attr_impl_global, 1},
-	{"topchild", NI_attr_type_NewIfcObj, attr_impl_global, 1},
-	{"type", NI_attr_type_NewIfc_object, attr_impl_global, 1},
-	{"width", NI_attr_type_uint16_t, attr_impl_global, 0},
-	{"xpos", NI_attr_type_uint16_t, attr_impl_global, 0},
-	{"ypos", NI_attr_type_uint16_t, attr_impl_global, 0},
+	{"bg_colour", NI_attr_type_uint32_t, attr_impl_global, 0, 0},
+	{"bottomchild", NI_attr_type_NewIfcObj, attr_impl_global, 1, 0},
+	{"child_height", NI_attr_type_uint16_t, attr_impl_global, 1, 0},
+	{"child_width", NI_attr_type_uint16_t, attr_impl_global, 1, 0},
+	{"child_xpos", NI_attr_type_uint16_t, attr_impl_global, 1, 0},
+	{"child_ypos", NI_attr_type_uint16_t, attr_impl_global, 1, 0},
+	{"fg_colour", NI_attr_type_uint32_t, attr_impl_global, 0, 0},
+	{"fill_character", NI_attr_type_uint8_t, attr_impl_global, 0, 0},
+	{"fill_character_colour", NI_attr_type_uint32_t, attr_impl_global, 0, 0},
+	{"fill_colour", NI_attr_type_uint32_t, attr_impl_global, 0, 0},
+	{"fill_font", NI_attr_type_uint8_t, attr_impl_global, 0, 0},
+	{"focus", NI_attr_type_bool, attr_impl_global_custom_setter, 0, 0},
+	{"font", NI_attr_type_uint8_t, attr_impl_global, 0, 0},
+	{"height", NI_attr_type_uint16_t, attr_impl_global, 0, 0},
+	{"hidden", NI_attr_type_bool, attr_impl_global, 1, 0},
+	{"higherpeer", NI_attr_type_NewIfcObj, attr_impl_global, 1, 0},
+	{"last_error", NI_attr_type_NI_err, attr_impl_global, 1, 0},
+	{"locked", NI_attr_type_bool, attr_impl_root, 0, 1},
+	{"locked_by_me", NI_attr_type_bool, attr_impl_root, 1, 1},
+	{"lowerpeer", NI_attr_type_NewIfcObj, attr_impl_global, 1, 0},
+	{"min_height", NI_attr_type_uint16_t, attr_impl_global, 1, 0},
+	{"min_width", NI_attr_type_uint16_t, attr_impl_global, 1, 0},
+	{"parent", NI_attr_type_NewIfcObj, attr_impl_global, 1, 0},
+	{"root", NI_attr_type_NewIfcObj, attr_impl_global, 1, 0},
+	{"text", NI_attr_type_charptr, attr_impl_object, 0, 0},
+	{"topchild", NI_attr_type_NewIfcObj, attr_impl_global, 1, 0},
+	{"type", NI_attr_type_NewIfc_object, attr_impl_global, 1, 0},
+	{"width", NI_attr_type_uint16_t, attr_impl_global, 0, 0},
+	{"xpos", NI_attr_type_uint16_t, attr_impl_global, 0, 0},
+	{"ypos", NI_attr_type_uint16_t, attr_impl_global, 0, 0},
 };
 
 struct attribute_alias {
@@ -98,6 +104,8 @@ struct attribute_alias {
 const struct attribute_alias aliases[] = {
 	{"fill_colour", "fill_color"},
 	{"fill_character_colour", "fill_character_color"},
+	{"fg_colour", "fg_color"},
+	{"bg_colour", "bg_color"},
 };
 
 struct error_info {
@@ -154,17 +162,19 @@ attribute_functions(size_t i, FILE *c_code, const char *alias)
 						"	NI_err ret;\n"
 						"	if (obj == NULL)\n"
 						"		return NewIfc_error_invalid_arg;\n"
-						"	if (NI_set_locked(obj, true)) {\n"
-						"		ret = call_%s_change_handlers(obj, NewIfc_%s, value);\n"
-						"		if (ret != NewIfc_error_none)\n"
-						"			return ret;\n"
-						"		ret = obj->set(obj, NewIfc_%s, value);\n"
+						"	if (NI_set_locked(obj, true)) {\n", alias, type_str[attributes[i].type].type);
+				if (!attributes[i].no_event) {
+					fprintf(c_code, "		ret = call_%s_change_handlers(obj, NewIfc_%s, value);\n"
+							"		if (ret != NewIfc_error_none)\n"
+							"			return ret;\n", type_str[attributes[i].type].var_name, attributes[i].name);
+				}
+				fprintf(c_code, "		ret = obj->set(obj, NewIfc_%s, value);\n"
 						"		NI_set_locked(obj, false);\n"
 						"	}\n"
 						"	else\n"
 						"		ret = NewIfc_error_lock_failed;\n"
 						"	return ret;\n"
-						"}\n\n", alias, type_str[attributes[i].type].type, type_str[attributes[i].type].var_name, attributes[i].name, attributes[i].name);
+						"}\n\n", attributes[i].name);
 			}
 
 			fprintf(c_code, "NI_err\n"
@@ -190,8 +200,13 @@ attribute_functions(size_t i, FILE *c_code, const char *alias)
 						"	NI_err ret;\n"
 						"	if (obj == NULL)\n"
 						"		return NewIfc_error_invalid_arg;\n"
-						"	if (NI_set_locked(obj, true)) {\n"
-						"		ret = obj->set(obj, NewIfc_%s, value);\n"
+						"	if (NI_set_locked(obj, true)) {\n", alias, type_str[attributes[i].type].type);
+				if (!attributes[i].no_event) {
+					fprintf(c_code, "		ret = call_%s_change_handlers(obj, NewIfc_%s, value);\n"
+							"		if (ret != NewIfc_error_none)\n"
+							"			return ret;\n", type_str[attributes[i].type].var_name, attributes[i].name);
+				}
+				fprintf(c_code, "		ret = obj->set(obj, NewIfc_%s, value);\n"
 						"		if (ret != NewIfc_error_none && obj->last_error != NewIfc_error_not_implemented) {\n"
 						"			obj->%s = value;\n"
 						"			obj->last_error = NewIfc_error_none;\n"
@@ -201,7 +216,7 @@ attribute_functions(size_t i, FILE *c_code, const char *alias)
 						"	else\n"
 						"		ret = NewIfc_error_lock_failed;\n"
 						"	return ret;\n"
-						"}\n\n", alias, type_str[attributes[i].type].type, attributes[i].name, attributes[i].name);
+						"}\n\n", attributes[i].name, attributes[i].name);
 			}
 
 			// Fall-through
@@ -250,27 +265,29 @@ attribute_functions(size_t i, FILE *c_code, const char *alias)
 					"}\n\n", alias, type_str[attributes[i].type].type, attributes[i].name);
 			break;
 	}
-	fprintf(c_code, "NI_err\n"
-			"NI_add_%s_handler(NewIfcObj obj, NI_err (*handler)(NewIfcObj obj, %s newval, void *cbdata), void *cbdata)\n"
-			"{\n"
-			"	NI_err ret;\n"
-			"	if (obj == NULL || handler == NULL)\n"
-			"		return NewIfc_error_invalid_arg;\n"
-			"	if (NI_set_locked(obj, true)) {\n"
-			"		struct NewIfc_handler *h = malloc(sizeof(struct NewIfc_handler));\n"
-			"		if (h == NULL)\n"
-			"			return NewIfc_error_allocation_failure;\n"
-			"		h->on_%s_change = handler;\n"
-			"		h->cbdata = cbdata;\n"
-			"		h->event = NewIfc_on_%s_change;\n"
-			"		h->next = NULL;\n"
-			"		ret = NI_install_handler(obj, h);\n"
-			"		NI_set_locked(obj, false);\n"
-			"	}\n"
-			"	else\n"
-			"		ret = NewIfc_error_lock_failed;\n"
-			"	return ret;\n"
-			"}\n\n", alias, type_str[attributes[i].type].type, type_str[attributes[i].type].var_name, attributes[i].name);
+	if (!attributes[i].no_event) {
+		fprintf(c_code, "NI_err\n"
+				"NI_add_%s_handler(NewIfcObj obj, NI_err (*handler)(NewIfcObj obj, %s newval, void *cbdata), void *cbdata)\n"
+				"{\n"
+				"	NI_err ret;\n"
+				"	if (obj == NULL || handler == NULL)\n"
+				"		return NewIfc_error_invalid_arg;\n"
+				"	if (NI_set_locked(obj, true)) {\n"
+				"		struct NewIfc_handler *h = malloc(sizeof(struct NewIfc_handler));\n"
+				"		if (h == NULL)\n"
+				"			return NewIfc_error_allocation_failure;\n"
+				"		h->on_%s_change = handler;\n"
+				"		h->cbdata = cbdata;\n"
+				"		h->event = NewIfc_on_%s_change;\n"
+				"		h->next = NULL;\n"
+				"		ret = NI_install_handler(obj, h);\n"
+				"		NI_set_locked(obj, false);\n"
+				"	}\n"
+				"	else\n"
+				"		ret = NewIfc_error_lock_failed;\n"
+				"	return ret;\n"
+				"}\n\n", alias, type_str[attributes[i].type].type, type_str[attributes[i].type].var_name, attributes[i].name);
+	}
 }
 
 int
@@ -318,21 +335,21 @@ main(int argc, char **argv)
 
 	fputs("#define NI_TRANSPARENT  UINT32_MAX\n"
 	      "#define NI_BLACK        UINT32_C(0)\n"
-	      "#define NI_BLUE         UINT32_C(0x0000A8)\n"
-	      "#define NI_GREEN        UINT32_C(0x00A800)\n"
-	      "#define NI_CYAN         UINT32_C(0x00A8A8)\n"
-	      "#define NI_RED          UINT32_C(0xA80000)\n"
-	      "#define NI_MAGENTA      UINT32_C(0xA800A8)\n"
-	      "#define NI_BROWN        UINT32_C(0xA85400)\n"
-	      "#define NI_LIGHTGRAY    UINT32_C(0xA8A8A8)\n"
-	      "#define NI_DARKGRAY     UINT32_C(0x545454)\n"
-	      "#define NI_LIGHTBLUE    UINT32_C(0x5454FF)\n"
-	      "#define NI_LIGHTGREEN   UINT32_C(0x54FF54)\n"
-	      "#define NI_LIGHTCYAN    UINT32_C(0x54FFFF)\n"
-	      "#define NI_LIGHTRED     UINT32_C(0xFF5454)\n"
-	      "#define NI_LIGHTMAGENTA UINT32_C(0xFF54FF)\n"
-	      "#define NI_YELLOW       UINT32_C(0xFFFF54)\n"
-	      "#define NI_WHITE        UINT32_C(0xFFFFFF)\n\n", header);
+	      "#define NI_BLUE         UINT32_C(0x800000A8)\n"
+	      "#define NI_GREEN        UINT32_C(0x8000A800)\n"
+	      "#define NI_CYAN         UINT32_C(0x8000A8A8)\n"
+	      "#define NI_RED          UINT32_C(0x80A80000)\n"
+	      "#define NI_MAGENTA      UINT32_C(0x80A800A8)\n"
+	      "#define NI_BROWN        UINT32_C(0x80A85400)\n"
+	      "#define NI_LIGHTGRAY    UINT32_C(0x80A8A8A8)\n"
+	      "#define NI_DARKGRAY     UINT32_C(0x80545454)\n"
+	      "#define NI_LIGHTBLUE    UINT32_C(0x805454FF)\n"
+	      "#define NI_LIGHTGREEN   UINT32_C(0x8054FF54)\n"
+	      "#define NI_LIGHTCYAN    UINT32_C(0x8054FFFF)\n"
+	      "#define NI_LIGHTRED     UINT32_C(0x80FF5454)\n"
+	      "#define NI_LIGHTMAGENTA UINT32_C(0x80FF54FF)\n"
+	      "#define NI_YELLOW       UINT32_C(0x80FFFF54)\n"
+	      "#define NI_WHITE        UINT32_C(0x80FFFFFF)\n\n", header);
 
 	fputs("NI_err NI_copy(NewIfcObj obj, NewIfcObj *newobj);\n", header);
 	fputs("NI_err NI_create(enum NewIfc_object obj, NewIfcObj parent, NewIfcObj *newobj);\n", header);
@@ -342,9 +359,10 @@ main(int argc, char **argv)
 	nitems = sizeof(attributes) / sizeof(attributes[0]);
 	for (i = 0; i < nitems; i++) {
 		if (!attributes[i].read_only) {
-			fprintf(header, "NI_err NI_add_%s_handler(NewIfcObj obj, NI_err (*handler)(NewIfcObj obj, %s newval, void *cbdata), void *cbdata);\n", attributes[i].name, type_str[attributes[i].type].type);
 			fprintf(header, "NI_err NI_set_%s(NewIfcObj obj, %s value);\n", attributes[i].name, type_str[attributes[i].type].type);
 		}
+		if (!attributes[i].no_event)
+			fprintf(header, "NI_err NI_add_%s_handler(NewIfcObj obj, NI_err (*handler)(NewIfcObj obj, %s newval, void *cbdata), void *cbdata);\n", attributes[i].name, type_str[attributes[i].type].type);
 		fprintf(header, "NI_err NI_get_%s(NewIfcObj obj, %s* value);\n", attributes[i].name, type_str[attributes[i].type].type);
 	}
 	nitems = sizeof(aliases) / sizeof(aliases[0]);
@@ -353,7 +371,8 @@ main(int argc, char **argv)
 		if (!attributes[a].read_only) {
 			fprintf(header, "NI_err NI_set_%s(NewIfcObj obj, %s value);\n", aliases[i].alias_name, type_str[attributes[a].type].type);
 		}
-		fprintf(header, "NI_err NI_add_%s_handler(NewIfcObj obj, NI_err (*handler)(NewIfcObj obj, %s newval, void *cbdata), void *cbdata);\n", aliases[i].alias_name, type_str[attributes[a].type].type);
+		if (!attributes[i].no_event)
+			fprintf(header, "NI_err NI_add_%s_handler(NewIfcObj obj, NI_err (*handler)(NewIfcObj obj, %s newval, void *cbdata), void *cbdata);\n", aliases[i].alias_name, type_str[attributes[a].type].type);
 		fprintf(header, "NI_err NI_get_%s(NewIfcObj obj, %s* value);\n", aliases[i].alias_name, type_str[attributes[a].type].type);
 	}
 	fputs("\n#endif\n", header);
@@ -372,6 +391,8 @@ main(int argc, char **argv)
 	fputs("#ifndef NEWIFC_INTERNAL_H\n"
 	      "#define NEWIFC_INTERNAL_H\n\n", internal_header);
 
+	fputs("#include \"ciolib.h\"\n\n", internal_header);
+
 	fputs("enum NewIfc_event_handlers {\n", internal_header);
 	nitems = sizeof(attributes) / sizeof(attributes[0]);
 	for (i = 0; i < nitems; i++) {
@@ -399,10 +420,21 @@ main(int argc, char **argv)
 	      "	enum NewIfc_event_handlers event;\n"
 	      "};\n\n", internal_header);
 
+	fputs("struct NewIfc_render_context {\n"
+	      "	struct vmem_cell *vmem;\n"
+	      "	uint16_t dwidth;\n"
+	      "	uint16_t dheight;\n"
+	      "	uint16_t width;\n"
+	      "	uint16_t height;\n"
+	      "	uint16_t xpos;\n"
+	      "	uint16_t ypos;\n"
+	      "};\n\n", internal_header);
+
 	fputs("struct newifc_api {\n"
 	      "	NI_err (*set)(NewIfcObj niobj, const int attr, ...);\n"
 	      "	NI_err (*get)(NewIfcObj niobj, const int attr, ...);\n"
 	      "	NI_err (*copy)(NewIfcObj obj, NewIfcObj *newobj);\n"
+	      "	NI_err (*do_render)(NewIfcObj obj, struct NewIfc_render_context *ctx);\n"
 	      "	struct NewIfc_handler **handlers;\n"
 	      "	NewIfcObj root;\n"
 	      "	NewIfcObj parent;\n"
@@ -413,6 +445,8 @@ main(int argc, char **argv)
 	      "	size_t handlers_sz;\n"
 	      "	uint32_t fill_character_colour;\n"
 	      "	uint32_t fill_colour;\n"
+	      "	uint32_t fg_colour;\n"
+	      "	uint32_t bg_colour;\n"
 	      "	enum NewIfc_object type;\n"
 	      "	NI_err last_error;\n"
 	      "	uint16_t height;\n"
@@ -427,6 +461,7 @@ main(int argc, char **argv)
 	      "	uint16_t child_ypos;\n"
 	      "	uint8_t fill_character;\n"
 	      "	uint8_t fill_font;\n"
+	      "	uint8_t font;\n"
 	      "	unsigned focus:1;\n"
 	      "	unsigned hidden:1;\n"
 	      "};\n\n", internal_header);
@@ -454,9 +489,11 @@ main(int argc, char **argv)
 	fputs("#include \"newifc.h\"\n", c_code);
 	fputs("#include \"newifc_internal.h\"\n", c_code);
 	fputs("#include \"internal_macros.h\"\n", c_code);
+	fputs("\n", c_code);
+
 	nitems = sizeof(objtypes) / sizeof(objtypes[0]);
 	for (i = 0; i < nitems; i++) {
-		fprintf(c_code, "#include \"%s.c\"\n", objtypes[i].name);
+		fprintf(c_code, "static NI_err NewIFC_%s(NewIfcObj parent, NewIfcObj *newobj);\n", objtypes[i].name);
 	}
 	fputs("\n", c_code);
 
@@ -484,7 +521,7 @@ main(int argc, char **argv)
 			      "				break;\n"
 			      "			}\n", c_code);
 		}
-		fprintf(c_code, "			ret = NewIFC_%s(newobj);\n"
+		fprintf(c_code, "			ret = NewIFC_%s(parent, newobj);\n"
 		                "			if (ret != NewIfc_error_none) {\n"
 		                "				break;\n"
 		                "			}\n"
@@ -517,6 +554,17 @@ main(int argc, char **argv)
 
 	fputs("#include \"newifc_nongen.c\"\n\n", c_code);
 
+	fputs("static NI_err\n"
+	      "NI_copy_globals(NewIfcObj dst, NewIfcObj src)\n"
+	      "{\n", c_code);
+	nitems = sizeof(attributes) / sizeof(attributes[0]);
+	for (i = 0; i < nitems; i++) {
+		if (attributes[i].impl == attr_impl_global || attributes[i].impl == attr_impl_global_custom_setter)
+			fprintf(c_code,"	dst->%s = src->%s;\n", attributes[i].name, attributes[i].name);
+	}
+	fputs("	return NewIfc_error_none;\n"
+	      "}\n\n", c_code);
+
 	nitems = sizeof(type_str) / sizeof(type_str[0]);
 	for (i = 0; i < nitems; i++) {
 		fprintf(c_code, "NI_err\n"
@@ -549,6 +597,11 @@ main(int argc, char **argv)
 	}
 
 	fputs("#include \"newifc_nongen_after.c\"\n\n", c_code);
+	nitems = sizeof(objtypes) / sizeof(objtypes[0]);
+	for (i = 0; i < nitems; i++) {
+		fprintf(c_code, "#include \"%s.c\"\n", objtypes[i].name);
+	}
+	fputs("\n", c_code);
 
 	fclose(c_code);
 }
diff --git a/src/newifc/internal_macros.h b/src/newifc/internal_macros.h
index d8eb769e83fc947a20128c0c1cd183dfec31f815..aa9481babfd918cf14347aace4c0cd4fd986c7eb 100644
--- a/src/newifc/internal_macros.h
+++ b/src/newifc/internal_macros.h
@@ -17,7 +17,7 @@
 	st->field = va_arg(ap, uint16_t); \
 } while(0);
 
-#define SET_STRING(st, field, sz_field) do {                       \
+#define SET_STRING(st, field) do {                                 \
 	buf = strdup(va_arg(ap, const char *));                     \
 	if (buf == NULL) {                                           \
 		st->api.last_error = NewIfc_error_allocation_failure; \
@@ -25,7 +25,6 @@
 	}                                                               \
 	free(st->field);                                                 \
 	st->field = buf;                                                  \
-	st->sz_field = strlen(st->field);                                  \
 } while(0)
 
 
diff --git a/src/newifc/label.c b/src/newifc/label.c
new file mode 100644
index 0000000000000000000000000000000000000000..a6c17b29b34ea413e7aa01d86bf67a2de8065e93
--- /dev/null
+++ b/src/newifc/label.c
@@ -0,0 +1,190 @@
+#include <assert.h>
+#include <stdarg.h>
+#include <stdlib.h>  // malloc()/free()
+
+#define CIOLIB_NO_MACROS
+#include "ciolib.h"
+#include "genwrap.h"
+#include "strwrap.h"
+#include "threadwrap.h"
+
+#include "newifc.h"
+#include "newifc_internal.h"
+
+#include "internal_macros.h"
+
+#ifdef BUILD_TESTS
+#include "CuTest.h"
+#endif
+
+struct label {
+	struct newifc_api api;
+	char *text;
+};
+
+static NI_err
+label_set(NewIfcObj obj, int attr, ...)
+{
+	struct label *l = (struct label *)obj;
+	SET_VARS;
+
+	l->api.last_error = NewIfc_error_none;
+	va_start(ap, attr);
+	switch (attr) {
+		case NewIfc_text:
+			SET_STRING(l, text);
+			break;
+		default:
+			l->api.last_error = NewIfc_error_not_implemented;
+			break;
+	}
+	va_end(ap);
+
+	return l->api.last_error;
+}
+
+static NI_err
+label_get(NewIfcObj obj, int attr, ...)
+{
+	struct label *l = (struct label *)obj;
+	GET_VARS;
+
+	l->api.last_error = NewIfc_error_none;
+	va_start(ap, attr);
+	switch (attr) {
+		case NewIfc_text:
+			GET_STRING(l, text);
+			break;
+		default:
+			l->api.last_error = NewIfc_error_not_implemented;
+			break;
+	}
+
+	return l->api.last_error;
+}
+
+static NI_err
+label_copy(NewIfcObj old, NewIfcObj *newobj)
+{
+	struct label **newl = (struct label **)newobj;
+	struct label *oldl = (struct label *)old;
+
+	*newl = malloc(sizeof(struct label));
+	if (*newl == NULL)
+		return NewIfc_error_allocation_failure;
+	memcpy(*newl, oldl, sizeof(struct label));
+	assert(oldl->text);
+	(*newl)->text = strdup(oldl->text);
+	if ((*newl)->text == NULL) {
+		free(*newl);
+		return NewIfc_error_allocation_failure;
+	}
+
+	return NewIfc_error_none;
+}
+
+static NI_err
+label_do_render(NewIfcObj obj, struct NewIfc_render_context *ctx)
+{
+	size_t sz = 0;
+	struct label *l = (struct label *) obj;
+	if (l->text)
+		sz = strlen(l->text);
+	// TODO: Ensure text fits...
+	size_t c = ctx->ypos * ctx->dwidth + ctx->xpos;
+	for (size_t i = 0; i < sz; i++, c++) {
+		if (obj->bg_colour != NI_TRANSPARENT)
+			ctx->vmem[c].bg = obj->bg_colour;
+		if (obj->fg_colour != NI_TRANSPARENT) {
+			ctx->vmem[c].fg = obj->fg_colour;
+			ctx->vmem[c].font = obj->font;
+			ctx->vmem[c].ch = l->text[i];
+		}
+	}
+	return NewIfc_error_none;
+}
+
+static NI_err
+NewIFC_label(NewIfcObj parent, NewIfcObj *newobj)
+{
+	struct label **newl = (struct label **)newobj;
+
+	if (parent == NULL)
+		return NewIfc_error_invalid_arg;
+	*newl = calloc(1, sizeof(struct label));
+
+	if (*newl == NULL)
+		return NewIfc_error_allocation_failure;
+	(*newl)->text = strdup("");
+	if ((*newl)->text == NULL) {
+		free(*newl);
+		return NewIfc_error_allocation_failure;
+	}
+	NI_copy_globals(&((*newl)->api), parent);
+	(*newl)->api.get = &label_get;
+	(*newl)->api.set = &label_set;
+	(*newl)->api.copy = &label_copy;
+	(*newl)->api.do_render = &label_do_render;
+	(*newl)->api.last_error = NewIfc_error_none;
+	(*newl)->api.child_ypos = 0;
+	(*newl)->api.child_xpos = 0;
+	(*newl)->api.child_width = 0;
+	(*newl)->api.child_height = 0;
+	// TODO: This is only needed by the unit tests...
+	(*newl)->api.root = parent->root;
+	parent->bottomchild = parent->topchild = (NewIfcObj)*newl;
+	(*newl)->api.root = parent->root;
+	(*newl)->api.parent = parent;
+	(*newl)->api.width = 0;
+	(*newl)->api.height = 1;
+	(*newl)->api.min_width = 0;
+	(*newl)->api.min_height = 1;
+	return NewIfc_error_none;
+}
+
+#ifdef BUILD_TESTS
+
+void test_label(CuTest *ct)
+{
+	bool b;
+	char *s;
+	NewIfcObj obj;
+	NewIfcObj robj;
+	static const char *new_title = "New Title";
+	struct vmem_cell cells;
+
+	CuAssertTrue(ct, NewIFC_root_window(NULL, &robj) == NewIfc_error_none);
+	CuAssertTrue(ct, NewIFC_label(robj, &obj) == NewIfc_error_none);
+	CuAssertPtrNotNull(ct, obj);
+	CuAssertPtrNotNull(ct, obj->get);
+	CuAssertPtrNotNull(ct, obj->set);
+	CuAssertPtrNotNull(ct, obj->copy);
+	CuAssertTrue(ct, obj->last_error == NewIfc_error_none);
+	CuAssertTrue(ct, obj->width == 0);
+	CuAssertTrue(ct, obj->height == 1);
+	CuAssertTrue(ct, obj->min_width == 0);
+	CuAssertTrue(ct, obj->min_height == 1);
+	CuAssertTrue(ct, obj->last_error == NewIfc_error_none);
+	CuAssertTrue(ct, obj->focus == true);
+	CuAssertTrue(ct, obj->get(obj, NewIfc_text, &s) == NewIfc_error_none && strcmp(s, "") == 0);
+	CuAssertTrue(ct, obj->last_error == NewIfc_error_none);
+
+	CuAssertTrue(ct, obj->last_error == NewIfc_error_none);
+	CuAssertTrue(ct, obj->set(obj, NewIfc_text, new_title) == NewIfc_error_none);
+	CuAssertTrue(ct, obj->last_error == NewIfc_error_none);
+
+	CuAssertTrue(ct, obj->get(obj, NewIfc_text, &s) == NewIfc_error_none && strcmp(s, new_title) == 0);
+	CuAssertTrue(ct, obj->last_error == NewIfc_error_none);
+
+	CuAssertTrue(ct, robj->do_render(robj, NULL) == NewIfc_error_none);
+	size_t sz = strlen(new_title);
+	for (size_t i = 0; i < sz; i++) {
+		ciolib_vmem_gettext(i+1,1,i+1,1,&cells);
+		CuAssertTrue(ct, cells.ch == new_title[i]);
+		CuAssertTrue(ct, cells.fg == obj->fg_colour);
+		CuAssertTrue(ct, cells.bg == obj->bg_colour);
+		CuAssertTrue(ct, cells.font == obj->font);
+	}
+}
+
+#endif
diff --git a/src/newifc/newifc_nongen.c b/src/newifc/newifc_nongen.c
index f07ba84fce676c03bec4bad3612a1918ac9ad5f2..b3a45db2759611831664f70ae5f84fbe4baa0794 100644
--- a/src/newifc/newifc_nongen.c
+++ b/src/newifc/newifc_nongen.c
@@ -1,3 +1,5 @@
+#include <stdlib.h>
+
 #include "newifc.h"
 
 NI_err
diff --git a/src/newifc/root_window.c b/src/newifc/root_window.c
index 81d284ac219a9ed50009021d1313950c047bce3f..45717554d5dd1eaa2551831a0ba5d06328933505 100644
--- a/src/newifc/root_window.c
+++ b/src/newifc/root_window.c
@@ -24,7 +24,6 @@ struct root_window {
 	struct vmem_cell *display;
 	pthread_mutex_t mtx;
 	unsigned locks;
-	unsigned dirty:1;
 };
 
 struct rw_recalc_child_cb_params {
@@ -146,31 +145,110 @@ rw_copy(NewIfcObj old, NewIfcObj *newobj)
 }
 
 static NI_err
-rw_render(NewIfcObj obj, uint16_t xpos, uint16_t ypos, uint16_t width, uint16_t height)
+call_render_handlers(NewIfcObj obj)
 {
-	struct root_window *rw = (struct root_window *)obj;
-	size_t sz = rw->api.width;
-	sz *= rw->api.height;
-	if (rw->api.fill_colour != NI_TRANSPARENT || rw->api.fill_character_colour != NI_TRANSPARENT) {
-		for (size_t c = 0; c < sz; c++) {
-			// TODO: No way to generate legacy attribute from colour.
-			if (rw->api.fill_colour != NI_TRANSPARENT)
-				rw->display[c].bg = rw->api.fill_colour;
-			if (rw->api.fill_character_colour != NI_TRANSPARENT) {
-				rw->display[c].fg = rw->api.fill_character_colour;
-				rw->display[c].ch = rw->api.fill_character;
-				if (ciolib_checkfont(rw->api.fill_font))
-					rw->display[c].font = rw->api.fill_font;
+	enum NewIfc_event_handlers hval = NewIfc_on_render;
+	if (obj->handlers == NULL)
+		return NewIfc_error_none;
+	struct NewIfc_handler **head = bsearch(&hval, obj->handlers, obj->handlers_sz, sizeof(obj->handlers[0]), handler_bsearch_compar);
+	if (head == NULL)
+		return NewIfc_error_none;
+	struct NewIfc_handler *h = *head;
+	while (h != NULL) {
+		NI_err ret = h->on_render(obj, h->cbdata);
+		if (ret != NewIfc_error_none)
+			return ret;
+		h = h->next;
+	}
+	return NewIfc_error_none;
+}
+
+static NI_err
+rw_do_render_recurse(NewIfcObj obj, struct NewIfc_render_context *ctx)
+{
+	int16_t oxpos, oypos, owidth, oheight;
+	NI_err ret = call_render_handlers(obj);
+	if (ret != NewIfc_error_none)
+		return ret;
+
+	// Fill background
+	if (obj->fill_colour != NI_TRANSPARENT || obj->fill_character_colour != NI_TRANSPARENT) {
+		size_t c;
+		for (size_t y = 0; y < obj->child_height; y++) {
+			c = (y + ctx->ypos + obj->child_ypos) * ctx->dwidth;
+			for (size_t x = 0; x < obj->child_width; x++) {
+				if (obj->fill_colour != NI_TRANSPARENT)
+					ctx->vmem[c].bg = obj->fill_colour;
+				if (obj->fill_character_colour != NI_TRANSPARENT) {
+					ctx->vmem[c].fg = obj->fill_character_colour;
+					ctx->vmem[c].ch = obj->fill_character;
+					if (ciolib_checkfont(obj->fill_font))
+						ctx->vmem[c].font = obj->fill_font;
+				}
 			}
 		}
 	}
+	if (obj->root != obj)
+		obj->do_render(obj, ctx);
+
+	owidth = ctx->width;
+	oheight = ctx->height;
+	oxpos = ctx->xpos;
+	oypos = ctx->ypos;
+	ctx->width = obj->child_width;
+	ctx->height = obj->child_height;
+	assert(ctx->xpos >= obj->xpos);
+	ctx->xpos -= obj->xpos;
+	assert(ctx->ypos >= obj->ypos);
+	ctx->ypos -= obj->ypos;
+
+	// Recurse children
+	if (obj->bottomchild) {
+		ret = rw_do_render_recurse(obj->bottomchild, ctx);
+		if (ret != NewIfc_error_none)
+			return ret;
+	}
+	ctx->width = owidth;
+	ctx->height = oheight;
+	ctx->xpos = oxpos;
+	ctx->ypos = oypos;
+	// Recurse peers
+	if (obj->higherpeer) {
+		ret = rw_do_render_recurse(obj->higherpeer, ctx);
+		if (ret != NewIfc_error_none)
+			return ret;
+	}
+	return NewIfc_error_none;
+}
+
+static NI_err
+rw_do_render(NewIfcObj obj, struct NewIfc_render_context *nullctx)
+{
+	struct root_window *rw = (struct root_window *) obj;
+	struct NewIfc_render_context ctx = {
+		.vmem = rw->display,
+		.dwidth = obj->width,
+		.dheight = obj->height,
+		.width = obj->width,
+		.height = obj->height,
+		.xpos = 0,
+		.ypos = 0,
+	};
+	NI_err ret = rw_do_render_recurse(obj, &ctx);
+	if (ret == NewIfc_error_none) {
+		struct root_window *rw = (struct root_window *)obj;
+		ciolib_vmem_puttext(obj->xpos + 1, obj->ypos + 1, obj->width + obj->xpos, obj->height + obj->ypos, rw->display);
+	}
+	return ret;
 }
 
 static NI_err
-NewIFC_root_window(NewIfcObj *newobj)
+NewIFC_root_window(NewIfcObj parent, NewIfcObj *newobj)
 {
 	struct root_window **newrw = (struct root_window **)newobj;
 
+	if (parent != NULL)
+		return NewIfc_error_invalid_arg;
 	*newrw = calloc(1, sizeof(struct root_window));
 
 	if (*newrw == NULL)
@@ -183,6 +261,10 @@ NewIFC_root_window(NewIfcObj *newobj)
 	// TODO: This is only needed by the unit tests...
 	(*newrw)->api.root = *newobj;
 	(*newrw)->api.focus = true;
+	(*newrw)->api.do_render = &rw_do_render;
+	(*newrw)->api.fg_colour = NI_LIGHTGRAY;
+	(*newrw)->api.bg_colour = NI_BLACK;
+	(*newrw)->api.font = 0;
 	(*newrw)->mtx = pthread_mutex_initializer_np(true);
 
 	struct text_info ti;
@@ -219,7 +301,7 @@ void test_root_window(CuTest *ct)
 	NewIfcObj obj;
 	static const char *new_title = "New Title";
 
-	CuAssertTrue(ct, !NewIFC_root_window(&obj));
+	CuAssertTrue(ct, NewIFC_root_window(NULL, &obj) == NewIfc_error_none);
 	CuAssertPtrNotNull(ct, obj);
 	CuAssertPtrNotNull(ct, obj->get);
 	CuAssertPtrNotNull(ct, obj->set);
@@ -235,6 +317,9 @@ void test_root_window(CuTest *ct)
 	CuAssertTrue(ct, obj->last_error == NewIfc_error_none);
 	CuAssertTrue(ct, obj->get(obj, NewIfc_locked_by_me, &b) == NewIfc_error_none && !b);
 	CuAssertTrue(ct, obj->last_error == NewIfc_error_none);
+	CuAssertTrue(ct, obj->fg_colour == NI_LIGHTGRAY);
+	CuAssertTrue(ct, obj->bg_colour == NI_BLACK);
+	CuAssertTrue(ct, obj->font == 0);
 
 	CuAssertTrue(ct, obj->last_error == NewIfc_error_none);
 	CuAssertTrue(ct, obj->set(obj, NewIfc_locked, false) == NewIfc_error_lock_failed);
@@ -254,12 +339,8 @@ void test_root_window(CuTest *ct)
 	CuAssertTrue(ct, obj->last_error == NewIfc_error_lock_failed);
 	CuAssertTrue(ct, obj->get(obj, NewIfc_locked_by_me, &b) == NewIfc_error_none && !b);
 	CuAssertTrue(ct, obj->last_error == NewIfc_error_none);
-}
 
-CuSuite* root_window_get_test_suite() {
-	CuSuite* suite = CuSuiteNew();
-	SUITE_ADD_TEST(suite, test_root_window);
-	return suite;
+	CuAssertTrue(ct, obj->do_render(obj, NULL) == NewIfc_error_none);
 }
 
 #endif