vtgl

terminal emulator implemented in OpenGL
git clone anongit@rnpnr.xyz:vtgl.git
Log | Files | Refs | Feed | LICENSE

Commit: 15356a94e46add94d999b8a9fa13eb0dae1eecca
Parent: ad6e8ba279987b30591c6bd5948837b438c995da
Author: Randy Palamar
Date:   Sun, 23 Jun 2024 11:49:34 -0600

spawn the child process and read its output

Diffstat:
Mmain.c | 5+++++
Mos_unix.c | 149++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mutil.h | 28++++++++++++++++++++--------
Mvtgl.c | 31+++++++++++++++++++++++++++----
4 files changed, 200 insertions(+), 13 deletions(-)

diff --git a/main.c b/main.c @@ -6,6 +6,8 @@ #include "util.h" +#define BACKLOG_SIZE (16 * MEGABYTE) + #ifndef _DEBUG static void do_debug(void) { } #else @@ -240,6 +242,9 @@ main(void) init_window(&term); init_fonts(&term, font_paths, ARRAY_COUNT(font_paths), 64, &memory); + os_alloc_ring_buffer(&term.log, BACKLOG_SIZE); + term.child = os_fork_child("/bin/sh"); + f32 last_time = 0; while (!glfwWindowShouldClose(term.gl.window)) { do_debug(&term.gl); diff --git a/os_unix.c b/os_unix.c @@ -1,15 +1,25 @@ /* See LICENSE for copyright details */ #include <fcntl.h> +#include <pty.h> +#include <pwd.h> #include <sys/mman.h> +#include <sys/select.h> #include <sys/stat.h> +#include <sys/wait.h> #include <unistd.h> typedef struct timespec os_filetime; +typedef s8 os_mapped_file; + typedef struct { size filesize; os_filetime timestamp; } os_file_stats; -typedef s8 os_mapped_file; + +typedef struct { + i32 fd; + pid_t pid; +} os_child; #define OS_MAP_READ PROT_READ #define OS_MAP_PRIVATE MAP_PRIVATE @@ -90,3 +100,140 @@ os_unmap_file(u8 *data, size len) { munmap(data, len); } + +static void +os_alloc_ring_buffer(RingBuf *rb, size capacity) +{ + size pagesize = sysconf(_SC_PAGESIZE); + if (capacity % pagesize != 0) + capacity += pagesize - capacity % pagesize; + + rb->widx = 0; + rb->filled = 0; + rb->cap = capacity; + rb->buf = mmap(0, 3 * rb->cap, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); + + if (rb->buf == MAP_FAILED) + die("os_alloc_ring_buffer: initial mmap failed\n"); + + for (i32 i = 0; i < 3; i++) { + void *ret = mmap(rb->buf + i * rb->cap, rb->cap, PROT_READ|PROT_WRITE, + MAP_ANONYMOUS|MAP_FIXED|MAP_PRIVATE, -1, 0); + if (ret == MAP_FAILED) + die("os_alloc_ring_buffer: mmap(%d) failed\n", i); + } + /* NOTE: start in the middle page */ + rb->buf += rb->cap; +} + +static void +execsh(char *defcmd) +{ + char *sh; + struct passwd *pw; + + if ((pw = getpwuid(getuid())) == NULL) + die("are you real?\n"); + + if ((sh = getenv("SHELL")) == NULL) + sh = pw->pw_shell[0] ? pw->pw_shell : defcmd; + + char *argv[] = {sh, NULL}; + setenv("USER", pw->pw_name, 1); + setenv("LOGNAME", pw->pw_name, 1); + setenv("SHELL", sh, 1); + setenv("HOME", pw->pw_dir, 1); + /* TODO: don't pretend to be xterm ? */ + setenv("TERM", "xterm", 1); + + execvp(sh, argv); + + _exit(1); +} + +static os_child +os_fork_child(char *cmd) +{ + i32 cfd; + + struct termios raw; + cfmakeraw(&raw); + pid_t pid = forkpty(&cfd, NULL, &raw, NULL); + + switch (pid) { + case -1: + die("os_fork_child: failed to spawn child: %s\n", cmd); + case 0: /* child */ + execsh(cmd); + break; + } + + i32 flags = fcntl(cfd, F_GETFL); + if (flags == -1) + die("os_fork_child: fcntl: F_GETFL\n"); + if (fcntl(cfd, F_SETFL, flags | O_NONBLOCK) == -1) + die("os_fork_child: fcntl: F_SETFL\n"); + + return (os_child){ .pid = pid, .fd = cfd }; +} + +static b32 +os_child_data_available(os_child c) +{ + b32 result = 0; + + struct timespec timeout = {0}; + fd_set rfd; + FD_ZERO(&rfd); + FD_SET(c.fd, &rfd); + + pselect(c.fd+1, &rfd, NULL, NULL, &timeout, NULL); + + result = FD_ISSET(c.fd, &rfd) != 0; + return result; +} + +static b32 +os_child_exited(os_child c) +{ + i32 r, status; + r = waitpid(c.pid, &status, WNOHANG); + return r == c.pid && WIFEXITED(status); +} + +static size +os_read_from_child(os_child c, RingBuf *rb) +{ + size r = read(c.fd, rb->buf + rb->widx, rb->cap); + + ASSERT(rb->widx < rb->cap); + ASSERT(r <= rb->cap); + ASSERT(r != -1); + + rb->widx += r; + rb->filled += r; + + CLAMP(rb->filled, 0, rb->cap); + if (rb->widx >= rb->cap) + rb->widx -= rb->cap; + + ASSERT(rb->filled >= 0); + ASSERT(rb->widx >= 0 && rb->widx < rb->cap); + return r; +} + +static void +os_child_put_s8(os_child c, s8 text) +{ + write(c.fd, text.data, text.len); +} + +static void +os_child_put_char(os_child c, u32 cp) +{ + /* TODO: encode to utf-8 */ + ASSERT(cp <= 0x7f); + u8 character = (u8)cp; + s8 text = {.len = 1, .data = &character}; + os_child_put_s8(c, text); +} diff --git a/util.h b/util.h @@ -23,6 +23,7 @@ #define BETWEEN(x, a, b) ((x) >= (a) && (x) <= (b)) #define MIN(a, b) ((a) <= (b) ? (a) : (b)) #define MAX(a, b) ((a) >= (b) ? (a) : (b)) +#define CLAMP(x, a, b) ((x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)) typedef float f32; typedef double f64; @@ -68,6 +69,14 @@ typedef struct { u8 *beg, *end; } Arena; typedef struct { size len; u8 *data; } s8; #define s8(s) (s8){.len = ARRAY_COUNT(s) - 1, .data = (u8 *)s} +/* NOTE: virtual memory ring buffer */ +typedef struct { + size cap; + size filled; + size widx; + u8 *buf; +} RingBuf; + #define GL_UNIFORMS \ X(Pmat) \ X(charmap) \ @@ -114,6 +123,13 @@ typedef struct { #include <ft2build.h> #include FT_FREETYPE_H +#include "util.c" + +#ifdef __unix__ +#include "os_unix.c" +#else +#error Unsupported Platform! +#endif typedef struct { u8 *buf; @@ -132,16 +148,12 @@ typedef struct { GLCtx gl; FontAtlas fa; - FT_Library ftlib; -} Term; + RingBuf log; -#include "util.c" + os_child child; -#ifdef __unix__ -#include "os_unix.c" -#else -#error Unsupported Platform! -#endif + FT_Library ftlib; +} Term; #include "font.c" diff --git a/vtgl.c b/vtgl.c @@ -122,7 +122,10 @@ draw_text(GLCtx *gl, s8 text, v2 position, Colour colour, b32 monospaced) f32 single_space_width = gl->glyph_cache[0].size.w; for (size i = 0; i < text.len; i++) { Glyph g; - i32 glyph_idx = get_gpu_glyph_index(gl, text.data[i], &g); + /* TODO: don't do this */ + u32 cp = text.data[i]; + CLAMP(cp, ' ', '~'); + i32 glyph_idx = get_gpu_glyph_index(gl, cp, &g); v2 texscale = {.x = g.size.w / MAX_FONT_SIZE, .y = g.size.h / MAX_FONT_SIZE}; v2 vertscale = {.x = g.size.w, .y = g.size.h}; v2 vertoff = {.x = position.x + g.delta.x, .y = position.y + g.delta.y}; @@ -194,16 +197,26 @@ fb_callback(GLFWwindow *win, i32 w, i32 h) } static void -key_callback(GLFWwindow *w, i32 key, i32 sc, i32 act, i32 mods) +key_callback(GLFWwindow *win, i32 key, i32 sc, i32 act, i32 mods) { + Term *t = glfwGetWindowUserPointer(win); if (key == GLFW_KEY_ESCAPE && act == GLFW_PRESS) - glfwSetWindowShouldClose(w, GL_TRUE); + glfwSetWindowShouldClose(win, GL_TRUE); + if (key == GLFW_KEY_ENTER && act == GLFW_PRESS) + os_child_put_char(t->child, '\r'); +} + +static void +char_callback(GLFWwindow *win, u32 codepoint) +{ + Term *t = glfwGetWindowUserPointer(win); + os_child_put_char(t->child, codepoint); } DEBUG_EXPORT void init_callbacks(GLCtx *gl) { - //glfwSetCharCallback(gl->window, char_callback); + glfwSetCharCallback(gl->window, char_callback); glfwSetFramebufferSizeCallback(gl->window, fb_callback); glfwSetKeyCallback(gl->window, key_callback); //glfwSetWindowRefreshCallback(gl->window, refresh_callback); @@ -221,6 +234,16 @@ do_terminal(Term *t, Arena a) clear_colour(); + if (os_child_data_available(t->child)) { + if (os_child_exited(t->child)) + glfwSetWindowShouldClose(t->gl.window, GL_TRUE); + os_read_from_child(t->child, &t->log); + } + /* TODO: actually parse this data */ + s8 text = {.len = t->log.widx, .data = t->log.buf}; + v2 cpos = { .x = 0, .y = t->gl.window_size.h/2 }; + draw_text(&t->gl, text, cpos, (Colour){.rgba = 0x1e9e33ff}, 1); + Rect r1 = {.pos = {.x = 20, .y = 20}, .size = {.x = 200, .y = 100}}; Rect r3 = {.pos = {.x = 100, .y = 600}, .size = {.x = 50, .y = 100}}; static Rect r2 = {.pos = {.x = 50, .y = 300}, .size = {.x = 100, .y = 100}};