yterm

commit 48017b499ce6588381a0856302a5ed8d3643e06a

Author: mesyeti <mesyeti@mesyeti.uk>

terminal read working

 Makefile | 5 
 source/app.c | 48 +++++++++++
 source/app.h | 5 +
 source/backends/soft.c | 6 
 source/buffer.c | 64 ++++++++++++++
 source/buffer.h | 8 +
 source/event.c | 27 ++++-
 source/event.h | 4 
 source/pane.c | 57 +++++++++++++
 source/pane.h | 33 +++++++
 source/screen.c | 1 
 source/sysTerm.h | 20 ++++
 source/sysTerm/unix.c | 188 ++++++++++++++++++++++++++++++++++++++++++++
 source/sysTerm/unix.h | 9 ++
 source/terminal.c | 51 +++++++++++
 source/terminal.h | 19 ++++
 source/util.c | 8 +
 source/util.h | 1 


diff --git a/Makefile b/Makefile
index 06b43e87ef90af4e127b7a56d2987c0f2369a0b9..f4f53aec95303251c4b14910b02a489ccfda6a5a 100644
--- a/Makefile
+++ b/Makefile
@@ -77,7 +77,10 @@
 bin/platform:
 	mkdir -p bin/platform
 
-bin/%.o: source/%.c $(call deps,source/%.c) | bin/ bin/backends bin/input bin/window bin/event bin/platform
+bin/sysTerm:
+	mkdir -p bin/sysTerm
+
+bin/%.o: source/%.c $(call deps,source/%.c) | bin/ bin/backends bin/input bin/window bin/event bin/platform bin/sysTerm
 	$(CC) $(CFLAGS) $(CPPFLAGS) $< -c -o $@
 
 clean:




diff --git a/source/app.c b/source/app.c
index 70b77c69916aeb86d9e11db60bf5fe882c7011b8..f7365e829e0fddf9236db61e4e9ad3f9f30ea031 100644
--- a/source/app.c
+++ b/source/app.c
@@ -1,4 +1,7 @@
+#include <time.h>
 #include "app.h"
+#include "log.h"
+#include "mem.h"
 #include "event.h"
 #include "screen.h"
 #include "backend.h"
@@ -12,6 +15,17 @@ 	Event_Init();
 	Window_Init();
 	Video_Init("yterm");
 	Screen_Init();
+
+	bool success;
+
+	app.tabs    = SafeMalloc(sizeof(Pane));
+	app.tabNum  = 1;
+	app.tabSel  = 0;
+	app.tabs[0] = Pane_NewRootPane(&success);
+
+	if (!success) {
+		Error("Failed to start terminal");
+	}
 }
 
 void App_Update(void) {
@@ -30,7 +44,41 @@ 			default:         continue;
 		}
 	}
 
+	// update tabs
+	for (size_t i = 0; i < app.tabNum; ++ i) {
+		Pane_Update(&app.tabs[i]);
+	}
+
 	// render
+	Buffer_Clear(&screen.buffer);
+	BufCell bar;
+	bar.ch          = ' ';
+	bar.attr.fgMode = BUF_DEFAULT;
+	bar.attr.bgMode = BUF_16COL;
+	bar.attr.bg.idx = 0x8; // grey
+
+	Buffer_HLine(&screen.buffer, 0, 0, screen.buffer.width, bar);
+
+	char info[32];
+	snprintf(info, sizeof(info), "yterm - [%d/%d]", (int) app.tabSel + 1, (int) app.tabNum);
+	Buffer_Print(&screen.buffer, 0, 0, bar.attr, info);
+
+	// clock
+	time_t now = time(NULL);
+	if (now != -1) {
+		struct tm* timeInfo = localtime(&now);
+
+		char time[32];
+		snprintf(
+			time, sizeof(time), "%.2d:%.2d:%.2d", timeInfo->tm_hour, timeInfo->tm_min,
+			timeInfo->tm_sec
+		);
+
+		Buffer_Print(&screen.buffer, screen.buffer.width - strlen(time), 0, bar.attr, time);
+	}
+
+	Rect within = {0, 1, screen.buffer.width, screen.buffer.height - 1};
+	Pane_Render(&app.tabs[app.tabSel], within);
 
 	Backend_Render();
 	oldFrameTime = newFrameTime;




diff --git a/source/app.h b/source/app.h
index 28816ffff0c2827cd98044db089f436ef1aa6c8a..003d93cb76a631f71806339a6b18763d310b529c 100644
--- a/source/app.h
+++ b/source/app.h
@@ -1,6 +1,7 @@
 #ifndef YT_APP_H
 #define YT_APP_H
 
+#include "pane.h"
 #include "video.h"
 #include "common.h"
 #include "platform.h"
@@ -8,6 +9,10 @@
 typedef struct {
 	bool  running;
 	float delta;
+
+	Pane*  tabs;
+	size_t tabNum;
+	size_t tabSel;
 } App;
 
 extern App app;




diff --git a/source/backends/soft.c b/source/backends/soft.c
index 470294f21cff22ccb5d5b667ae9b7024085af219..508de517c1fb5eeb600ced53aeac8cc04a5de0e9 100644
--- a/source/backends/soft.c
+++ b/source/backends/soft.c
@@ -203,8 +203,10 @@ 			int cy = y * ch;
 
 			BufCell* cell = Buffer_Get(&screen.buffer, x, y);
 
-			ColourRGB fg = Screen_GetColour(true,  cell->attr.fgMode, cell->attr.fg);
-			ColourRGB bg = Screen_GetColour(false, cell->attr.bgMode, cell->attr.bg);
+			bool isFG = !cell->attr.invert;
+
+			ColourRGB fg = Screen_GetColour(isFG,  cell->attr.fgMode, cell->attr.fg);
+			ColourRGB bg = Screen_GetColour(!isFG, cell->attr.bgMode, cell->attr.bg);
 
 			FillRect(cx, cy, cw, ch, (Colour) {bg.r, bg.g, bg.b, 255});
 




diff --git a/source/buffer.c b/source/buffer.c
index 0fad27ebab72086dd675c34a6312e1480d20b2ef..ebf1c839d295a74f3a3bd20de5deb5f04398ea66 100644
--- a/source/buffer.c
+++ b/source/buffer.c
@@ -1,11 +1,19 @@
 #include "mem.h"
 #include "buffer.h"
+#include "backend.h"
+
+BufAttr BufAttr_Default(void) {
+	return (BufAttr) {
+		.fgMode = BUF_DEFAULT,
+		.bgMode = BUF_DEFAULT,
+		.invert = false
+	};
+}
 
 static BufCell FromChar(char ch) {
 	BufCell ret;
-	ret.ch          = ch;
-	ret.attr.fgMode = BUF_DEFAULT;
-	ret.attr.bgMode = BUF_DEFAULT;
+	ret.ch   = ch;
+	ret.attr = BufAttr_Default();
 	return ret;
 }
 
@@ -37,6 +45,7 @@ bool Buffer_CellEq(BufCell a, BufCell b) {
 	if (a.ch          != b.ch)          return false;
 	if (a.attr.fgMode != b.attr.fgMode) return false;
 	if (a.attr.bgMode != b.attr.bgMode) return false;
+	if (a.attr.invert != b.attr.invert) return false;
 
 	switch (a.attr.fgMode) {
 		case BUF_DEFAULT: break;
@@ -63,6 +72,44 @@
 	return &buf->buf[(y * buf->width) + x];
 }
 
+void Buffer_Set(Buffer* buf, int x, int y, BufCell cell) {
+	BufCell* ptr = Buffer_Get(buf, x, y);
+
+	if (!ptr) return;
+
+	bool change = !Buffer_CellEq(*ptr, cell);
+
+	*ptr = cell;
+
+	if (buf->isScreenBuf && change) {
+		Backend_OnCellChange(x, y);
+	}
+}
+
+void Buffer_InvertCell(Buffer* buf, int x, int y) {
+	BufCell* cell = Buffer_Get(buf, x, y);
+
+	if (!cell) return;
+
+	cell->attr.invert = !cell->attr.invert;
+
+	if (buf->isScreenBuf) {
+		Backend_OnCellChange(x, y);
+	}
+}
+
+void Buffer_Clear(Buffer* buf) {
+	for (int i = 0; i < buf->width * buf->height; ++ i) {
+		buf->buf[i] = FromChar(0);
+	}
+}
+
+void Buffer_Print(Buffer* buf, int x, int y, BufAttr attr, const char* str) {
+	for (int i = 0; str[i] != 0; ++ i) {
+		Buffer_Set(buf, x + i, y, (BufCell) {(UChar) str[i], attr});
+	}
+}
+
 void Buffer_Blit(Buffer* dest, Buffer* src, int x, int y, Rect clip) {
 	for (int iy = 0; iy < clip.h; ++ iy) {
 		for (int ix = 0; ix < clip.w; ++ ix) {
@@ -72,7 +119,18 @@ 			int sx = ix + clip.x;
 			int sy = iy + clip.y;
 
 			BufCell* cell = Buffer_Get(src, sx, sy);
+
+			if (!Buffer_CellEq(*Buffer_Get(dest, x + ix, y + iy), *cell) && dest->isScreenBuf) {
+				Backend_OnCellChange(x + ix, y + iy);
+			}
+
 			*Buffer_Get(dest, x + ix, y + iy) = *cell;
 		}
 	}
 }
+
+void Buffer_HLine(Buffer* buf, int x, int y, int len, BufCell cell) {
+	for (int i = 0; i < len; ++ i) {
+		Buffer_Set(buf, x + i, y, cell);
+	}
+}




diff --git a/source/buffer.h b/source/buffer.h
index 5f0c1adce15c9d2d328d14b431fa89975cc7ae0d..bfa2934a736b262bc1b474e2d6356df951a81d16 100644
--- a/source/buffer.h
+++ b/source/buffer.h
@@ -22,6 +22,7 @@ 	uint8_t fgMode;
 	uint8_t bgMode;
 	BufCol  fg;
 	BufCol  bg;
+	bool    invert;
 } BufAttr;
 
 typedef struct {
@@ -36,11 +37,18 @@ 	int      height;
 	bool     isScreenBuf;
 } Buffer;
 
+BufAttr BufAttr_Default(void);
+
 Buffer   Buffer_New(int width, int height);
 void     Buffer_Free(Buffer* buf);
 void     Buffer_WriteChar(Buffer* buf, UChar ch, int x, int y);
 bool     Buffer_CellEq(BufCell a, BufCell b);
 BufCell* Buffer_Get(Buffer* buf, int x, int y);
+void     Buffer_Set(Buffer* buf, int x, int y, BufCell cell);
+void     Buffer_InvertCell(Buffer* buf, int x, int y);
+void     Buffer_Clear(Buffer* buf);
+void     Buffer_Print(Buffer* buf, int x, int y, BufAttr attr, const char* str);
 void     Buffer_Blit(Buffer* dest, Buffer* src, int x, int y, Rect clip);
+void     Buffer_HLine(Buffer* buf, int x, int y, int len, BufCell cell);
 
 #endif




diff --git a/source/event.c b/source/event.c
index c6a3f127f6874c39494c31991943d5fc6372468a..0d46fddfb6b51b07497c99f4b1cbc3476e90d02f 100644
--- a/source/event.c
+++ b/source/event.c
@@ -4,7 +4,8 @@ #include "util.h"
 #include "event.h"
 #include "input/sdl.h"
 
-Event events[EVENTS_AMOUNT];
+Event* events;
+size_t eventsSize;
 
 typedef struct {
 	Event_Type    type;
@@ -15,7 +16,10 @@ static Handler* handlers   = NULL;
 static size_t   handlerNum = 0;
 
 void Event_Init(void) {
-	for (size_t i = 0; i < EVENTS_AMOUNT; ++ i) {
+	eventsSize = 32;
+	events     = SafeMalloc(32 * sizeof(Event));
+
+	for (size_t i = 0; i < eventsSize; ++ i) {
 		events[i].type = EVENT_NONE;
 	}
 }
@@ -29,16 +33,23 @@
 static int FindFree(void) {
 	int i;
 
-	for (i = 0; i < EVENTS_AMOUNT; ++ i) {
+	for (i = 0; i < eventsSize; ++ i) {
 		if (events[i].type == EVENT_NONE) return i;
 	}
 
-	for (i = 0; i < EVENTS_AMOUNT; ++ i) {
+	for (i = 0; i < eventsSize; ++ i) {
 		printf("Event %d: %d\n", i, (int) events[i].type);
 	}
 
-	Error("Event pool full");
-	return -1;
+	int old     = eventsSize;
+	events      = SafeRealloc(events, eventsSize * 2 * sizeof(Event));
+	eventsSize *= 2;
+
+	for (int i = old; i < eventsSize; ++ i) {
+		events[i].type = EVENT_NONE;
+	}
+
+	return old;
 }
 
 void Event_Add(Event e) {
@@ -61,7 +72,7 @@ 	}
 }
 
 bool Event_Available(void) {
-	for (size_t i = 0; i < EVENTS_AMOUNT; ++ i) {
+	for (size_t i = 0; i < eventsSize; ++ i) {
 		if (events[i].type != EVENT_NONE) {
 			return true;
 		}
@@ -75,7 +86,7 @@ 	if (!Event_Available()) {
 		Event_Update();
 	}
 
-	for (size_t i = 0; i < EVENTS_AMOUNT; ++ i) {
+	for (size_t i = 0; i < eventsSize; ++ i) {
 		if (events[i].type != EVENT_NONE) {
 			*e             = events[i];
 			events[i].type = EVENT_NONE;




diff --git a/source/event.h b/source/event.h
index b32e58db315dca59e76f5fa6a3912f7808818483..0882518027eaba14daeae24780aac4164687e5d6 100644
--- a/source/event.h
+++ b/source/event.h
@@ -61,8 +61,8 @@ 	Event_WindowResize windowResize;
 	Event_TextInput    textInput;
 } Event;
 
-#define EVENTS_AMOUNT 32
-extern Event events[EVENTS_AMOUNT];
+extern Event* events;
+extern size_t eventsSize;
 
 typedef void (*Event_Handler)(Event* event);
 




diff --git a/source/pane.c b/source/pane.c
new file mode 100644
index 0000000000000000000000000000000000000000..13c465868946c8f3d512734620acd47233b5e022
--- /dev/null
+++ b/source/pane.c
@@ -0,0 +1,57 @@
+#include "pane.h"
+#include "screen.h"
+
+Pane Pane_NewRootPane(bool* success) {
+	Pane ret;
+	ret.type       = PANE_TERMINAL;
+	ret.v.terminal = Terminal_New(screen.buffer.width, screen.buffer.height - 1, success);
+	return ret;
+}
+
+void Pane_Free(Pane* pane) {
+	switch (pane->type) {
+		case PANE_TERMINAL: {
+			Terminal_Free(&pane->v.terminal);
+			break;
+		}
+	}
+}
+
+void Pane_Update(Pane* pane) {
+	switch (pane->type) {
+		case PANE_SPLIT: {
+			if (pane->v.split.left)  Pane_Update(pane->v.split.left);
+			if (pane->v.split.right) Pane_Update(pane->v.split.right);
+			break;
+		}
+		case PANE_TERMINAL: {
+			Terminal_Update(&pane->v.terminal);
+			break;
+		}
+		default: assert(0);
+	}
+}
+
+void Pane_Render(Pane* pane, Rect within) {
+	switch (pane->type) {
+		case PANE_TERMINAL: {
+			Buffer* buf = &pane->v.terminal.buffer;
+			Rect    clip;
+			clip.x = 0;
+			clip.y = buf->height - within.h;
+			clip.w = within.w;
+			clip.h = within.h;
+
+			Vec2 cursorPos;
+			cursorPos.x = pane->v.terminal.cursor.x;
+			cursorPos.y = (pane->v.terminal.cursor.y - clip.y) + within.y;
+
+			Buffer_Blit(&screen.buffer, buf, within.x, within.y, clip);
+
+			// TODO: only render cursor in selected pane
+			Buffer_InvertCell(&screen.buffer, cursorPos.x, cursorPos.y);
+			break;
+		}
+		default: assert(0);
+	}
+}




diff --git a/source/pane.h b/source/pane.h
new file mode 100644
index 0000000000000000000000000000000000000000..0e6377dbe3272bb2ccf585244608b0ca3afbe11a
--- /dev/null
+++ b/source/pane.h
@@ -0,0 +1,33 @@
+#ifndef YT_PANE_H
+#define YT_PANE_H
+
+#include "terminal.h"
+
+typedef struct Pane Pane;
+
+enum {
+	PANE_TERMINAL = 0,
+	PANE_SPLIT
+};
+
+struct Pane {
+	int type;
+
+	union {
+		struct {
+			Pane* left;
+			Pane* right;
+			bool  horizontal;
+			float percent;
+		} split;
+
+		Terminal terminal;
+	} v;
+};
+
+Pane Pane_NewRootPane(bool* success);
+void Pane_Free(Pane* pane);
+void Pane_Update(Pane* pane);
+void Pane_Render(Pane* pane, Rect within);
+
+#endif




diff --git a/source/screen.c b/source/screen.c
index 271ac94133b21efb3794d6b992d1b265c83a392f..300c79c7cf5a6af81bbebea6a91ce5bab92fd708 100644
--- a/source/screen.c
+++ b/source/screen.c
@@ -25,6 +25,7 @@ 	int ch;
 	Font_CharSize(&screen.font, &cw, &ch);
 
 	screen.buffer = Buffer_New(video.windows[0].width / cw, video.windows[0].height / ch);
+	screen.buffer.isScreenBuf = true;
 
 	Backend_OnScreenUpdate();
 }




diff --git a/source/sysTerm/unix.c b/source/sysTerm/unix.c
new file mode 100644
index 0000000000000000000000000000000000000000..b887a3b3d58401a3252f92cf4668d702708ade2e
--- /dev/null
+++ b/source/sysTerm/unix.c
@@ -0,0 +1,188 @@
+#define _XOPEN_SOURCE 600
+#include "../common.h"
+
+#if defined(PLATFORM_UNIX) || defined(PLATFORM_LINUX)
+
+#include <pwd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include "../log.h"
+#include "../mem.h"
+#include "../util.h"
+#include "../sysTerm.h"
+
+#define TERM "xterm-16color"
+
+extern char** environ;
+
+static char* GetUserShell(void) {
+	struct passwd* data = getpwuid(geteuid());
+
+	if (data == NULL) {
+		Error("Failed to get user shell: %s", strerror(errno));
+	}
+
+	return data->pw_shell;
+}
+
+SysTerm SysTerm_New(bool* success, int w, int h) {
+	SysTerm ret;
+
+	ret.master = posix_openpt(O_RDWR | O_NOCTTY);
+	if (ret.master == -1) {
+		Log("posix_openpt failed: %s", strerror(errno));
+		*success = false;
+		return ret;
+	}
+
+	if (grantpt(ret.master) == -1) {
+		Log("grantpt failed: %s", strerror(errno));
+		*success = false;
+		return ret;
+	}
+
+	if (unlockpt(ret.master) == -1) {
+		Log("unlockpt failed: %s", strerror(errno));
+		*success = false;
+		return ret;
+	}
+
+	char* slaveName = ptsname(ret.master);
+	if (slaveName == NULL) {
+		Log("ptsname failed: %s", strerror(errno));
+		*success = false;
+		return ret;
+	}
+
+	ret.slave = open(slaveName, O_RDWR | O_NOCTTY);
+	if (ret.slave == -1) {
+		Log("open failed: %s", strerror(errno));
+		*success = false;
+		return ret;
+	}
+
+	SysTerm_UpdateSize(&ret, w, h);
+
+	// set up env
+	size_t envSize = 0;
+	for (size_t i = 0; environ[i] != NULL; ++ i) {
+		++ envSize;
+	}
+	char** envArray = (char**) SafeMalloc((envSize + 1) * sizeof(char*));
+
+	for (size_t i = 0; i <= envSize; ++ i) {
+		envArray[i] = environ[i];
+	}
+
+	bool termSet = false;
+	for (size_t i = 0; i < envSize; ++ i) {
+		if (StringStartsWith(envArray[i], "TERM=")) {
+			envArray[i] = "TERM=" TERM;
+			termSet     = true;
+		}
+	}
+
+	if (!termSet) {
+		envArray = (char**) SafeRealloc(envArray, (envSize + 2) * sizeof(char**));
+
+		envArray[envSize + 1] = NULL;
+		envArray[envSize]     = "TERM=" TERM;
+	}
+
+	pid_t p = fork();
+	if (p == 0) {
+		close(ret.master);
+
+		setsid();
+		if (ioctl(ret.slave, TIOCSCTTY, NULL) == -1) {
+			Log("ioctl failed: %s", strerror(errno));
+			*success = false;
+			return ret;
+		}
+
+		dup2(ret.slave, 0);
+		dup2(ret.slave, 1);
+		dup2(ret.slave, 2);
+		close(ret.slave);
+
+		char* command = GetUserShell();
+
+		//execle(SHELL, "-" SHELL, (char*) NULL, envArray);
+		if (!execle(command, command, (char*) NULL, envArray)) {
+			Log("Failed to start shell: %s", strerror(errno));
+			_exit(1);
+		}
+
+		free(envArray);
+		_exit(1); // TODO: make this close the tab or something
+	}
+	else if (p > 0) {
+		free(envArray);
+		close(ret.slave);
+
+		*success = true;
+		return ret;
+	}
+
+	*success = false;
+	Log("fork failed: %s", strerror(errno));
+	return ret;
+}
+
+void SysTerm_Free(SysTerm* term) {
+	(void) term;
+	// TODO
+}
+
+void SysTerm_UpdateSize(SysTerm* term, int w, int h) {
+	struct winsize ws = {
+		.ws_col = w,
+		.ws_row = h
+	};
+
+	if (ioctl(term->master, TIOCSWINSZ, &ws) == -1) {
+		Log("ioctl failed: %s", strerror(errno));
+	}
+}
+
+bool SysTerm_DataAvailable(SysTerm* term) {
+	struct timeval selectTimeout;
+	selectTimeout.tv_sec  = 0;
+	selectTimeout.tv_usec = 1;
+
+	fd_set readable;
+
+	FD_ZERO(&readable);
+	FD_SET(term->master, &readable);
+
+	if (select(term->master + 1, &readable, NULL, NULL, &selectTimeout) == -1) {
+		Log("select failed: %s", strerror(errno));
+		return false;
+	}
+
+	return FD_ISSET(term->master, &readable);
+}
+
+bool SysTerm_ReadByte(SysTerm* term, char* result) {
+	if (read(term->master, result, 1) <= 0) {
+		Log("read failed: %s", strerror(errno));
+		return false;
+	}
+
+	return true;
+}
+
+bool SysTerm_WriteByte(SysTerm* term, char val) {
+	if (write(term->master, &val, 1) <= 0) {
+		Log("write failed: %s", strerror(errno));
+		return false;
+	}
+
+	return true;
+}
+
+#endif




diff --git a/source/sysTerm/unix.h b/source/sysTerm/unix.h
new file mode 100644
index 0000000000000000000000000000000000000000..896e9528deb4c207f9a88a60cc31ea4fc62b19c3
--- /dev/null
+++ b/source/sysTerm/unix.h
@@ -0,0 +1,9 @@
+#ifndef YT_SYSTERM_UNIX_H
+#define YT_SYSTERM_UNIX_H
+
+typedef struct {
+	int master;
+	int slave;
+} SysTerm;
+
+#endif




diff --git a/source/sysTerm.h b/source/sysTerm.h
new file mode 100644
index 0000000000000000000000000000000000000000..c90e9456469de6b4b2e3bdc5cd16bd0608a17bd5
--- /dev/null
+++ b/source/sysTerm.h
@@ -0,0 +1,20 @@
+#ifndef YT_SYSTERM_H
+#define YT_SYSTERM_H
+
+#include "common.h"
+
+// defines SysTerm
+#if defined(PLATFORM_UNIX) || defined(PLATFORM_LINUX)
+	#include "sysTerm/unix.h"
+#else
+	#error "Unsupported"
+#endif
+
+SysTerm SysTerm_New(bool* success, int w, int h);
+void    SysTerm_Free(SysTerm* term);
+void    SysTerm_UpdateSize(SysTerm* term, int w, int h);
+bool    SysTerm_DataAvailable(SysTerm* term);
+bool    SysTerm_ReadByte(SysTerm* term, char* result);
+bool    SysTerm_WriteByte(SysTerm* term, char val);
+
+#endif




diff --git a/source/terminal.c b/source/terminal.c
new file mode 100644
index 0000000000000000000000000000000000000000..93c52df5fd7c2e0489a258d5303b409ace32ece4
--- /dev/null
+++ b/source/terminal.c
@@ -0,0 +1,51 @@
+#include "terminal.h"
+
+Terminal Terminal_New(int w, int h, bool* success) {
+	Terminal ret;
+	ret.cursor = (Vec2) {0, 0};
+	ret.attr   = BufAttr_Default();
+	ret.buffer = Buffer_New(w, h);
+
+	bool success2;
+	ret.term = SysTerm_New(&success2, w, h);
+
+	if (!success2) {
+		*success = false;
+		Buffer_Free(&ret.buffer);
+		return ret;
+	}
+
+	*success = true;
+	return ret;
+}
+
+void Terminal_Free(Terminal* terminal) {
+	Buffer_Free(&terminal->buffer);
+}
+
+void Terminal_Update(Terminal* terminal) {
+	while (SysTerm_DataAvailable(&terminal->term)) {
+		char ch = 0;
+		SysTerm_ReadByte(&terminal->term, &ch);
+
+		switch (ch) {
+			case '\r': {
+				terminal->cursor.x = 0;
+				break;
+			}
+			case '\n': {
+				++ terminal->cursor.y;
+				terminal->cursor.x = 0;
+				break;
+			}
+			default: {
+				BufCell cell;
+				cell.ch   = (UChar) ch;
+				cell.attr = terminal->attr;
+
+				Buffer_Set(&terminal->buffer, terminal->cursor.x, terminal->cursor.y, cell);
+				++ terminal->cursor.x;
+			}
+		}
+	}
+}




diff --git a/source/terminal.h b/source/terminal.h
new file mode 100644
index 0000000000000000000000000000000000000000..56db665967d275602765966cea723eba7d930b1e
--- /dev/null
+++ b/source/terminal.h
@@ -0,0 +1,19 @@
+#ifndef YT_TERMINAL_H
+#define YT_TERMINAL_H
+
+#include "types.h"
+#include "buffer.h"
+#include "sysTerm.h"
+
+typedef struct {
+	Vec2    cursor;
+	BufAttr attr;
+	Buffer  buffer;
+	SysTerm term;
+} Terminal;
+
+Terminal Terminal_New(int w, int h, bool* success);
+void     Terminal_Free(Terminal* terminal);
+void     Terminal_Update(Terminal* terminal);
+
+#endif




diff --git a/source/util.c b/source/util.c
index fadf402cb1db4cf9f705ed37a0562bfed606f6d8..2c0340b626cfac0ed4f9e980f8b19d252f616089 100644
--- a/source/util.c
+++ b/source/util.c
@@ -184,6 +184,14 @@
 	free(array);
 }
 
+bool StringStartsWith(char* str, char* with) {
+	if (strlen(with) > strlen(str)) {
+		return false;
+	}
+
+	return memcmp(str, with, strlen(with)) == 0;
+}
+
 const char* BaseName(const char* path) {
 	char* ret = strrchr(path, '/');
 




diff --git a/source/util.h b/source/util.h
index 359c5143f8c851ec23de79ac354f923d14a1ae73..0fd90269b294516520fc0097c6d43227fe6390a2 100644
--- a/source/util.h
+++ b/source/util.h
@@ -42,6 +42,7 @@ char** AppendStrArray(char** array, char* string);
 bool   StrArrayContains(char** array, char* string);
 size_t StrArrayFind(char** array, char* string);
 void   FreeStrArray(char** array);
+bool   StringStartsWith(char* str, char* with);
 
 const char* BaseName(const char* path);