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);