vtgl

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

Commit: 2cfaa5b99e70e408789879ec12b2c0b9383d218b
Parent: ff7eb7ae337b13b9bd1a3a3dd803d64cc85d557a
Author: Randy Palamar
Date:   Sun, 10 Nov 2024 14:37:22 -0700

introduce platform file watch and use it for debug lib

Diffstat:
Mos_unix.c | 16----------------
Mplatform_linux_x11.c | 312++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mvtgl.h | 9+++++++++
3 files changed, 213 insertions(+), 124 deletions(-)

diff --git a/os_unix.c b/os_unix.c @@ -304,22 +304,6 @@ os_fork_child(char *cmd) } static b32 -os_child_data_available(iptr c) -{ - b32 result = 0; - - struct timespec timeout = {0}; - fd_set rfd; - FD_ZERO(&rfd); - FD_SET(c, &rfd); - - pselect(c + 1, &rfd, NULL, NULL, &timeout, NULL); - - result = FD_ISSET(c, &rfd) != 0; - return result; -} - -static b32 os_child_exited(iptr pid) { i32 r, status; diff --git a/platform_linux_x11.c b/platform_linux_x11.c @@ -19,6 +19,39 @@ typedef void *Window; #define VERSION "unknown" #endif +typedef struct { + platform_file_watch_callback_fn *fn; + u8 *path; + void *user_ctx; + i32 inode; + i32 handle; +} linux_file_watch; + +typedef struct { + Arena platform_memory; + void *window; + + TerminalMemory memory; + TerminalInput input; + + posix_platform_process child; + i32 inotify_fd; + + linux_file_watch file_watches[32]; + i32 file_watch_count; + + Stream error_stream; + +#ifdef _DEBUG + void *library_handle; +#endif + + /* TODO: remove */ + b32 terminal_initialized; +} PlatformCtx; + +static PlatformCtx linux_ctx; + #ifndef _DEBUG #define do_debug(...) #include "vtgl.c" @@ -26,8 +59,7 @@ typedef void *Window; #include <dlfcn.h> #include <time.h> -static char *libname = "./vtgl.so"; -static void *libhandle; +#define DEBUG_LIB_NAME "./vtgl.so" /* TODO: cleanup */ typedef void reset_terminal_fn(TerminalMemory *); @@ -44,51 +76,40 @@ typedef void reset_terminal_fn(TerminalMemory *); LIB_FNS #undef X -static void -load_library(const char *lib, Stream *err) +static PLATFORM_FILE_WATCH_CALLBACK_FN(debug_reload_library) { + PlatformCtx *ctx = user_ctx; + + if (ctx->input.executable_reloaded) + return; + ctx->input.executable_reloaded = 1; s8 nl = s8("\n"); /* NOTE: glibc sucks and will crash if this is NULL */ - if (libhandle) - dlclose(libhandle); - libhandle = dlopen(lib, RTLD_NOW|RTLD_LOCAL); - if (!libhandle) - stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlopen: "), c_str_to_s8(dlerror()), nl}); + if (ctx->library_handle) + dlclose(ctx->library_handle); + ctx->library_handle = dlopen((c8 *)path, RTLD_NOW|RTLD_LOCAL); + if (!ctx->library_handle) + stream_push_s8s(&ctx->error_stream, 3, + (s8 []){s8("dlopen: "), c_str_to_s8(dlerror()), nl}); + #define X(name) \ - name = dlsym(libhandle, #name); \ - if (!name) stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlsym: "), \ - c_str_to_s8(dlerror()), nl}); + name = dlsym(ctx->library_handle, #name); \ + if (!name) stream_push_s8s(&ctx->error_stream, 3, (s8 []){s8("dlsym: "), \ + c_str_to_s8(dlerror()), nl}); LIB_FNS #undef X -} -static void -do_debug(TerminalMemory *t, Stream *err) -{ - static FileStats updated; - FileStats test = posix_get_file_stats((u8 *)libname); - - if (test.timestamp > updated.timestamp) { - updated = test; - /* NOTE: sucks but seems to be easiest reliable way to make sure lib is written */ - struct timespec sleep_time = { .tv_nsec = 100e6 }; - nanosleep(&sleep_time, &sleep_time); - load_library(libname, err); - if (t) reset_terminal(t); - stream_push_s8(err, s8("Reloaded Main Program\n")); - } - - /* TODO: clear debug memory? */ + if (ctx->terminal_initialized) reset_terminal(&ctx->memory); + stream_push_s8(&ctx->error_stream, s8("Reloaded Main Program\n")); - if (err->widx) { - os_write_err_msg(stream_to_s8(err)); - err->widx = 0; - } + os_write_err_msg(stream_to_s8(&ctx->error_stream)); + ctx->error_stream.widx = 0; } - #endif /* _DEBUG */ -static GLFWwindow *g_window; +#include <sys/inotify.h> + +#define LINUX_INOTIFY_MASK (IN_CLOSE|IN_MODIFY) static void button_action(ButtonState *button, b32 pressed) @@ -199,13 +220,54 @@ init_window(tmp_user_ctx *ctx, iv2 window_size) } static void -update_input(GLFWwindow *win, TerminalInput *input, posix_platform_process child) +dispatch_file_watch_events(PlatformCtx *ctx) { + u8 *mem = alloc_(&ctx->platform_memory, 4096, 64, 1); + s8 buf = {.len = 4096, .data = mem}; + for (;;) { + size rlen = read(ctx->inotify_fd, buf.data, buf.len); + if (rlen <= 0) + break; + struct inotify_event *ie; + for (u8 *data = buf.data; data < buf.data + rlen; data += sizeof(*ie) + ie->len) { + ie = (struct inotify_event *)data; + for (i32 i = 0; i < ctx->file_watch_count; i++) { + linux_file_watch *fw = ctx->file_watches + i; + if (fw->handle == ie->wd) { + b32 file_changed = (ie->mask & IN_CLOSE_WRITE) != 0; + file_changed |= (ie->mask & IN_MODIFY) != 0; + /* NOTE: some editors and the compiler will rewrite a file + * completely and thus the inode will change; here we + * detect that and restart the watch */ + struct stat sb; + stat((c8 *)fw->path, &sb); + if (fw->inode != sb.st_ino) { + inotify_rm_watch(ctx->inotify_fd, fw->handle); + fw->inode = sb.st_ino; + fw->handle = inotify_add_watch(ctx->inotify_fd, + (c8 *)fw->path, + LINUX_INOTIFY_MASK); + file_changed = 1; + } + if (file_changed) + fw->fn(fw->path, fw->user_ctx); + } + } + } + } +} + +static void +update_input(PlatformCtx *ctx) +{ + TerminalInput *input = &ctx->input; + ctx->input.executable_reloaded = 0; + /* NOTE: mouse */ input->mouse_scroll = (v2){0}; f64 mouse_x, mouse_y; - glfwGetCursorPos(win, &mouse_x, &mouse_y); + glfwGetCursorPos(ctx->window, &mouse_x, &mouse_y); input->mouse.x = mouse_x; input->mouse.y = input->window_size.h - mouse_y; @@ -216,8 +278,25 @@ update_input(GLFWwindow *win, TerminalInput *input, posix_platform_process child glfwPollEvents(); + /* TODO: pselect should include x11 fd after removing glfw */ + /* TODO: for now this is a poll but we could roll some wait time into it as well */ + struct timespec timeout = {0}; + fd_set rfd; + FD_ZERO(&rfd); + FD_SET(ctx->child.handle, &rfd); + FD_SET(ctx->inotify_fd, &rfd); + + i32 max_fd = MAX(ctx->inotify_fd, ctx->child.handle); + pselect(max_fd + 1, &rfd, NULL, NULL, &timeout, NULL); + + input->data_available = FD_ISSET(ctx->child.handle, &rfd) != 0; + + if (FD_ISSET(ctx->inotify_fd, &rfd)) { + dispatch_file_watch_events(ctx); + } + + /* NOTE: char_stream was filled in char callback */ input->character_input = stream_to_s8(&input->char_stream); - input->data_available = os_child_data_available(child.handle); } static PLATFORM_CLIPBOARD_FN(x11_get_clipboard) @@ -256,7 +335,7 @@ static PLATFORM_CLIPBOARD_FN(x11_set_clipboard) static PLATFORM_WINDOW_TITLE_FN(x11_get_window_title) { ASSERT(buffer); - char *title = (c8 *)glfwGetWindowTitle(g_window); + char *title = (c8 *)glfwGetWindowTitle(linux_ctx.window); if (title) stream_push_s8(buffer, c_str_to_s8(title)); } @@ -265,7 +344,23 @@ static PLATFORM_WINDOW_TITLE_FN(x11_set_window_title) ASSERT(buffer); stream_push_byte(buffer, 0); if (!buffer->errors) - glfwSetWindowTitle(g_window, (c8 *)buffer->buf); + glfwSetWindowTitle(linux_ctx.window, (c8 *)buffer->buf); +} + +static PLATFORM_ADD_FILE_WATCH_FN(linux_add_file_watch) +{ + struct stat sb; + stat((c8 *)path, &sb); + + i32 wd = inotify_add_watch(linux_ctx.inotify_fd, (c8 *)path, LINUX_INOTIFY_MASK); + i32 idx = linux_ctx.file_watch_count++; + ASSERT(idx < ARRAY_COUNT(linux_ctx.file_watches)); + + linux_ctx.file_watches[idx].fn = fn; + linux_ctx.file_watches[idx].path = path; + linux_ctx.file_watches[idx].handle = wd; + linux_ctx.file_watches[idx].inode = sb.st_ino; + linux_ctx.file_watches[idx].user_ctx = user_ctx; } static void @@ -280,34 +375,34 @@ usage(char *argv0, Stream *err) i32 main(i32 argc, char *argv[], char *envp[]) { - TerminalMemory term_memory = {0}; { /* TODO: remove this step */ Arena tmp = os_new_arena(16 * MEGABYTE); - term_memory.memory = tmp.beg; - term_memory.memory_size = tmp.end - tmp.beg; + linux_ctx.memory.memory = tmp.beg; + linux_ctx.memory.memory_size = tmp.end - tmp.beg; } #ifdef _DEBUG { - Arena debug_memory = os_new_arena(128 * MEGABYTE); - term_memory.debug_memory = debug_memory.beg; - term_memory.debug_memory_size = debug_memory.end - debug_memory.beg; + Arena debug_memory = os_new_arena(128 * MEGABYTE); + linux_ctx.memory.debug_memory = debug_memory.beg; + linux_ctx.memory.debug_memory_size = debug_memory.end - debug_memory.beg; } #endif - term_memory.platform_api.read = posix_read; - term_memory.platform_api.write = posix_write; - term_memory.platform_api.allocate_ring_buffer = posix_allocate_ring_buffer; - term_memory.platform_api.get_file_stats = posix_get_file_stats; - term_memory.platform_api.read_file = posix_read_file; - term_memory.platform_api.get_clipboard = x11_get_clipboard; - term_memory.platform_api.set_clipboard = x11_set_clipboard; - term_memory.platform_api.get_window_title = x11_get_window_title; - term_memory.platform_api.set_window_title = x11_set_window_title; - term_memory.platform_api.path_separator = '/'; - - Arena platform_arena = os_new_arena(2 * MEGABYTE); - Stream error_stream = stream_alloc(&platform_arena, MEGABYTE / 4); + linux_ctx.memory.platform_api.read = posix_read; + linux_ctx.memory.platform_api.write = posix_write; + linux_ctx.memory.platform_api.allocate_ring_buffer = posix_allocate_ring_buffer; + linux_ctx.memory.platform_api.get_file_stats = posix_get_file_stats; + linux_ctx.memory.platform_api.read_file = posix_read_file; + linux_ctx.memory.platform_api.get_clipboard = x11_get_clipboard; + linux_ctx.memory.platform_api.set_clipboard = x11_set_clipboard; + linux_ctx.memory.platform_api.get_window_title = x11_get_window_title; + linux_ctx.memory.platform_api.set_window_title = x11_set_window_title; + linux_ctx.memory.platform_api.add_file_watch = linux_add_file_watch; + linux_ctx.memory.platform_api.path_separator = '/'; + + linux_ctx.platform_memory = os_new_arena(2 * MEGABYTE); + linux_ctx.error_stream = stream_alloc(&linux_ctx.platform_memory, MEGABYTE / 4); iv2 cells = {.x = -1, .y = -1}; @@ -317,14 +412,14 @@ main(i32 argc, char *argv[], char *envp[]) for (i32 i = 0; i < argc; i++) { char *arg = argv[i]; if (!arg || !arg[0]) - usage(argv0, &error_stream); + usage(argv0, &linux_ctx.error_stream); if (arg[0] != '-') break; arg++; switch (arg[0]) { case 'g': if (!argv[i + 1]) - usage(argv0, &error_stream); + usage(argv0, &linux_ctx.error_stream); cres = i32_from_cstr(argv[i + 1], 'x'); if (cres.status == CR_SUCCESS) cells.w = cres.i; @@ -332,27 +427,32 @@ main(i32 argc, char *argv[], char *envp[]) if (cres.status == CR_SUCCESS) cells.h = cres.i; if (cells.w <= 0 || cells.h <= 0) { - stream_push_s8(&error_stream, s8("ignoring malformed geometry: ")); - stream_push_s8(&error_stream, c_str_to_s8(argv[i + 1])); - stream_push_byte(&error_stream, '\n'); + stream_push_s8(&linux_ctx.error_stream, s8("ignoring malformed geometry: ")); + stream_push_s8(&linux_ctx.error_stream, c_str_to_s8(argv[i + 1])); + stream_push_byte(&linux_ctx.error_stream, '\n'); } argv++; argc--; break; case 'v': - stream_push_s8s(&error_stream, 2, + stream_push_s8s(&linux_ctx.error_stream, 2, (s8 []){c_str_to_s8(argv0), s8(" " VERSION "\n")}); - os_fatal(stream_to_s8(&error_stream)); + os_fatal(stream_to_s8(&linux_ctx.error_stream)); default: - usage(argv0, &error_stream); + usage(argv0, &linux_ctx.error_stream); } } - if (error_stream.widx) { - os_write_err_msg(stream_to_s8(&error_stream)); - error_stream.widx = 0; + if (linux_ctx.error_stream.widx) { + os_write_err_msg(stream_to_s8(&linux_ctx.error_stream)); + linux_ctx.error_stream.widx = 0; } - do_debug(NULL, &error_stream); + linux_ctx.inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + +#ifdef _DEBUG + debug_reload_library((u8 *)DEBUG_LIB_NAME, &linux_ctx); + linux_add_file_watch((u8 *)DEBUG_LIB_NAME, debug_reload_library, &linux_ctx); +#endif if (!glfwInit()) os_fatal(s8("Failed to init GLFW\n")); @@ -367,84 +467,80 @@ main(i32 argc, char *argv[], char *envp[]) iv2 monitor_size; glfwGetMonitorWorkarea(mon, NULL, NULL, &monitor_size.w, &monitor_size.h); - TerminalInput input = {0}; - input.char_stream = arena_stream(platform_arena); + linux_ctx.input.char_stream = arena_stream(linux_ctx.platform_memory); /* TODO: delete !!!! */ tmp_user_ctx ctx; - ctx.memory = &term_memory; - ctx.input = &input; + ctx.memory = &linux_ctx.memory; + ctx.input = &linux_ctx.input; - iv2 window_size = {.w = 1280, .h = 720}; - GLFWwindow *window = init_window(&ctx, window_size); - g_window = window; + iv2 window_size = {.w = 1280, .h = 720}; + linux_ctx.window = init_window(&ctx, window_size); + linux_ctx.child = os_fork_child("/bin/sh"); - posix_platform_process child = os_fork_child("/bin/sh"); - - iv2 requested_size = vtgl_initialize(&term_memory, child.handle, cells, monitor_size); + iv2 requested_size = vtgl_initialize(&linux_ctx.memory, linux_ctx.child.handle, cells, monitor_size); if (requested_size.w > 0 && requested_size.h > 0 && (requested_size.w != window_size.w || requested_size.h != window_size.h)) { - glfwSetWindowAttrib(window, GLFW_FLOATING, GLFW_TRUE); + glfwSetWindowAttrib(linux_ctx.window, GLFW_FLOATING, GLFW_TRUE); i32 x = ABS(window_size.w - requested_size.w) / 2; i32 y = ABS(window_size.h - requested_size.h) / 2; window_size = requested_size; - glfwSetWindowMonitor(window, 0, x, y, window_size.w, window_size.h, GLFW_DONT_CARE); + glfwSetWindowMonitor(linux_ctx.window, 0, x, y, window_size.w, window_size.h, GLFW_DONT_CARE); /* NOTE: resizable must be set after the window is shown; otherwise tiling window * managers will forcibly resize us even if we are supposed to be floating */ - glfwShowWindow(window); - glfwSetWindowAttrib(window, GLFW_RESIZABLE, GLFW_TRUE); + glfwShowWindow(linux_ctx.window); + glfwSetWindowAttrib(linux_ctx.window, GLFW_RESIZABLE, GLFW_TRUE); } else { /* NOTE: on the other hand we should let the window be resized if no size was * explicitly requested */ - glfwSetWindowAttrib(window, GLFW_RESIZABLE, GLFW_TRUE); - glfwSetWindowPos(window, + glfwSetWindowAttrib(linux_ctx.window, GLFW_RESIZABLE, GLFW_TRUE); + glfwSetWindowPos(linux_ctx.window, ABS(monitor_size.w - window_size.w) / 2, ABS(monitor_size.h - window_size.h) / 2); - glfwShowWindow(window); + glfwShowWindow(linux_ctx.window); } - input.window_size = window_size; + linux_ctx.input.window_size = window_size; /* TODO: remove */ { - Term *t = term_memory.memory; - t->gl.window = window; - reset_terminal(&term_memory); + Term *t = linux_ctx.memory.memory; + t->gl.window = linux_ctx.window; + reset_terminal(&linux_ctx.memory); + linux_ctx.terminal_initialized = 1; } Range last_sel = {0}; f64 last_time = os_get_time(); - while (!glfwWindowShouldClose(window)) { - if (os_child_exited(child.process_id)) + while (!glfwWindowShouldClose(linux_ctx.window)) { + if (os_child_exited(linux_ctx.child.process_id)) break; - update_input(window, &input, child); - - do_debug(&term_memory, &error_stream); + update_input(&linux_ctx); /* TODO: cpu time excluding waiting for the vblank */ - f64 current_time = os_get_time(); - input.dt = current_time - last_time; - last_time = current_time; + f64 current_time = os_get_time(); + linux_ctx.input.dt = current_time - last_time; + last_time = current_time; - debug_begin_frame(&term_memory, input.dt); + debug_begin_frame(&linux_ctx.memory, linux_ctx.input.dt); - vtgl_frame_step(&term_memory, &input); + vtgl_frame_step(&linux_ctx.memory, &linux_ctx.input); - Range current_sel = vtgl_active_selection(&term_memory, 0); + Range current_sel = vtgl_active_selection(&linux_ctx.memory, 0); if (is_valid_range(current_sel) && !equal_range(current_sel, last_sel)) { - Stream buf = arena_stream(platform_arena); - vtgl_active_selection(&term_memory, &buf); + Stream buf = arena_stream(linux_ctx.platform_memory); + vtgl_active_selection(&linux_ctx.memory, &buf); stream_push_byte(&buf, 0); if (!buf.errors) glfwSetX11SelectionString((c8 *)buf.buf); last_sel = current_sel; } - debug_end_frame(&term_memory); + debug_end_frame(&linux_ctx.memory); - glfwSwapBuffers(window); + glfwSwapBuffers(linux_ctx.window); } return 0; diff --git a/vtgl.h b/vtgl.h @@ -24,6 +24,13 @@ typedef PLATFORM_CLIPBOARD_FN(platform_clipboard_fn); #define PLATFORM_WINDOW_TITLE_FN(name) void name(Stream *buffer) typedef PLATFORM_WINDOW_TITLE_FN(platform_window_title_fn); +/* NOTE: for now we will do the callback route but this will change if we do multithreading */ +#define PLATFORM_FILE_WATCH_CALLBACK_FN(name) void name(u8 *path, void *user_ctx) +typedef PLATFORM_FILE_WATCH_CALLBACK_FN(platform_file_watch_callback_fn); + +#define PLATFORM_ADD_FILE_WATCH_FN(name) void name(u8 *path, platform_file_watch_callback_fn *fn, void *user_ctx) +typedef PLATFORM_ADD_FILE_WATCH_FN(platform_add_file_watch_fn); + typedef struct { platform_allocate_ring_buffer_fn *allocate_ring_buffer; platform_read_fn *read; @@ -38,6 +45,8 @@ typedef struct { platform_window_title_fn *get_window_title; platform_window_title_fn *set_window_title; + platform_add_file_watch_fn *add_file_watch; + u8 path_separator; } PlatformAPI;