vtgl

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

Commit: 5cfafe14ace64e47eca65e3998ecc0391c915773
Parent: e357a4d259c41869292a4255dc5ec302d00d614d
Author: Randy Palamar
Date:   Mon, 21 Apr 2025 18:47:22 -0600

cleanup code style

Diffstat:
Mbuild.sh | 2+-
Mconfig.def.h | 48++++++++++++++++++++++++------------------------
Mdebug.c | 47++++++++++++++++++++++++-----------------------
Mdebug.h | 2+-
Mextern/stb_truetype.h | 4++--
Mfont.c | 36++++++++++++++++++------------------
Mintrinsics.c | 12+++++++++---
Aos.h | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aos_linux_aarch64.c | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aos_linux_amd64.c | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aos_linux_common.c | 559+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aos_linux_x11.c | 526+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dplatform_linux_aarch64.c | 172-------------------------------------------------------------------------------
Dplatform_linux_amd64.c | 162-------------------------------------------------------------------------------
Dplatform_linux_common.c | 558-------------------------------------------------------------------------------
Dplatform_linux_x11.c | 544-------------------------------------------------------------------------------
Mterminal.c | 234+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mtests/test-common.c | 69+++++++++++++++++++++++++++++++++------------------------------------
Mtests/test-fuzz.c | 2+-
Mtests/test.c | 124++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mutil.c | 217+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mutil.h | 24++++++++++++------------
Mvtgl.c | 332++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mvtgl.h | 356+++++++++++++++++++++++++++++++++----------------------------------------------
24 files changed, 2246 insertions(+), 2208 deletions(-)

diff --git a/build.sh b/build.sh @@ -85,5 +85,5 @@ fuzz_results) esac [ ${build_lib} ] && ${cc} ${cflags} -fPIC vtgl.c -o vtgl.so ${ldflags} -shared -${cc} ${cflags} -o vtgl platform_linux_x11.c ${ldflags} +${cc} ${cflags} -o vtgl os_linux_x11.c ${ldflags} ${cc} ${testcflags} -O0 -o tests/test tests/test.c diff --git a/config.def.h b/config.def.h @@ -1,5 +1,5 @@ /* See LICENSE for copyright details */ -static FontDesc g_fonts[][FS_COUNT] = { +global char *g_fonts[][FS_COUNT] = { { [FS_NORMAL] = "/usr/share/fonts/gofont/Go-Mono.ttf", [FS_BOLD] = "/usr/share/fonts/gofont/Go-Mono-Bold.ttf", @@ -17,25 +17,25 @@ static FontDesc g_fonts[][FS_COUNT] = { }, }; -static i32 g_font_size = 28; -static i32 g_ui_font_size = 20; +global i32 g_font_size = 28; +global i32 g_ui_font_size = 20; /* NOTE: indices into the array above */ -static u32 g_ui_small_font_id = 2; -static u32 g_ui_large_font_id = 3; -static u32 g_ui_debug_font_id = 4; +global u32 g_ui_small_font_id = 2; +global u32 g_ui_large_font_id = 3; +global u32 g_ui_debug_font_id = 4; /* NOTE: terminal margin in pixels */ -static iv2 g_term_margin = {.w = 8, .h = 8}; +global iv2 g_term_margin = {.w = 8, .h = 8}; -static u8 g_tabstop = 8; +global u8 g_tabstop = 8; /* NOTE: number of blinks per second */ -static f32 g_blink_speed = 1.0f; +global f32 g_blink_speed = 1.0f; -static s8 g_shader_path_prefix = s8(""); +global s8 g_shader_path_prefix = s8(""); -static Colour base16_colours[16] = { +global Colour base16_colours[16] = { [0] = { .rgba = 0x000000ff }, /* black */ [1] = { .rgba = 0xaa0000ff }, /* red */ [2] = { .rgba = 0x00aa00ff }, /* green */ @@ -56,13 +56,13 @@ static Colour base16_colours[16] = { [15] = { .rgba = 0xffffffff }, /* white */ }; -struct { +global struct { Colour *data; u8 fgidx; u8 bgidx; } g_colours = {base16_colours, 7, 0}; -#define KEYBIND_FN(name) b32 name(Term *t, PlatformAPI *platform, Arg a) +#define KEYBIND_FN(name) b32 name(Term *t, OS *os, Arg a) typedef KEYBIND_FN(KeyBind_Fn); /* NOTE: Bindable Functions */ @@ -76,19 +76,19 @@ KEYBIND_FN(zoom); /* arg: .i = font size increment */ #define ALTMOD (MOD_ALT|MOD_SHIFT) //#define XXX (MOD_SUPER) -struct hotkey { +global struct hotkey { u32 key; KeyBind_Fn *fn; Arg arg; } g_hotkeys[] = { - {ENCODE_KEY(ACT_PRESS, MODKEY, KEY_C), copy, {.i = CLIPBOARD_0}}, - {ENCODE_KEY(ACT_PRESS, MODKEY, KEY_V), paste, {.i = CLIPBOARD_0}}, - {ENCODE_KEY(ACT_PRESS, 0, KEY_PAGE_UP), scroll, {.i = +3}}, - {ENCODE_KEY(ACT_REPEAT, 0, KEY_PAGE_UP), scroll, {.i = +3}}, - {ENCODE_KEY(ACT_PRESS, 0, KEY_PAGE_DOWN), scroll, {.i = -3}}, - {ENCODE_KEY(ACT_REPEAT, 0, KEY_PAGE_DOWN), scroll, {.i = -3}}, - {ENCODE_KEY(ACT_PRESS, TERMMOD, KEY_MINUS), zoom, {.i = -1}}, - {ENCODE_KEY(ACT_REPEAT, TERMMOD, KEY_MINUS), zoom, {.i = -1}}, - {ENCODE_KEY(ACT_PRESS, TERMMOD, KEY_EQUAL), zoom, {.i = +1}}, - {ENCODE_KEY(ACT_REPEAT, TERMMOD, KEY_EQUAL), zoom, {.i = +1}}, + {ENCODE_KEY(BUTTON_PRESS, MODKEY, KEY_C), copy, {.i = OS_CLIPBOARD_PRIMARY}}, + {ENCODE_KEY(BUTTON_PRESS, MODKEY, KEY_V), paste, {.i = OS_CLIPBOARD_PRIMARY}}, + {ENCODE_KEY(BUTTON_PRESS, 0, KEY_PAGE_UP), scroll, {.i = +3}}, + {ENCODE_KEY(BUTTON_REPEAT, 0, KEY_PAGE_UP), scroll, {.i = +3}}, + {ENCODE_KEY(BUTTON_PRESS, 0, KEY_PAGE_DOWN), scroll, {.i = -3}}, + {ENCODE_KEY(BUTTON_REPEAT, 0, KEY_PAGE_DOWN), scroll, {.i = -3}}, + {ENCODE_KEY(BUTTON_PRESS, TERMMOD, KEY_MINUS), zoom, {.i = -1}}, + {ENCODE_KEY(BUTTON_REPEAT, TERMMOD, KEY_MINUS), zoom, {.i = -1}}, + {ENCODE_KEY(BUTTON_PRESS, TERMMOD, KEY_EQUAL), zoom, {.i = +1}}, + {ENCODE_KEY(BUTTON_REPEAT, TERMMOD, KEY_EQUAL), zoom, {.i = +1}}, }; diff --git a/debug.c b/debug.c @@ -1,22 +1,23 @@ -static void +/* See LICENSE for copyright details */ +function void dump_lines_to_file(Term *t) { u8 buf[256]; - Stream fname = {.cap = sizeof(buf), .buf = buf}; + Stream fname = {.capacity = sizeof(buf), .data = buf}; u64 current_time = os_get_time(); stream_push_u64(&fname, current_time); stream_push_s8(&fname, s8("-lines.bin\0")); /* TODO: just replace this with some giant buffer, this is debug code */ - iptr file = os_open(buf, FA_WRITE); + iptr file = os_open(buf, OS_FA_WRITE); if (file == INVALID_FILE) return; - fname.buf[fname.widx - 1] = '\n'; + fname.data[fname.count - 1] = '\n'; os_write_err_msg(s8("dumping lines to ")); os_write_err_msg(stream_to_s8(&fname)); - TermView *tv = t->views + t->view_idx; - size line_count = MIN(256, tv->lines.filled); + TermView *tv = t->views + t->view_idx; + iz line_count = MIN(256, tv->lines.filled); Arena temp_arena = t->arena_for_frame; Stream out = stream_alloc(&temp_arena, MB(1)); @@ -35,16 +36,16 @@ dump_lines_to_file(Term *t) stream_push_s8(&out, s8("\nRaw Line Count: ")); stream_push_u64(&out, (u32)line_count); stream_push_s8(&out, s8("\n==============================\n")); - size file_offset = 0; - for (size i = -(line_count - 1); i <= 0; i++) { - Line *line = tv->lines.buf + get_line_idx(&tv->lines, i); - s8 l = line_to_s8(line, &tv->log); + iz file_offset = 0; + for (iz i = -(line_count - 1); i <= 0; i++) { + Line *line = tv->lines.data + get_line_idx(&tv->lines, i); + s8 l = line_to_s8(line); stream_push_s8(&out, l); if (out.errors) { /* TODO: cleanup */ os_offset_write(file, stream_to_s8(&out), file_offset); - file_offset += out.widx; - out.widx = 0; + file_offset += out.count; + stream_reset(&out, 0); stream_push_s8(&out, l); } } @@ -55,7 +56,7 @@ dump_lines_to_file(Term *t) os_close(file); } -static OpenDebugBlock * +function OpenDebugBlock * get_open_debug_block(DebugState *ds, DebugEvent *de) { OpenDebugBlock *result = ds->first_free_block; @@ -70,7 +71,7 @@ get_open_debug_block(DebugState *ds, DebugEvent *de) return result; } -static void +function void restart_collation(DebugState *ds, u32 invalid_record_index) { end_temp_arena(ds->temp_memory); @@ -84,7 +85,7 @@ restart_collation(DebugState *ds, u32 invalid_record_index) ds->open_record = 0; } -static void +function void coalesce_debug_events(DebugState *ds, u32 invalid_snapshot_index) { BEGIN_TIMED_BLOCK(); @@ -177,14 +178,14 @@ coalesce_debug_events(DebugState *ds, u32 invalid_snapshot_index) END_TIMED_BLOCK(); } -static void +function void refresh_collation(DebugState *ds) { restart_collation(ds, g_debug_table.snapshot_index); coalesce_debug_events(ds, g_debug_table.snapshot_index); } -static void +function void draw_debug_bar_chart(Term *t, DebugState *ds, TerminalInput *input, RenderCtx *rc, v2 bar_chart_top_left, f32 bar_chart_magnitude) { @@ -248,7 +249,7 @@ draw_debug_bar_chart(Term *t, DebugState *ds, TerminalInput *input, RenderCtx *r v2 txt_s = measure_text(rc, g_ui_debug_font_id, stream_to_s8(&txt)); v2 txt_p = {.x = pos.x - txt_s.w - 10, .y = pos.y}; push_s8(rc, txt_p, fg, g_ui_debug_font_id, stream_to_s8(&txt)); - txt.widx = 0; + stream_reset(&txt, 0); } v4 target_colour = (v4){.g = .38, .b = .78, .a = 1}; @@ -267,7 +268,7 @@ draw_debug_bar_chart(Term *t, DebugState *ds, TerminalInput *input, RenderCtx *r if (hot_region) { DebugMetadata *txt_meta = hot_region->meta; - txt.widx = 0; + stream_reset(&txt, 0); stream_push_s8(&txt, c_str_to_s8(txt_meta->block_name)); stream_push_s8(&txt, s8(": ")); stream_push_f64(&txt, hot_region_secs * 1e3, 100); @@ -288,7 +289,7 @@ draw_debug_bar_chart(Term *t, DebugState *ds, TerminalInput *input, RenderCtx *r END_TIMED_BLOCK(); } -static void +function void draw_debug_overlay(TerminalMemory *term_memory, TerminalInput *input, RenderCtx *rc) { Term *t = term_memory->memory; @@ -351,7 +352,7 @@ draw_debug_overlay(TerminalMemory *term_memory, TerminalInput *input, RenderCtx s8(" Recycles: "), }; for (u32 i = 0; i < ARRAY_COUNT(glyph_stats.E); i++) { - txt.widx = 0; + stream_reset(&txt, 0); stream_push_s8(&txt, fmts[i]); stream_push_u64(&txt, glyph_stats.E[i]); txt_pos.y = (u32)(txt_pos.y - line_height - line_pad); @@ -370,7 +371,7 @@ draw_debug_overlay(TerminalMemory *term_memory, TerminalInput *input, RenderCtx END_TIMED_BLOCK(); } -static void +function void debug_init(TerminalMemory *memory) { DebugState *ds = memory->debug_memory; @@ -383,7 +384,7 @@ debug_init(TerminalMemory *memory) restart_collation(ds, g_debug_table.snapshot_index); } -static void +function void debug_frame_end(TerminalMemory *memory, TerminalInput *input) { DebugState *debug_state = memory->debug_memory; diff --git a/debug.h b/debug.h @@ -102,7 +102,7 @@ typedef struct { u32 metadata_count; u32 snapshot_index; } DebugTable; -static DebugTable g_debug_table; +global DebugTable g_debug_table; #define RECORD_DEBUG_EVENT_COMMON(counter, event_type) \ u64 event_index = atomic_fetch_add(&g_debug_table.event_array_event_index, 1); \ diff --git a/extern/stb_truetype.h b/extern/stb_truetype.h @@ -1451,8 +1451,8 @@ static int stbtt__GetGlyphShapeTT(Arena *a, const stbtt_fontinfo *info, int glyp // Append vertices. tmp = alloc(a, stbtt_vertex, num_vertices + comp_num_verts); if (num_vertices > 0 && vertices) - mem_copy(vertices, tmp, num_vertices * sizeof(stbtt_vertex)); - mem_copy(comp_verts, tmp + num_vertices, comp_num_verts * sizeof(stbtt_vertex)); + mem_copy(tmp, vertices, num_vertices * sizeof(stbtt_vertex)); + mem_copy(tmp + num_vertices, comp_verts, comp_num_verts * sizeof(stbtt_vertex)); vertices = tmp; num_vertices += comp_num_verts; } diff --git a/font.c b/font.c @@ -6,7 +6,7 @@ * "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", | p - w * "│", "≤", "≥", "π", "≠", "£", "·", | x - ~ */ -static u16 graphic_0[31] = { +global u16 graphic_0[31] = { 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1, 0x2424, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0x23BA, 0x23BB, 0x2500, 0x23BC, 0x23BD, 0x251C, 0x2524, 0x2534, 0x252C, @@ -17,7 +17,7 @@ static u16 graphic_0[31] = { #define STB_STATIC #include "extern/stb_truetype.h" -static b32 +function b32 init_font(Font *f, char *font_path, i32 font_size) { b32 result = 0; @@ -40,7 +40,7 @@ init_font(Font *f, char *font_path, i32 font_size) return result; } -static u32 +function u32 compute_glyph_hash(GlyphCache *gc, u32 cp) { /* TODO: better hash function! */ @@ -48,7 +48,7 @@ compute_glyph_hash(GlyphCache *gc, u32 cp) return result; } -static uv2 +function uv2 unpack_gpu_tile_coord(u32 gpu_index) { uv2 result; @@ -57,7 +57,7 @@ unpack_gpu_tile_coord(u32 gpu_index) return result; } -static void +function void cached_glyph_to_uv(FontAtlas *fa, CachedGlyph *cg, v2 *start, v2 *end, v2 scale) { v2 cs = {.w = fa->info.w, .h = fa->info.h}; @@ -72,7 +72,7 @@ cached_glyph_to_uv(FontAtlas *fa, CachedGlyph *cg, v2 *start, v2 *end, v2 scale) } -static void +function void recycle_cache(GlyphCache *gc) { CachedGlyph *sentinel = gc->glyphs + 0; @@ -99,7 +99,7 @@ recycle_cache(GlyphCache *gc) gc->stats.recycle_count++; } -static i32 +function i32 pop_free_glyph_entry(GlyphCache *gc) { CachedGlyph *sentinel = gc->glyphs + 0; @@ -124,7 +124,7 @@ pop_free_glyph_entry(GlyphCache *gc) return result; } -static u32 +function u32 get_glyph_entry_index(GlyphCache *gc, u32 cp) { u32 result = 0; @@ -168,7 +168,7 @@ get_glyph_entry_index(GlyphCache *gc, u32 cp) return result; } -static GlyphCacheStats +function GlyphCacheStats get_and_clear_glyph_cache_stats(GlyphCache *gc) { GlyphCacheStats result = gc->stats; @@ -176,7 +176,7 @@ get_and_clear_glyph_cache_stats(GlyphCache *gc) return result; } -static u32 * +function u32 * render_glyph(Arena *a, FontAtlas *fa, u32 cp, u32 font_id, enum face_style style, CachedGlyph **out_glyph) { BEGIN_TIMED_BLOCK(); @@ -323,19 +323,19 @@ end: return rgba_bitmap; } -static FontInfo +function FontInfo compute_font_info(Font *font, u32 font_size) { FontInfo result = {0}; - static s8 ascii = s8(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"\ - "`abcdefghijklmnopqrstuvwxyz{|}~"); + local_persist s8 ascii = s8(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"); f32 scale = stbtt_ScaleForMappingEmToPixels(font->font_info, font_size); i32 min_y = 0; i32 max_height = 0; f32 width = 0; - for (size i = 0; i < ascii.len; i++) { + for (iz i = 0; i < ascii.len; i++) { u32 glyph_idx = stbtt_FindGlyphIndex(font->font_info, ascii.data[i]); i32 x0, y0, x1, y1, advance, left_bearing; stbtt_GetGlyphBitmapBoxSubpixel(font->font_info, glyph_idx, scale, scale, 0, 0, @@ -348,7 +348,7 @@ compute_font_info(Font *font, u32 font_size) } u32 graphic_0_count = 0; - for (size i = 0; i < ARRAY_COUNT(graphic_0); i++) { + for (iz i = 0; i < ARRAY_COUNT(graphic_0); i++) { if (graphic_0[i] == 0) continue; graphic_0_count++; @@ -371,7 +371,7 @@ compute_font_info(Font *font, u32 font_size) return result; } -static void +function void font_atlas_update(FontAtlas *fa, iv2 glyph_bitmap_dim) { GlyphCache *gc = &fa->glyph_cache; @@ -396,7 +396,7 @@ font_atlas_update(FontAtlas *fa, iv2 glyph_bitmap_dim) gc->glyphs[i].next_with_same_hash = i + 1; } -static void +function void shift_font_sizes(FontAtlas *fa, i32 size_delta) { g_font_size += size_delta; @@ -416,7 +416,7 @@ shift_font_sizes(FontAtlas *fa, i32 size_delta) } } -static void +function void init_fonts(FontAtlas *fa, Arena *a, iv2 glyph_bitmap_dim) { fa->nfonts = 0; diff --git a/intrinsics.c b/intrinsics.c @@ -1,6 +1,11 @@ #define FORCE_INLINE inline __attribute__((always_inline)) -static FORCE_INLINE u32 +#define atomic_and(ptr, n) __atomic_and_fetch(ptr, n, __ATOMIC_RELEASE); +#define atomic_fetch_add(ptr, n) __atomic_fetch_add(ptr, n, __ATOMIC_RELEASE); +#define atomic_load(ptr) __atomic_load_n(ptr, __ATOMIC_ACQUIRE) +#define atomic_exchange_n(ptr, val) __atomic_exchange_n(ptr, val, __ATOMIC_SEQ_CST) + +function FORCE_INLINE u32 clz_u32(u32 a) { u32 result = 32; @@ -8,7 +13,7 @@ clz_u32(u32 a) return result; } -static FORCE_INLINE u32 +function FORCE_INLINE u32 ctz_u32(u32 a) { u32 result = 32; @@ -20,13 +25,14 @@ ctz_u32(u32 a) /* TODO? debuggers just loop here forever and need a manual PC increment (jump +1 in gdb) */ #define debugbreak() asm volatile ("brk 0xf000") -static FORCE_INLINE u64 +function FORCE_INLINE u64 rdtsc(void) { register u64 cntvct asm("x0"); asm volatile ("mrs x0, cntvct_el0" : "=x"(cntvct)); return cntvct; } + #elif __x86_64__ #include <immintrin.h> diff --git a/os.h b/os.h @@ -0,0 +1,84 @@ +/* See LICENSE for copyright details */ +#ifndef _OS_H_ +#define _OS_H_ + +#define INVALID_FILE (-1) +typedef enum { + OS_FA_READ = 1 << 0, + OS_FA_WRITE = 1 << 1, + OS_FA_APPEND = 1 << 2, +} OSFileAttribute; + +/* NOTE(rnp): if your platform doesn't support secondary clipboard just push to primary */ +typedef enum { + OS_CLIPBOARD_PRIMARY, + OS_CLIPBOARD_SECONDARY, +} OSClipboard; + +typedef struct { void *memory; iz size; } OSMemoryBlock; + +/* NOTE: virtual memory ring buffer */ +typedef struct { + iz capacity; + iz filled; + iz write_index; + u8 *data; +} OSRingBuffer; + +/* NOTE: for now we will do the callback route but this will change if we do multithreading */ +#define OS_FILE_WATCH_CALLBACK_FN(name) void name(u8 *path, void *user_ctx) +typedef OS_FILE_WATCH_CALLBACK_FN(os_file_watch_callback_fn); + +#define OS_ADD_FILE_WATCH_FN(name) void name(u8 *path, os_file_watch_callback_fn *fn, void *user_ctx) +typedef OS_ADD_FILE_WATCH_FN(os_add_file_watch_fn); + +#define OS_ALLOCATE_RING_BUFFER_FN(name) OSRingBuffer name(iz capacity) +typedef OS_ALLOCATE_RING_BUFFER_FN(os_allocate_ring_buffer_fn); + +#define OS_GET_CLIPBOARD_FN(name) u8 *name(OSClipboard clipboard) +typedef OS_GET_CLIPBOARD_FN(os_get_clipboard_fn); + +#define OS_SET_CLIPBOARD_FN(name) b32 name(u8 *buffer, iz length, OSClipboard clipboard) +typedef OS_SET_CLIPBOARD_FN(os_set_clipboard_fn); + +#define OS_READ_FILE_FN(name) s8 name(u8 *path, Arena *a) +typedef OS_READ_FILE_FN(os_read_file_fn); + +/* TODO: this should possibly just take a stream buffer */ +#define OS_READ_FN(name) iz name(iptr file, s8 buffer) +typedef OS_READ_FN(os_read_fn); + +#define OS_SET_TERMINAL_SIZE_FN(name) void name(iptr child, i32 rows, i32 columns, \ + i32 window_width, i32 window_height) +typedef OS_SET_TERMINAL_SIZE_FN(os_set_terminal_size_fn); + +#define OS_GET_WINDOW_TITLE_FN(name) u8 *name(void) +typedef OS_GET_WINDOW_TITLE_FN(os_get_window_title_fn); + +#define OS_SET_WINDOW_TITLE_FN(name) void name(s8 title) +typedef OS_SET_WINDOW_TITLE_FN(os_set_window_title_fn); + +#define OS_WRITE_FN(name) b32 name(iptr file, s8 raw) +typedef OS_WRITE_FN(os_write_fn); + +#define OS_FUNCTIONS \ + X(add_file_watch) \ + X(allocate_ring_buffer) \ + X(get_clipboard) \ + X(set_clipboard) \ + X(read_file) \ + X(read) \ + X(set_terminal_size) \ + X(get_window_title) \ + X(set_window_title) \ + X(write) + +typedef struct { +#define X(name) os_ ## name ## _fn *name; + OS_FUNCTIONS +#undef X + + u8 path_separator; +} OS; + +#endif /* _OS_H_ */ diff --git a/os_linux_aarch64.c b/os_linux_aarch64.c @@ -0,0 +1,175 @@ +/* TODO: generate this whole file with a metaprogram */ + +/* See LICENSE for license details. */ +#ifndef asm +#ifdef __asm +#define asm __asm +#else +#define asm __asm__ +#endif +#endif + +typedef enum { + SYS_dup3 = 24, + SYS_inotify_init1 = 26, + SYS_inotify_add_watch = 27, + SYS_inotify_rm_watch = 28, + SYS_ioctl = 29, + SYS_ftruncate = 46, + SYS_openat = 56, + SYS_close = 57, + SYS_read = 63, + SYS_write = 64, + SYS_pwrite64 = 68, + SYS_pselect6 = 72, + SYS_exit = 93, + SYS_exit_group = 94, + SYS_futex = 98, + SYS_clock_gettime = 113, + SYS_setsid = 157, + SYS_prctl = 167, + SYS_munmap = 215, + SYS_clone = 220, + SYS_execve = 221, + SYS_mmap = 222, + SYS_mprotect = 226, + SYS_madvise = 233, + SYS_wait4 = 260, + SYS_memfd_create = 279, + SYS_statx = 291, +} AArch64Syscall; + +#define SIGCHLD 17 + +/* NOTE(rnp): technically arm64 can have 4K, 16K or 64K pages but we will just assume 64K */ +#define PAGE_SIZE 65536 + +/* TODO: check that this is equivalent */ +typedef u64 sys_fd_set[16]; + +function FORCE_INLINE u64 +syscall0(AArch64Syscall n) +{ + register i64 x8 asm("x8") = n; + register u64 x0 asm("x0"); + asm volatile ("svc 0" + : "=x"(x0) + : "x"(x8) + : "memory", "cc" + ); + return x0; +} + +function FORCE_INLINE u64 +syscall1(AArch64Syscall n, i64 a1) +{ + register i64 x8 asm("x8") = n; + register u64 x0 asm("x0") = a1; + asm volatile ("svc 0" + : "+x"(x0) + : "x"(x8) + : "memory", "cc" + ); + return x0; +} + +function FORCE_INLINE u64 +syscall2(AArch64Syscall n, i64 a1, i64 a2) +{ + register i64 x8 asm("x8") = n; + register u64 x0 asm("x0") = a1; + register i64 x1 asm("x1") = a2; + asm volatile ("svc 0" + : "+x"(x0) + : "x"(x8), "x"(x1) + : "memory", "cc" + ); + return x0; +} + +function FORCE_INLINE u64 +syscall3(AArch64Syscall n, i64 a1, i64 a2, i64 a3) +{ + register i64 x8 asm("x8") = n; + register u64 x0 asm("x0") = a1; + register i64 x1 asm("x1") = a2; + register i64 x2 asm("x2") = a3; + asm volatile ("svc 0" + : "+x"(x0) + : "x"(x8), "x"(x1), "x"(x2) + : "memory", "cc" + ); + return x0; +} + +function FORCE_INLINE u64 +syscall4(AArch64Syscall n, i64 a1, i64 a2, i64 a3, i64 a4) +{ + register i64 x8 asm("x8") = n; + register u64 x0 asm("x0") = a1; + register i64 x1 asm("x1") = a2; + register i64 x2 asm("x2") = a3; + register i64 x3 asm("x3") = a4; + asm volatile ("svc 0" + : "+x"(x0) + : "x"(x8), "x"(x1), "x"(x2), "x"(x3) + : "memory", "cc" + ); + return x0; +} + +function FORCE_INLINE u64 +syscall5(AArch64Syscall n, i64 a1, i64 a2, i64 a3, i64 a4, i64 a5) +{ + register i64 x8 asm("x8") = n; + register u64 x0 asm("x0") = a1; + register i64 x1 asm("x1") = a2; + register i64 x2 asm("x2") = a3; + register i64 x3 asm("x3") = a4; + register i64 x4 asm("x4") = a5; + asm volatile ("svc 0" + : "+x"(x0) + : "x"(x8), "x"(x1), "x"(x2), "x"(x3), "x"(x4) + : "memory", "cc" + ); + return x0; +} + +function FORCE_INLINE u64 +syscall6(AArch64Syscall n, i64 a1, i64 a2, i64 a3, i64 a4, i64 a5, i64 a6) +{ + register i64 x8 asm("x8") = n; + register u64 x0 asm("x0") = a1; + register i64 x1 asm("x1") = a2; + register i64 x2 asm("x2") = a3; + register i64 x3 asm("x3") = a4; + register i64 x4 asm("x4") = a5; + register i64 x5 asm("x5") = a6; + asm volatile ("svc 0" + : "+x"(x0) + : "x"(x8), "x"(x1), "x"(x2), "x"(x3), "x"(x4), "x"(x5) + : "memory", "cc" + ); + return x0; +} + +__attribute__((naked)) +function i64 +new_thread(void *stack_base) +{ + asm volatile ( + "mov x8, #220\n" // SYS_clone + "mov x1, x0\n" // arg2 = new stack + "mov x0, #0xF00\n" // arg1 = clone flags (VM|FS|FILES|SIGHAND|THREAD|SYSVMEM) + "movk x0, #0x5, lsl #16\n" // no 32 bit immediates in general on arm + "svc 0\n" + "cbnz x0, 1f\n" // don't clobber syscall return in calling thread + "mov x0, sp\n" + "ldr x1, [sp]\n" // arm doesn't take the return address from the stack; + "blr x1\n" // we need to load it and branch to it + "1: ret" + ::: "x8", "x1", "memory", "cc" + ); +} + +#include "platform_linux_common.c" diff --git a/os_linux_amd64.c b/os_linux_amd64.c @@ -0,0 +1,165 @@ +/* See LICENSE for license details. */ +#ifndef asm +#ifdef __asm +#define asm __asm +#else +#define asm __asm__ +#endif +#endif + +/* TODO: X macro that defines all of these with the appropriate function/macro */ +typedef enum { + SYS_read = 0, + SYS_write = 1, + SYS_close = 3, + SYS_mmap = 9, + SYS_mprotect = 10, + SYS_munmap = 11, + SYS_ioctl = 16, + SYS_pwrite64 = 18, + SYS_madvise = 28, + SYS_clone = 56, + SYS_execve = 59, + SYS_exit = 60, + SYS_wait4 = 61, + SYS_ftruncate = 77, + SYS_setsid = 112, + SYS_prctl = 157, + SYS_futex = 202, + SYS_getdents64 = 217, + SYS_clock_gettime = 228, + SYS_exit_group = 231, + SYS_inotify_add_watch = 254, + SYS_inotify_rm_watch = 255, + SYS_openat = 257, + SYS_pselect6 = 270, + SYS_dup3 = 292, + SYS_inotify_init1 = 294, + SYS_memfd_create = 319, + SYS_statx = 332, +} AMD64Syscall; + +#define SIGCHLD 17 + +#define PAGE_SIZE 4096 + +typedef u64 sys_fd_set[16]; + +#define DIRENT_RECLEN_OFF 16 +#define DIRENT_TYPE_OFF 18 +#define DIRENT_NAME_OFF 19 + +function FORCE_INLINE u64 +syscall1(AMD64Syscall n, i64 a1) +{ + u64 result; + asm volatile ("syscall" + : "=a"(result) + : "a"(n), "D"(a1) + : "rcx", "r11", "memory" + ); + return result; +} + +function FORCE_INLINE u64 +syscall2(AMD64Syscall n, i64 a1, i64 a2) +{ + i64 result; + asm volatile ("syscall" + : "=a"(result) + : "a"(n), "D"(a1), "S"(a2) + : "rcx", "r11", "memory" + ); + return result; +} + +function FORCE_INLINE u64 +syscall3(AMD64Syscall n, i64 a1, i64 a2, i64 a3) +{ + u64 result; + asm volatile ("syscall" + : "=a"(result) + : "a"(n), "D"(a1), "S"(a2), "d"(a3) + : "rcx", "r11", "memory" + ); + return result; +} + +function FORCE_INLINE u64 +syscall4(AMD64Syscall n, i64 a1, i64 a2, i64 a3, i64 a4) +{ + u64 result; + register i64 r10 asm("r10") = a4; + asm volatile ("syscall" + : "=a"(result) + : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10) + : "rcx", "r11", "memory" + ); + return result; +} + + +function FORCE_INLINE u64 +syscall5(AMD64Syscall n, i64 a1, i64 a2, i64 a3, i64 a4, i64 a5) +{ + u64 result; + register i64 r10 asm("r10") = a4; + register i64 r8 asm("r8") = a5; + asm volatile ("syscall" + : "=a"(result) + : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10), "r"(r8) + : "rcx", "r11", "memory" + ); + return result; +} + +function FORCE_INLINE u64 +syscall6(AMD64Syscall n, i64 a1, i64 a2, i64 a3, i64 a4, i64 a5, i64 a6) +{ + i64 result; + register i64 r10 asm("r10") = a4; + register i64 r8 asm("r8") = a5; + register i64 r9 asm("r9") = a6; + asm volatile ("syscall" + : "=a"(result) + : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10), "r"(r8), "r"(r9) + : "rcx", "r11", "memory" + ); + return result; +} + +/* NOTE: based on code from nullprogram (Chris Wellons) */ +__attribute__((naked)) +function i64 +new_thread(void *stack_base) +{ + asm volatile ( + "mov %%rdi, %%rsi\n" // arg2 = new stack + "mov $0x50F00, %%edi\n" // arg1 = clone flags (VM|FS|FILES|SIGHAND|THREAD|SYSVMEM) + "mov $56, %%eax\n" // SYS_clone + "syscall\n" + "test %%eax, %%eax\n" // don't mess with the calling thread's stack + "jne 1f\n" + "mov %%rsp, %%rdi\n" + "sub $8, %%rsp\n" // place a 0 return branch pointer on the child's stack + "push (%%rdi)\n" // push the entry point back onto the stack for use by ret + "1: ret\n" + ::: "rax", "rcx", "rsi", "rdi", "r11", "memory" + ); +} + +#include "os_linux_common.c" + +#if 0 +asm ( + ".intel_syntax noprefix\n" + ".global _start\n" + "_start:\n" + " mov edi, DWORD PTR [rsp]\n" + " lea rsi, [rsp+8]\n" + " lea rdx, [rsi+rdi*8+8]\n" + " call linux_main\n" + " ud2\n" + ".att_syntax\n" +); +#endif diff --git a/os_linux_common.c b/os_linux_common.c @@ -0,0 +1,559 @@ +#define FUTEX_WAIT 0 +#define FUTEX_WAKE 1 + +#define CLOCK_MONOTONIC 1 + +#define PR_SET_NAME 15 + +#define PROT_NONE 0x00 +#define PROT_READ 0x01 +#define PROT_RW 0x03 + +#define MFD_CLOEXEC 0x01 + +#define MAP_SHARED 0x01 +#define MAP_PRIVATE 0x02 +#define MAP_FIXED 0x10 +#define MAP_ANON 0x20 + +#define MADV_FREE 8 +#define MADV_HUGEPAGE 14 + +#define O_RDONLY 0x00000 +#define O_WRONLY 0x00001 +#define O_RDWR 0x00002 +#define O_CREAT 0x00040 +#define O_NOCTTY 0x00100 +#define O_APPEND 0x00400 +#define O_NONBLOCK 0x00800 +#define O_CLOEXEC 0x80000 + +#define IN_CLOSE_WRITE 0x00000008 +#define IN_CLOSE_NOWRITE 0x00000010 +#define IN_MODIFY 0x00000002 + +#define AT_EMPTY_PATH 0x1000 +#define AT_FDCWD (-100) + +#define LINUX_INOTIFY_MASK (IN_CLOSE_WRITE|IN_CLOSE_NOWRITE|IN_MODIFY) + +#define WNOHANG 1 +#define W_IF_EXITED(s) (!((s) & 0x7F)) + +/* TODO: glibc/gcc indirectly include sys/select.h if you include immintrin.h. If that + * header is removed this can also be removed */ +#undef FD_SET +#undef FD_ISSET + +#define FD_SET(d, s) ((s)[(d) / (8 * sizeof(*(s)))] |= (1ULL << ((d) % (8 * sizeof(*(s)))))) +#define FD_ISSET(d, s) ((s)[(d) / (8 * sizeof(*(s)))] & (1ULL << ((d) % (8 * sizeof(*(s)))))) + +typedef __attribute__((aligned(16))) u8 statx_buffer[256]; +#define STATX_BUF_MEMBER(sb, t, off) (*(t *)((u8 *)(sb) + off)) +#define STATX_INODE(sb) STATX_BUF_MEMBER(sb, u64, 32) +#define STATX_FILE_SIZE(sb) STATX_BUF_MEMBER(sb, u64, 40) + +#define STATX_INO 0x00000100U +#define STATX_SIZE 0x00000200U + +#define TIOCSCTTY 0x540E +#define TIOCSWINSZ 0x5414 +#define TIOCSPTLCK 0x40045431 /* (un)lock pty */ +#define TIOCGPTN 0x80045430 /* get pty number */ + +#ifndef VERSION +#define VERSION "unknown" +#endif + +#define OS_MAP_READ PROT_READ +#define OS_MAP_PRIVATE MAP_PRIVATE + +struct __attribute__((aligned(16))) stack_base { + void (*entry)(struct stack_base *stack); + Arena thread_arena; + void *window; + TerminalMemory *terminal_memory; + TerminalInput *input; + i32 work_futex; + b32 thread_asleep; +}; + +typedef struct { + os_file_watch_callback_fn *fn; + u8 *path; + void *user_ctx; + u64 inode; + i32 handle; +} linux_file_watch; + +typedef struct linux_deferred_file_reload_queue { + struct linux_deferred_file_reload_queue *next; + struct linux_deferred_file_reload_queue *last; + i32 index; + i32 failures; +} linux_deferred_file_reload_queue; + +typedef struct { + iptr handle; + iptr process_id; +} linux_platform_process; + +typedef struct { + Arena platform_memory; + void *window; + + TerminalMemory memory; + TerminalInput input; + + Stream char_stream; + + linux_platform_process child; + i32 inotify_fd; + i32 win_fd; + + linux_deferred_file_reload_queue file_reload_queue; + linux_deferred_file_reload_queue *file_reload_free_list; + linux_file_watch file_watches[32]; + i32 file_watch_count; + + Stream error_stream; + + struct stack_base *render_stack; + +#ifdef _DEBUG + void *library_handle; +#endif +} PlatformCtx; +global PlatformCtx linux_ctx; + +function void +os_write_err_msg(s8 msg) +{ + syscall3(SYS_write, 2, (iptr)msg.data, msg.len); +} + +__attribute__((noreturn)) +function void +os_fatal(s8 msg) +{ + os_write_err_msg(msg); + syscall1(SYS_exit_group, 1); + __builtin_unreachable(); +} + +function u32 +os_file_attribute_to_mode(u32 attr) +{ + u32 result = O_CREAT; + if (attr & OS_FA_READ && attr & OS_FA_WRITE) { + result |= O_RDWR; + } else if (attr & OS_FA_READ) { + result |= O_RDONLY; + } else if (attr & OS_FA_WRITE) { + result |= O_WRONLY; + } + + if (attr & OS_FA_APPEND) + result |= O_APPEND; + + return result; +} + +function iptr +os_open(u8 *name, u32 attr) +{ + u64 result = syscall4(SYS_openat, AT_FDCWD, (iptr)name, os_file_attribute_to_mode(attr), 0660); + if (result > -4096UL) + result = INVALID_FILE; + return result; +} + +function b32 +os_offset_write(iptr file, s8 raw, iz offset) +{ + iz result = syscall4(SYS_pwrite64, file, (iptr)raw.data, raw.len, offset); + return result == raw.len; +} + +function OS_WRITE_FN(os_write) +{ + iz result = syscall3(SYS_write, file, (iptr)raw.data, raw.len); + return result == raw.len; +} + +function void +os_close(iptr file) +{ + syscall1(SYS_close, file); +} + +function OS_READ_FN(os_read) +{ + u64 r = 0, remaining = buffer.len, total_bytes_read = 0; + + do { + remaining -= r; + total_bytes_read += r; + r = syscall3(SYS_read, file, (iptr)(buffer.data + total_bytes_read), remaining); + } while (r <= -4096UL && remaining != 0); + + return total_bytes_read; +} + +function OS_READ_FILE_FN(os_read_file) +{ + s8 result = {0}; + + statx_buffer sb; + u64 fd = syscall4(SYS_openat, AT_FDCWD, (iptr)path, O_RDONLY, 0); + u64 status = syscall5(SYS_statx, fd, 0, AT_EMPTY_PATH, STATX_SIZE, (iptr)sb); + + if (fd <= -4096UL && status == 0) { + result = s8alloc(a, STATX_FILE_SIZE(sb)); + iz rlen = os_read(fd, result); + syscall1(SYS_close, fd); + if (result.len != rlen) + result.len = 0; + } + + return result; +} + +function OSMemoryBlock +os_block_alloc(iz requested_size) +{ + OSMemoryBlock result = {0}; + + /* TODO: query system for HUGETLB support and use those instead of page size */ + iz alloc_size = requested_size; + if (alloc_size % PAGE_SIZE != 0) + alloc_size += PAGE_SIZE - alloc_size % PAGE_SIZE; + + u64 memory = syscall6(SYS_mmap, 0, alloc_size, PROT_RW, MAP_ANON|MAP_PRIVATE, -1, 0); + if (memory <= -4096UL) { + result.memory = (void *)memory; + result.size = alloc_size; + syscall3(SYS_madvise, memory, alloc_size, MADV_HUGEPAGE); + } + + return result; +} + +function void +os_release_memory_block(OSMemoryBlock memory) +{ + syscall3(SYS_madvise, (iptr)memory.memory, memory.size, MADV_FREE); + syscall3(SYS_mprotect, (iptr)memory.memory, memory.size, PROT_NONE); +} + +function void +os_release_ring_buffer(OSRingBuffer *rb) +{ + syscall2(SYS_munmap, (iptr)(rb->data - rb->capacity), rb->capacity * 3); +} + +function f64 +os_get_time(void) +{ + i64 timespec[2]; + syscall2(SYS_clock_gettime, CLOCK_MONOTONIC, (iptr)timespec); + f64 result = timespec[0] + ((f64)timespec[1]) * 1e-9; + return result; +} + +function os_mapped_file +os_map_file(char *path, i32 mode, i32 perm) +{ + os_mapped_file result = {0}; + + i32 open_mode = 0; + switch (mode) { + case OS_MAP_READ: open_mode = O_RDONLY; break; + default: ASSERT(0); + } + + statx_buffer sb; + u64 fd = syscall4(SYS_openat, AT_FDCWD, (iptr)path, open_mode, 0); + u64 status = syscall5(SYS_statx, fd, 0, AT_EMPTY_PATH, STATX_SIZE, (iptr)sb); + + if (fd <= -4096UL && status == 0) { + u64 memory = syscall6(SYS_mmap, 0, STATX_FILE_SIZE(sb), mode, perm, fd, 0); + if (memory <= -4096UL) { + result.data = (u8 *)memory; + result.len = STATX_FILE_SIZE(sb); + } + syscall1(SYS_close, fd); + } + + return result; +} + +function OS_ALLOCATE_RING_BUFFER_FN(os_allocate_ring_buffer) +{ + OSRingBuffer result = {0}; + /* TODO: query system for HUGETLB support and use those instead of page size */ + if (capacity % PAGE_SIZE != 0) + capacity += PAGE_SIZE - capacity % PAGE_SIZE; + ASSERT(capacity % PAGE_SIZE == 0); + + u64 fd = syscall2(SYS_memfd_create, (iptr)"vtgl:rb", MFD_CLOEXEC); + if (fd > -4096UL) os_fatal(s8("os_alloc_ring_buffer: failed to open mem_fd\n")); + syscall2(SYS_ftruncate, fd, capacity); + + result.capacity = capacity; + result.data = (u8 *)syscall6(SYS_mmap, 0, (iptr)(3 * capacity), 0, MAP_ANON|MAP_PRIVATE, -1, 0); + if ((u64)result.data > -4096UL) + os_fatal(s8("os_alloc_ring_buffer: initial mmap failed\n")); + syscall3(SYS_madvise, (iptr)result.data, 3 * capacity, MADV_HUGEPAGE); + + for (i32 i = 0; i < 3; i++) { + u64 memory = syscall6(SYS_mmap, (iptr)(result.data + i * capacity), capacity, + PROT_RW, MAP_FIXED|MAP_SHARED, fd, 0); + if (memory > -4096UL) { + u8 buf[256]; + Stream err = {.data = buf, .capacity = sizeof(buf)}; + stream_push_s8(&err, s8("os_alloc_ring_buffer: mmap(")); + stream_push_u64(&err, i); + stream_push_s8(&err, s8(") failed\n")); + os_fatal(stream_to_s8(&err)); + } + } + syscall1(SYS_close, fd); + + /* NOTE: start in the middle page */ + result.data += result.capacity; + + return result; +} + +function b32 +os_child_exited(iptr pid) +{ + i64 status; + i64 r = syscall4(SYS_wait4, pid, (iptr)&status, WNOHANG, 0); + return r == pid && W_IF_EXITED(status); +} + +function linux_platform_process +os_fork_child(s8 cmd, c8 **envp) +{ + i32 n = 0; + + /* NOTE: we open in non-blocking mode so that we can try and fully drain the pipe + * before processing. Otherwise a single read will be limited to the page size */ + u64 m = syscall4(SYS_openat, AT_FDCWD, (iptr)"/dev/ptmx", O_RDWR|O_NOCTTY|O_NONBLOCK|O_CLOEXEC, 0); + if (m > -4096UL) os_fatal(s8("os_fork_child: failed to open master terminal\n")); + /* NOTE: first unlock the tty, then get a valid pty number */ + if (syscall3(SYS_ioctl, m, TIOCSPTLCK, (iptr)&n) || syscall3(SYS_ioctl, m, TIOCGPTN, (iptr)&n)) + os_fatal(s8("os_fork_child: failed to get a pty number\n")); + + u8 buffer[20] = {"/dev/pts/"}; + Stream sbuf = {.data = buffer, .capacity = 20, .count = sizeof("/dev/pts/") - 1}; + stream_push_i64(&sbuf, n); + stream_push_byte(&sbuf, 0); + + u64 s = syscall4(SYS_openat, AT_FDCWD, (iptr)sbuf.data, O_RDWR|O_NOCTTY, 0); + if (s > -4096UL) os_fatal(s8("os_fork_child: failed to open slave terminal\n")); + + u64 pid = syscall2(SYS_clone, SIGCHLD, 0); + if (pid > -4096UL) os_fatal(s8("os_fork_child: failed to fork a child\n")); + + if (pid == 0) { + syscall1(SYS_setsid, 0); + syscall3(SYS_dup3, s, 0, 0); + syscall3(SYS_dup3, s, 1, 0); + syscall3(SYS_dup3, s, 2, 0); + syscall3(SYS_ioctl, s, TIOCSCTTY, 0); + if (s > 2) syscall1(SYS_close, s); + ASSERT(cmd.data[cmd.len] == 0); + u8 *argv[] = {cmd.data, 0}; + syscall3(SYS_execve, (iptr)cmd.data, (iptr)argv, (iptr)envp); + __builtin_unreachable(); + os_fatal(s8("failed to exec child\n")); + } + syscall1(SYS_close, s); + + return (linux_platform_process){.process_id = pid, .handle = m}; +} + +function OS_SET_TERMINAL_SIZE_FN(os_set_terminal_size) +{ + u16 win_size[4]; + win_size[0] = rows; + win_size[1] = columns; + win_size[2] = window_width; + win_size[3] = window_height; + if (syscall3(SYS_ioctl, child, TIOCSWINSZ, (iptr)win_size) > -4096UL) + os_write_err_msg(s8("os_set_term_size\n")); +} + +function OS_ADD_FILE_WATCH_FN(linux_add_file_watch) +{ + u64 wd = syscall3(SYS_inotify_add_watch, linux_ctx.inotify_fd, (iptr)path, LINUX_INOTIFY_MASK); + if (wd <= -4096UL) { + statx_buffer sb; + syscall5(SYS_statx, AT_FDCWD, (iptr)path, 0, STATX_INO, (iptr)sb); + + 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 = STATX_INODE(sb); + linux_ctx.file_watches[idx].user_ctx = user_ctx; + } +} + +function void +try_deferred_file_loads(PlatformCtx *ctx) +{ + linux_deferred_file_reload_queue *file = ctx->file_reload_queue.next; + while (file) { + linux_file_watch *fw = ctx->file_watches + file->index; + + statx_buffer sb; + syscall5(SYS_statx, AT_FDCWD, (iptr)fw->path, 0, STATX_INO, (iptr)sb); + + fw->handle = syscall3(SYS_inotify_add_watch, ctx->inotify_fd, (iptr)fw->path, + LINUX_INOTIFY_MASK); + fw->inode = STATX_INODE(sb); + + if ((u64)fw->handle <= -4096UL) { + fw->fn(fw->path, fw->user_ctx); + file->last->next = file->next; + file->next = ctx->file_reload_free_list; + ctx->file_reload_free_list = file; + file = file->last; + } else { + file->failures++; + #if 0 + TODO + if (file->failures > MAX_FILE_RELOAD_TRIES) { + log + remove from list + } + #endif + } + file = file->next; + } +} + +function b32 +defer_file_reload(PlatformCtx *ctx, i32 file_watch_index, statx_buffer *sb) +{ + b32 result = 1; + linux_file_watch *fw = ctx->file_watches + file_watch_index; + + fw->inode = STATX_INODE(*sb); + fw->handle = syscall3(SYS_inotify_add_watch, ctx->inotify_fd, (iptr)fw->path, LINUX_INOTIFY_MASK); + + if ((u64)fw->handle > -4096UL) { + result = 0; + + linux_deferred_file_reload_queue *new = ctx->file_reload_free_list; + if (new) ctx->file_reload_free_list = new->next; + else new = push_struct(&ctx->platform_memory, typeof(*new)); + new->index = file_watch_index; + new->failures = 0; + DLLPushDown(&ctx->file_reload_queue, new); + } + + return result; +} + +function void +dispatch_file_watch_events(PlatformCtx *ctx) +{ + struct { + i32 wd; + u32 mask, cookie, len; + c8 name[]; + } *ie; + + u8 *mem = alloc_(&ctx->platform_memory, 4096, 64, 1); + s8 buf = {.len = 4096, .data = mem}; + + for (;;) { + iz rlen = syscall3(SYS_read, ctx->inotify_fd, (iptr)buf.data, buf.len); + if (rlen <= 0) + break; + + for (u8 *data = buf.data; data < buf.data + rlen; data += sizeof(*ie) + ie->len) { + ie = (void *)data; + for (i32 i = 0; i < ctx->file_watch_count; i++) { + linux_file_watch *fw = ctx->file_watches + i; + if (fw->handle != ie->wd) + continue; + + 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 */ + statx_buffer sb; + u64 status = syscall5(SYS_statx, AT_FDCWD, (iptr)fw->path, 0, STATX_INO, (iptr)sb); + + if (status > -4096UL || fw->inode != STATX_INODE(sb)) { + syscall2(SYS_inotify_rm_watch, ctx->inotify_fd, fw->handle); + fw->handle = INVALID_FILE; + file_changed = defer_file_reload(ctx, i, &sb); + } + if (file_changed) + fw->fn(fw->path, fw->user_ctx); + } + } + } +} + +function struct stack_base * +new_stack(iz capacity) +{ + u64 p = syscall6(SYS_mmap, 0, capacity, PROT_RW, MAP_ANON|MAP_PRIVATE, -1, 0); + if (p > -4096UL) + os_fatal(s8("new_stack: mmap failed\n")); + i64 count = capacity / sizeof(struct stack_base); + /* NOTE: remember the stack grows down; we want to start at the highest address */ + struct stack_base *result = (struct stack_base *)p + count - 1; + return result; +} + +function void +usage(char *argv0, Stream *err) +{ + stream_push_s8(err, s8("usage: ")); + stream_push_s8(err, c_str_to_s8(argv0)); + stream_push_s8(err, s8(" [-v] [-g COLxROW]\n")); + os_fatal(stream_to_s8(err)); +} + +function s8 +get_default_cmd(char **envp) +{ + s8 result = envp_lookup(s8("SHELL="), envp); + if (result.len == 0) + result = s8("/bin/sh"); + return result; +} + +function SLLVariableVector +parse_environment(Arena *a, char **envp) +{ + SLLVariableVector env = {0}; + for (; *envp; envp++) { + s8 e = c_str_to_s8(*envp); + if (!s8_prefix_of(s8("TERM="), e)) { + Variable *var = push_struct(a, Variable); + var->type = VT_S8; + var->s8 = e; + SLLVariableVectorPush(a, &env, var); + } + } + + Variable *var = push_struct(a, Variable); + var->type = VT_S8; + /* TODO: don't pretend to be xterm ? */ + var->s8 = s8("TERM=xterm"); + SLLVariableVectorPush(a, &env, var); + + return env; +} diff --git a/os_linux_x11.c b/os_linux_x11.c @@ -0,0 +1,526 @@ +/* See LICENSE for copyright details */ +#define GL_GLEXT_PROTOTYPES +#include <GLFW/glfw3.h> + +/* TODO: fix glfw */ +typedef void *RROutput; +typedef void *RRCrtc; +typedef void *Display; +typedef void *Window; + +#define GLFW_EXPOSE_NATIVE_X11 +#define GLFW_NATIVE_INCLUDE_NONE +#include <GLFW/glfw3native.h> + +#include "vtgl.h" + +i32 XConnectionNumber(void *display); +i32 XPending(void *display); + +#ifndef _DEBUG +#define do_debug(...) +#include "vtgl.c" +#else +#include <dlfcn.h> + +#define DEBUG_LIB_NAME "./vtgl.so" + +#define LIB_FNS \ + X(vtgl_active_selection) \ + X(vtgl_initialize) \ + X(vtgl_render_frame) \ + X(vtgl_handle_keys) \ + X(vtgl_frame_step) + +#define X(name) static name ## _fn *name; +LIB_FNS +#undef X + +function OS_FILE_WATCH_CALLBACK_FN(debug_reload_library) +{ + PlatformCtx *ctx = user_ctx; + + if (ctx->input.executable_reloaded) + return; + + /* NOTE(rnp): spin until render thread finishes its work */ + while (!ctx->render_stack->thread_asleep); + + ctx->input.executable_reloaded = 1; + s8 nl = s8("\n"); + /* NOTE: glibc sucks and will crash if this is NULL */ + 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(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 + + stream_push_s8(&ctx->error_stream, s8("Reloaded Main Program\n")); + + os_write_err_msg(stream_to_s8(&ctx->error_stream)); + stream_reset(&ctx->error_stream, 0); +} +#endif /* _DEBUG */ + +function void +glfw_error_callback(int code, const char *desc) +{ + u8 buf[1024]; + Stream err = {.capacity = sizeof(buf), .data = buf}; + stream_push_s8(&err, s8("GLFW Error (0x")); + stream_push_hex_u64(&err, code); + stream_push_s8(&err, s8("): ")); + stream_push_s8(&err, c_str_to_s8((char *)desc)); + stream_push_byte(&err, '\n'); + os_write_err_msg(stream_to_s8(&err)); +} + +function void +char_callback(GLFWwindow *win, u32 codepoint) +{ + PlatformCtx *ctx = glfwGetWindowUserPointer(win); + stream_push_s8(&ctx->char_stream, utf8_encode(codepoint)); +} + +/* NOTE: called when the window was resized */ +function void +fb_callback(GLFWwindow *win, i32 w, i32 h) +{ + PlatformCtx *ctx = glfwGetWindowUserPointer(win); + ctx->input.window_size = (iv2){.w = w, .h = h}; +} + +function void +scroll_callback(GLFWwindow *win, f64 xoff, f64 yoff) +{ + PlatformCtx *ctx = glfwGetWindowUserPointer(win); + ctx->input.mouse_scroll.x = xoff; + ctx->input.mouse_scroll.y = yoff; +} + +function void +key_callback(GLFWwindow *win, i32 key, i32 scancode, i32 action, i32 modifiers) +{ + PlatformCtx *ctx = glfwGetWindowUserPointer(win); + TerminalInput *input = &ctx->input; + + /* TODO: base this on X11 keys directly */ + switch (key) { + case GLFW_KEY_LEFT_SHIFT: + button_action(input->keys + KEY_LEFT_SHIFT, action == GLFW_PRESS); + if (action == GLFW_RELEASE) input->modifiers &= ~MOD_SHIFT; + else input->modifiers |= MOD_SHIFT; + break; + case GLFW_KEY_LEFT_CONTROL: + button_action(input->keys + KEY_LEFT_CONTROL, action == GLFW_PRESS); + if (action == GLFW_RELEASE) input->modifiers &= ~MOD_CONTROL; + else input->modifiers |= MOD_CONTROL; + break; + case GLFW_KEY_LEFT_ALT: + button_action(input->keys + KEY_LEFT_ALT, action == GLFW_PRESS); + if (action == GLFW_RELEASE) input->modifiers &= ~MOD_ALT; + else input->modifiers |= MOD_ALT; + break; + case GLFW_KEY_LEFT_SUPER: + button_action(input->keys + KEY_LEFT_SUPER, action == GLFW_PRESS); + if (action == GLFW_RELEASE) input->modifiers &= ~MOD_SUPER; + else input->modifiers |= MOD_SUPER; + break; + case GLFW_KEY_RIGHT_SHIFT: + button_action(input->keys + KEY_RIGHT_SHIFT, action == GLFW_PRESS); + if (action == GLFW_RELEASE) input->modifiers &= ~MOD_SHIFT; + else input->modifiers |= MOD_SHIFT; + break; + case GLFW_KEY_RIGHT_CONTROL: + button_action(input->keys + KEY_RIGHT_CONTROL, action == GLFW_PRESS); + if (action == GLFW_RELEASE) input->modifiers &= ~MOD_CONTROL; + else input->modifiers |= MOD_CONTROL; + break; + case GLFW_KEY_RIGHT_ALT: + button_action(input->keys + KEY_RIGHT_ALT, action == GLFW_PRESS); + if (action == GLFW_RELEASE) input->modifiers &= ~MOD_ALT; + else input->modifiers |= MOD_ALT; + break; + case GLFW_KEY_RIGHT_SUPER: + button_action(input->keys + KEY_RIGHT_SUPER, action == GLFW_PRESS); + if (action == GLFW_RELEASE) input->modifiers &= ~MOD_SUPER; + else input->modifiers |= MOD_SUPER; + break; + case GLFW_KEY_MENU: + button_action(input->keys + KEY_MENU, action == GLFW_PRESS); + break; + } + vtgl_handle_keys(&ctx->memory, &ctx->input, key, action, modifiers); +} + +function void +mouse_button_callback(GLFWwindow *win, i32 button, i32 action, i32 modifiers) +{ + PlatformCtx *ctx = glfwGetWindowUserPointer(win); + TerminalInput *input = &ctx->input; + + switch (button) { + case GLFW_MOUSE_BUTTON_LEFT: + button_action(input->keys + MOUSE_LEFT, action == GLFW_PRESS); + break; + case GLFW_MOUSE_BUTTON_RIGHT: + button_action(input->keys + MOUSE_RIGHT, action == GLFW_PRESS); + break; + case GLFW_MOUSE_BUTTON_MIDDLE: + button_action(input->keys + MOUSE_MIDDLE, action == GLFW_PRESS); + break; + case GLFW_MOUSE_BUTTON_4: + button_action(input->keys + MOUSE_EXTENDED_0, action == GLFW_PRESS); + break; + case GLFW_MOUSE_BUTTON_5: + button_action(input->keys + MOUSE_EXTENDED_1, action == GLFW_PRESS); + break; + } +} + +function void +focus_callback(GLFWwindow *win, i32 focused) +{ + PlatformCtx *ctx = glfwGetWindowUserPointer(win); + ctx->input.window_focused = focused; + /* NOTE: force a refresh as well when the focus changes */ + ctx->input.window_refreshed = 1; +} + +function void +refresh_callback(GLFWwindow *win) +{ + PlatformCtx *ctx = glfwGetWindowUserPointer(win); + ctx->input.window_refreshed = 1; +} + +function GLFWwindow * +init_window(PlatformCtx *ctx, iv2 window_size) +{ + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + #ifdef _DEBUG + glfwWindowHint(GLFW_CONTEXT_DEBUG, GLFW_TRUE); + #endif + + /* NOTE: we initially hide the window so that it can be freely resized behind the + * back of the window manager prior to showing */ + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + GLFWwindow *window = glfwCreateWindow(window_size.w, window_size.h, "vtgl", NULL, NULL); + if (!window) { + glfwTerminate(); + os_fatal(s8("Failed to spawn GLFW window\n")); + } + glfwMakeContextCurrent(window); + glfwSetWindowUserPointer(window, ctx); + + /* TODO: swap interval is not needed because we will sleep on waiting for terminal input */ + glfwSwapInterval(1); + + glfwSetCharCallback(window, char_callback); + glfwSetFramebufferSizeCallback(window, fb_callback); + glfwSetKeyCallback(window, key_callback); + glfwSetMouseButtonCallback(window, mouse_button_callback); + glfwSetScrollCallback(window, scroll_callback); + glfwSetWindowFocusCallback(window, focus_callback); + glfwSetWindowRefreshCallback(window, refresh_callback); + + ctx->win_fd = XConnectionNumber(glfwGetX11Display()); + + return window; +} + +function void +update_input(PlatformCtx *ctx) +{ + TerminalInput *input = &ctx->input; + ctx->input.executable_reloaded = 0; + ctx->input.window_refreshed = 0; + + /* NOTE: mouse */ + input->last_mouse = input->mouse; + input->mouse_scroll = (v2){0}; + + f64 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; + + for (u32 i = 0; i < ARRAY_COUNT(input->keys); i++) + input->keys[i].transitions = 0; + + stream_reset(&ctx->char_stream, 0); + + i64 timeout[2] = {0, 25e6}; + if (input->pending_updates) { + timeout[1] = 0; + input->pending_updates = 0; + } + + sys_fd_set rfd = {0}; + FD_SET(ctx->child.handle, rfd); + FD_SET(ctx->inotify_fd, rfd); + FD_SET(ctx->win_fd, rfd); + + i32 max_fd = MAX(ctx->inotify_fd, ctx->child.handle); + max_fd = MAX(max_fd, ctx->win_fd); + syscall6(SYS_pselect6, max_fd + 1, (iptr)rfd, 0, 0, (iptr)timeout, 0); + + input->data_available = FD_ISSET(ctx->child.handle, rfd) != 0; + + try_deferred_file_loads(ctx); + if (FD_ISSET(ctx->inotify_fd, rfd)) + dispatch_file_watch_events(ctx); + + glfwPollEvents(); + + /* NOTE: char_stream was filled in char callback */ + input->character_input = stream_to_s8(&ctx->char_stream); +} + +function OS_GET_CLIPBOARD_FN(x11_get_clipboard) +{ + u8 *text = 0; + switch (clipboard) { + case OS_CLIPBOARD_PRIMARY: text = (u8 *)glfwGetClipboardString(0); break; + case OS_CLIPBOARD_SECONDARY: text = (u8 *)glfwGetX11SelectionString(); break; + } + return text; +} + +function OS_SET_CLIPBOARD_FN(x11_set_clipboard) +{ + switch (clipboard) { + case OS_CLIPBOARD_PRIMARY: glfwSetClipboardString(0, (c8 *)buffer); break; + case OS_CLIPBOARD_SECONDARY: glfwSetX11SelectionString((c8 *)buffer); break; + } + return 1; +} + +function OS_GET_WINDOW_TITLE_FN(x11_get_window_title) +{ + u8 *title = (u8 *)glfwGetWindowTitle(linux_ctx.window); + return title; +} + +function OS_SET_WINDOW_TITLE_FN(x11_set_window_title) +{ + glfwSetWindowTitle(linux_ctx.window, (c8 *)title.data); +} + +function void +linux_render_thread_entry(struct stack_base *stack) +{ + { + /* NOTE: set thread name */ + char name[16] = "[render]"; + syscall2(SYS_prctl, PR_SET_NAME, (iptr)name); + /* NOTE: halt until main thread is ready to hand off gl context */ + syscall4(SYS_futex, (iptr)&stack->work_futex, FUTEX_WAIT, 0, 0); + + glfwMakeContextCurrent(stack->window); + } + + for (;;) { + stack->thread_asleep = 1; + syscall4(SYS_futex, (iptr)&stack->work_futex, FUTEX_WAIT, 0, 0); + stack->thread_asleep = 0; + vtgl_render_frame(stack->terminal_memory, stack->input, stack->thread_arena); + glfwSwapBuffers(stack->window); + } + + __builtin_unreachable(); +} + +i32 +main(i32 argc, char *argv[], char *envp[]) +{ + linux_ctx.platform_memory = arena_from_memory_block(os_block_alloc(MB(2))); + linux_ctx.error_stream = stream_alloc(&linux_ctx.platform_memory, KB(256)); + + iv2 cells = {.x = -1, .y = -1}; + + char *argv0 = *argv++; + argc--; + for (i32 i = 0; i < argc; i++) { + char *arg = argv[i]; + if (!arg || !arg[0]) + usage(argv0, &linux_ctx.error_stream); + if (arg[0] != '-') + break; + arg++; + switch (arg[0]) { + case 'g': { + if (!argv[i + 1]) + usage(argv0, &linux_ctx.error_stream); + s8 g_arg = c_str_to_s8(argv[i + 1]); + struct conversion_result cres = s8_parse_i32_until(g_arg, 'x'); + if (cres.status == CR_SUCCESS) + cells.w = cres.i; + + if (cres.unparsed.len > 0 && cres.unparsed.data[0] == 'x') { + s8 remainder = {.len = cres.unparsed.len - 1, + .data = cres.unparsed.data + 1}; + cres = s8_parse_i32(remainder); + if (cres.status == CR_SUCCESS) + cells.h = cres.i; + } + + if (cells.w <= 0 || cells.h <= 0) { + 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(&linux_ctx.error_stream, 2, + (s8 []){c_str_to_s8(argv0), s8(" " VERSION "\n")}); + os_fatal(stream_to_s8(&linux_ctx.error_stream)); + default: + usage(argv0, &linux_ctx.error_stream); + } + } + if (linux_ctx.error_stream.count) { + os_write_err_msg(stream_to_s8(&linux_ctx.error_stream)); + stream_reset(&linux_ctx.error_stream, 0); + } + + linux_ctx.render_stack = new_stack(KB(256)); + linux_ctx.render_stack->entry = linux_render_thread_entry; + linux_ctx.render_stack->thread_asleep = 1; + new_thread(linux_ctx.render_stack); + + { + Arena tmp = linux_ctx.platform_memory; + SLLVariableVector environment_block = parse_environment(&tmp, envp); + + /* TODO: build up argv for the child as well */ + c8 **child_envp = construct_c_str_array(&tmp, environment_block); + linux_ctx.child = os_fork_child(get_default_cmd(envp), child_envp); + } + + { + OSMemoryBlock terminal_memory = os_block_alloc(MB(32)); + linux_ctx.memory.memory = terminal_memory.memory; + linux_ctx.memory.memory_size = terminal_memory.size; +#ifdef _DEBUG + OSMemoryBlock debug_memory = os_block_alloc(MB(128)); + linux_ctx.memory.debug_memory = debug_memory.memory; + linux_ctx.memory.debug_memory_size = debug_memory.size; +#endif + } + + linux_ctx.inotify_fd = syscall1(SYS_inotify_init1, O_NONBLOCK|O_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 + + linux_ctx.memory.os.add_file_watch = linux_add_file_watch; + linux_ctx.memory.os.allocate_ring_buffer = os_allocate_ring_buffer; + linux_ctx.memory.os.get_clipboard = x11_get_clipboard; + linux_ctx.memory.os.set_clipboard = x11_set_clipboard; + linux_ctx.memory.os.read_file = os_read_file; + linux_ctx.memory.os.read = os_read; + linux_ctx.memory.os.set_terminal_size = os_set_terminal_size; + linux_ctx.memory.os.get_window_title = x11_get_window_title; + linux_ctx.memory.os.set_window_title = x11_set_window_title; + linux_ctx.memory.os.write = os_write; + linux_ctx.memory.os.path_separator = '/'; + + if (!glfwInit()) + os_fatal(s8("Failed to init GLFW\n")); + glfwSetErrorCallback(glfw_error_callback); + + GLFWmonitor *mon = glfwGetPrimaryMonitor(); + if (!mon) { + glfwTerminate(); + os_fatal(s8("Failed to find any monitors!\n")); + } + iv2 monitor_size; + glfwGetMonitorWorkarea(mon, NULL, NULL, &monitor_size.w, &monitor_size.h); + + linux_ctx.char_stream = arena_stream(linux_ctx.platform_memory); + + iv2 window_size = {.w = 1280, .h = 720}; + linux_ctx.window = init_window(&linux_ctx, window_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(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(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(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(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(linux_ctx.window); + } + glfwMakeContextCurrent(0); + + linux_ctx.input.window_size = window_size; + + linux_ctx.render_stack->input = &linux_ctx.input; + linux_ctx.render_stack->terminal_memory = &linux_ctx.memory; + linux_ctx.render_stack->thread_arena = arena_from_memory_block(os_block_alloc(MB(8))); + linux_ctx.render_stack->window = linux_ctx.window; + syscall3(SYS_futex, (iptr)&linux_ctx.render_stack->work_futex, FUTEX_WAKE, 1); + + Range last_sel = {0}; + f64 last_time = os_get_time(); + while (!glfwWindowShouldClose(linux_ctx.window)) { + if (os_child_exited(linux_ctx.child.process_id)) + break; + + /* TODO: cpu time excluding waiting for the vblank */ + f64 current_time = os_get_time(); + linux_ctx.input.dt = current_time - last_time; + last_time = current_time; + + update_input(&linux_ctx); + if (vtgl_frame_step(&linux_ctx.memory, &linux_ctx.input)) + syscall3(SYS_futex, (iptr)&linux_ctx.render_stack->work_futex, FUTEX_WAKE, 1); + + 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(linux_ctx.platform_memory); + vtgl_active_selection(&linux_ctx.memory, &buf); + stream_push_byte(&buf, 0); + if (!buf.errors) + glfwSetX11SelectionString((c8 *)buf.data); + last_sel = current_sel; + } + } + + syscall1(SYS_exit_group, 0); + __builtin_unreachable(); + + return 0; +} diff --git a/platform_linux_aarch64.c b/platform_linux_aarch64.c @@ -1,172 +0,0 @@ -/* TODO: generate this whole file with a metaprogram */ - -/* See LICENSE for license details. */ -#ifndef asm -#ifdef __asm -#define asm __asm -#else -#define asm __asm__ -#endif -#endif - -#define SYS_dup3 24 -#define SYS_inotify_init1 26 -#define SYS_inotify_add_watch 27 -#define SYS_inotify_rm_watch 28 -#define SYS_ioctl 29 -#define SYS_ftruncate 46 -#define SYS_openat 56 -#define SYS_close 57 -#define SYS_read 63 -#define SYS_write 64 -#define SYS_pwrite64 68 -#define SYS_pselect6 72 -#define SYS_exit_group 94 -#define SYS_futex 98 -#define SYS_clock_gettime 113 -#define SYS_setsid 157 -#define SYS_prctl 167 -#define SYS_munmap 215 -#define SYS_clone 220 -#define SYS_execve 221 -#define SYS_mmap 222 -#define SYS_mprotect 226 -#define SYS_madvise 233 -#define SYS_wait4 260 -#define SYS_memfd_create 279 -#define SYS_statx 291 - -#define SIGCHLD 17 - -/* NOTE(rnp): technically arm64 can have 4K, 16K or 64K pages but we will just assume 64K */ -#define PAGE_SIZE 65536 - -/* TODO: check that this is equivalent */ -typedef u64 sys_fd_set[16]; - -static FORCE_INLINE u64 -syscall0(i64 n) -{ - register i64 x8 asm("x8") = n; - register u64 x0 asm("x0"); - asm volatile ("svc 0" - : "=x"(x0) - : "x"(x8) - : "memory", "cc" - ); - return x0; -} - -static FORCE_INLINE u64 -syscall1(i64 n, i64 a1) -{ - register i64 x8 asm("x8") = n; - register u64 x0 asm("x0") = a1; - asm volatile ("svc 0" - : "+x"(x0) - : "x"(x8) - : "memory", "cc" - ); - return x0; -} - -static FORCE_INLINE u64 -syscall2(i64 n, i64 a1, i64 a2) -{ - register i64 x8 asm("x8") = n; - register u64 x0 asm("x0") = a1; - register i64 x1 asm("x1") = a2; - asm volatile ("svc 0" - : "+x"(x0) - : "x"(x8), "x"(x1) - : "memory", "cc" - ); - return x0; -} - -static FORCE_INLINE u64 -syscall3(i64 n, i64 a1, i64 a2, i64 a3) -{ - register i64 x8 asm("x8") = n; - register u64 x0 asm("x0") = a1; - register i64 x1 asm("x1") = a2; - register i64 x2 asm("x2") = a3; - asm volatile ("svc 0" - : "+x"(x0) - : "x"(x8), "x"(x1), "x"(x2) - : "memory", "cc" - ); - return x0; -} - -static FORCE_INLINE u64 -syscall4(i64 n, i64 a1, i64 a2, i64 a3, i64 a4) -{ - register i64 x8 asm("x8") = n; - register u64 x0 asm("x0") = a1; - register i64 x1 asm("x1") = a2; - register i64 x2 asm("x2") = a3; - register i64 x3 asm("x3") = a4; - asm volatile ("svc 0" - : "+x"(x0) - : "x"(x8), "x"(x1), "x"(x2), "x"(x3) - : "memory", "cc" - ); - return x0; -} - -static FORCE_INLINE u64 -syscall5(i64 n, i64 a1, i64 a2, i64 a3, i64 a4, i64 a5) -{ - register i64 x8 asm("x8") = n; - register u64 x0 asm("x0") = a1; - register i64 x1 asm("x1") = a2; - register i64 x2 asm("x2") = a3; - register i64 x3 asm("x3") = a4; - register i64 x4 asm("x4") = a5; - asm volatile ("svc 0" - : "+x"(x0) - : "x"(x8), "x"(x1), "x"(x2), "x"(x3), "x"(x4) - : "memory", "cc" - ); - return x0; -} - -static FORCE_INLINE u64 -syscall6(i64 n, i64 a1, i64 a2, i64 a3, i64 a4, i64 a5, i64 a6) -{ - register i64 x8 asm("x8") = n; - register u64 x0 asm("x0") = a1; - register i64 x1 asm("x1") = a2; - register i64 x2 asm("x2") = a3; - register i64 x3 asm("x3") = a4; - register i64 x4 asm("x4") = a5; - register i64 x5 asm("x5") = a6; - asm volatile ("svc 0" - : "+x"(x0) - : "x"(x8), "x"(x1), "x"(x2), "x"(x3), "x"(x4), "x"(x5) - : "memory", "cc" - ); - return x0; -} - -__attribute__((naked)) -static i64 -new_thread(void *stack_base) -{ - asm volatile ( - "mov x8, #220\n" // SYS_clone - "mov x1, x0\n" // arg2 = new stack - "mov x0, #0xF00\n" // arg1 = clone flags (VM|FS|FILES|SIGHAND|THREAD|SYSVMEM) - "movk x0, #0x5, lsl #16\n" // no 32 bit immediates in general on arm - "svc 0\n" - "cbnz x0, 1f\n" // don't clobber syscall return in calling thread - "mov x0, sp\n" - "ldr x1, [sp]\n" // arm doesn't take the return address from the stack; - "blr x1\n" // we need to load it and branch to it - "1: ret" - ::: "x8", "x1", "memory", "cc" - ); -} - -#include "platform_linux_common.c" diff --git a/platform_linux_amd64.c b/platform_linux_amd64.c @@ -1,162 +0,0 @@ -/* See LICENSE for license details. */ -#ifndef asm -#ifdef __asm -#define asm __asm -#else -#define asm __asm__ -#endif -#endif - -/* TODO: X macro that defines all of these with the appropriate function/macro */ -#define SYS_read 0 -#define SYS_write 1 -#define SYS_close 3 -#define SYS_mmap 9 -#define SYS_mprotect 10 -#define SYS_munmap 11 -#define SYS_ioctl 16 -#define SYS_pwrite64 18 -#define SYS_madvise 28 -#define SYS_clone 56 -#define SYS_execve 59 -#define SYS_wait4 61 -#define SYS_ftruncate 77 -#define SYS_setsid 112 -#define SYS_prctl 157 -#define SYS_futex 202 -#define SYS_getdents64 217 -#define SYS_clock_gettime 228 -#define SYS_exit_group 231 -#define SYS_inotify_add_watch 254 -#define SYS_inotify_rm_watch 255 -#define SYS_openat 257 -#define SYS_pselect6 270 -#define SYS_dup3 292 -#define SYS_inotify_init1 294 -#define SYS_memfd_create 319 -#define SYS_statx 332 - -#define SIGCHLD 17 - -#define PAGE_SIZE 4096 - -typedef u64 sys_fd_set[16]; - -#define DIRENT_RECLEN_OFF 16 -#define DIRENT_TYPE_OFF 18 -#define DIRENT_NAME_OFF 19 - -static FORCE_INLINE u64 -syscall1(i64 n, i64 a1) -{ - u64 result; - asm volatile ("syscall" - : "=a"(result) - : "a"(n), "D"(a1) - : "rcx", "r11", "memory" - ); - return result; -} - -static FORCE_INLINE u64 -syscall2(i64 n, i64 a1, i64 a2) -{ - i64 result; - asm volatile ("syscall" - : "=a"(result) - : "a"(n), "D"(a1), "S"(a2) - : "rcx", "r11", "memory" - ); - return result; -} - -static FORCE_INLINE u64 -syscall3(i64 n, i64 a1, i64 a2, i64 a3) -{ - u64 result; - asm volatile ("syscall" - : "=a"(result) - : "a"(n), "D"(a1), "S"(a2), "d"(a3) - : "rcx", "r11", "memory" - ); - return result; -} - -static FORCE_INLINE u64 -syscall4(i64 n, i64 a1, i64 a2, i64 a3, i64 a4) -{ - u64 result; - register i64 r10 asm("r10") = a4; - asm volatile ("syscall" - : "=a"(result) - : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10) - : "rcx", "r11", "memory" - ); - return result; -} - - -static FORCE_INLINE u64 -syscall5(i64 n, i64 a1, i64 a2, i64 a3, i64 a4, i64 a5) -{ - u64 result; - register i64 r10 asm("r10") = a4; - register i64 r8 asm("r8") = a5; - asm volatile ("syscall" - : "=a"(result) - : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10), "r"(r8) - : "rcx", "r11", "memory" - ); - return result; -} - -static FORCE_INLINE u64 -syscall6(i64 n, i64 a1, i64 a2, i64 a3, i64 a4, i64 a5, i64 a6) -{ - i64 result; - register i64 r10 asm("r10") = a4; - register i64 r8 asm("r8") = a5; - register i64 r9 asm("r9") = a6; - asm volatile ("syscall" - : "=a"(result) - : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10), "r"(r8), "r"(r9) - : "rcx", "r11", "memory" - ); - return result; -} - -/* NOTE: based on code from nullprogram (Chris Wellons) */ -__attribute__((naked)) -static i64 -new_thread(void *stack_base) -{ - asm volatile ( - "mov %%rdi, %%rsi\n" // arg2 = new stack - "mov $0x50F00, %%edi\n" // arg1 = clone flags (VM|FS|FILES|SIGHAND|THREAD|SYSVMEM) - "mov $56, %%eax\n" // SYS_clone - "syscall\n" - "test %%eax, %%eax\n" // don't mess with the calling thread's stack - "jne 1f\n" - "mov %%rsp, %%rdi\n" - "sub $8, %%rsp\n" // place a 0 return branch pointer on the child's stack - "push (%%rdi)\n" // push the entry point back onto the stack for use by ret - "1: ret\n" - ::: "rax", "rcx", "rsi", "rdi", "r11", "memory" - ); -} - -#include "platform_linux_common.c" - -#if 0 -asm ( - ".intel_syntax noprefix\n" - ".global _start\n" - "_start:\n" - " mov edi, DWORD PTR [rsp]\n" - " lea rsi, [rsp+8]\n" - " lea rdx, [rsi+rdi*8+8]\n" - " call linux_main\n" - " ud2\n" - ".att_syntax\n" -); -#endif diff --git a/platform_linux_common.c b/platform_linux_common.c @@ -1,558 +0,0 @@ -#define FUTEX_WAIT 0 -#define FUTEX_WAKE 1 - -#define CLOCK_MONOTONIC 1 - -#define PR_SET_NAME 15 - -#define PROT_NONE 0x00 -#define PROT_READ 0x01 -#define PROT_RW 0x03 - -#define MFD_CLOEXEC 0x01 - -#define MAP_SHARED 0x01 -#define MAP_PRIVATE 0x02 -#define MAP_FIXED 0x10 -#define MAP_ANON 0x20 - -#define MADV_FREE 8 -#define MADV_HUGEPAGE 14 - -#define O_RDONLY 0x00000 -#define O_WRONLY 0x00001 -#define O_RDWR 0x00002 -#define O_CREAT 0x00040 -#define O_NOCTTY 0x00100 -#define O_APPEND 0x00400 -#define O_NONBLOCK 0x00800 -#define O_CLOEXEC 0x80000 - -#define IN_CLOSE_WRITE 0x00000008 -#define IN_CLOSE_NOWRITE 0x00000010 -#define IN_MODIFY 0x00000002 - -#define AT_EMPTY_PATH 0x1000 -#define AT_FDCWD (-100) - -#define LINUX_INOTIFY_MASK (IN_CLOSE_WRITE|IN_CLOSE_NOWRITE|IN_MODIFY) - -#define WNOHANG 1 -#define W_IF_EXITED(s) (!((s) & 0x7F)) - -/* TODO: glibc/gcc indirectly include sys/select.h if you include immintrin.h. If that - * header is removed this can also be removed */ -#undef FD_SET -#undef FD_ISSET - -#define FD_SET(d, s) ((s)[(d) / (8 * sizeof(*(s)))] |= (1ULL << ((d) % (8 * sizeof(*(s)))))) -#define FD_ISSET(d, s) ((s)[(d) / (8 * sizeof(*(s)))] & (1ULL << ((d) % (8 * sizeof(*(s)))))) - -typedef __attribute__((aligned(16))) u8 statx_buffer[256]; -#define STATX_BUF_MEMBER(sb, t, off) (*(t *)((u8 *)(sb) + off)) -#define STATX_INODE(sb) STATX_BUF_MEMBER(sb, u64, 32) -#define STATX_FILE_SIZE(sb) STATX_BUF_MEMBER(sb, u64, 40) - -#define STATX_INO 0x00000100U -#define STATX_SIZE 0x00000200U - -#define TIOCSCTTY 0x540E -#define TIOCSWINSZ 0x5414 -#define TIOCSPTLCK 0x40045431 /* (un)lock pty */ -#define TIOCGPTN 0x80045430 /* get pty number */ - -#ifndef VERSION -#define VERSION "unknown" -#endif - -#define OS_MAP_READ PROT_READ -#define OS_MAP_PRIVATE MAP_PRIVATE - -struct __attribute__((aligned(16))) stack_base { - void (*entry)(struct stack_base *stack); - Arena thread_arena; - void *window; - TerminalMemory *terminal_memory; - TerminalInput *input; - i32 work_futex; - b32 thread_asleep; -}; - -typedef struct { - platform_file_watch_callback_fn *fn; - u8 *path; - void *user_ctx; - u64 inode; - i32 handle; -} linux_file_watch; - -typedef struct linux_deferred_file_reload_queue { - struct linux_deferred_file_reload_queue *next; - struct linux_deferred_file_reload_queue *last; - i32 index; - i32 failures; -} linux_deferred_file_reload_queue; - -typedef struct { - iptr handle; - iptr process_id; -} linux_platform_process; - -typedef struct { - Arena platform_memory; - void *window; - - TerminalMemory memory; - TerminalInput input; - - Stream char_stream; - - linux_platform_process child; - i32 inotify_fd; - i32 win_fd; - - linux_deferred_file_reload_queue file_reload_queue; - linux_deferred_file_reload_queue *file_reload_free_list; - linux_file_watch file_watches[32]; - i32 file_watch_count; - - Stream error_stream; - - struct stack_base *render_stack; - -#ifdef _DEBUG - void *library_handle; -#endif -} PlatformCtx; -static PlatformCtx linux_ctx; - -static void -os_write_err_msg(s8 msg) -{ - syscall3(SYS_write, 2, (iptr)msg.data, msg.len); -} - -__attribute__((noreturn)) -static void -os_fatal(s8 msg) -{ - os_write_err_msg(msg); - syscall1(SYS_exit_group, 1); - __builtin_unreachable(); -} - -static u32 -os_file_attribute_to_mode(u32 attr) -{ - u32 result = O_CREAT; - if (attr & FA_READ && attr & FA_WRITE) { - result |= O_RDWR; - } else if (attr & FA_READ) { - result |= O_RDONLY; - } else if (attr & FA_WRITE) { - result |= O_WRONLY; - } - - if (attr & FA_APPEND) - result |= O_APPEND; - - return result; -} - -static iptr -os_open(u8 *name, u32 attr) -{ - u64 result = syscall4(SYS_openat, AT_FDCWD, (iptr)name, os_file_attribute_to_mode(attr), 0660); - if (result > -4096UL) - result = INVALID_FILE; - return result; -} - -static b32 -os_offset_write(iptr file, s8 raw, size offset) -{ - size result = syscall4(SYS_pwrite64, file, (iptr)raw.data, raw.len, offset); - return result == raw.len; -} - -static PLATFORM_WRITE_FN(os_write) -{ - size result = syscall3(SYS_write, file, (iptr)raw.data, raw.len); - return result == raw.len; -} - -static void -os_close(iptr file) -{ - syscall1(SYS_close, file); -} - -static PLATFORM_READ_FN(os_read) -{ - u64 r = 0, remaining = buffer.len, total_bytes_read = 0; - - do { - remaining -= r; - total_bytes_read += r; - r = syscall3(SYS_read, file, (iptr)(buffer.data + total_bytes_read), remaining); - } while (r <= -4096UL && remaining != 0); - - return total_bytes_read; -} - -static PLATFORM_READ_FILE_FN(os_read_file) -{ - s8 result = {0}; - - statx_buffer sb; - u64 fd = syscall4(SYS_openat, AT_FDCWD, (iptr)path, O_RDONLY, 0); - u64 status = syscall5(SYS_statx, fd, 0, AT_EMPTY_PATH, STATX_SIZE, (iptr)sb); - - if (fd <= -4096UL && status == 0) { - result = s8alloc(a, STATX_FILE_SIZE(sb)); - size rlen = os_read(fd, result); - syscall1(SYS_close, fd); - if (result.len != rlen) - result.len = 0; - } - - return result; -} - -static MemoryBlock -os_block_alloc(size requested_size) -{ - MemoryBlock result = {0}; - - /* TODO: query system for HUGETLB support and use those instead of page size */ - size alloc_size = requested_size; - if (alloc_size % PAGE_SIZE != 0) - alloc_size += PAGE_SIZE - alloc_size % PAGE_SIZE; - - u64 memory = syscall6(SYS_mmap, 0, alloc_size, PROT_RW, MAP_ANON|MAP_PRIVATE, -1, 0); - if (memory <= -4096UL) { - result.memory = (void *)memory; - result.size = alloc_size; - syscall3(SYS_madvise, memory, alloc_size, MADV_HUGEPAGE); - } - - return result; -} - -static void -os_release_memory_block(MemoryBlock memory) -{ - syscall3(SYS_madvise, (iptr)memory.memory, memory.size, MADV_FREE); - syscall3(SYS_mprotect, (iptr)memory.memory, memory.size, PROT_NONE); -} - -static void -os_release_ring_buffer(RingBuf *rb) -{ - syscall2(SYS_munmap, (iptr)(rb->buf - rb->cap), rb->cap * 3); -} - -static f64 -os_get_time(void) -{ - i64 timespec[2]; - syscall2(SYS_clock_gettime, CLOCK_MONOTONIC, (iptr)timespec); - f64 result = timespec[0] + ((f64)timespec[1]) * 1e-9; - return result; -} - -static os_mapped_file -os_map_file(char *path, i32 mode, i32 perm) -{ - os_mapped_file result = {0}; - - i32 open_mode = 0; - switch (mode) { - case OS_MAP_READ: open_mode = O_RDONLY; break; - default: ASSERT(0); - } - - statx_buffer sb; - u64 fd = syscall4(SYS_openat, AT_FDCWD, (iptr)path, open_mode, 0); - u64 status = syscall5(SYS_statx, fd, 0, AT_EMPTY_PATH, STATX_SIZE, (iptr)sb); - - if (fd <= -4096UL && status == 0) { - u64 memory = syscall6(SYS_mmap, 0, STATX_FILE_SIZE(sb), mode, perm, fd, 0); - if (memory <= -4096UL) { - result.data = (u8 *)memory; - result.len = STATX_FILE_SIZE(sb); - } - syscall1(SYS_close, fd); - } - - return result; -} - -static PLATFORM_ALLOCATE_RING_BUFFER_FN(os_allocate_ring_buffer) -{ - /* TODO: query system for HUGETLB support and use those instead of page size */ - if (capacity % PAGE_SIZE != 0) - capacity += PAGE_SIZE - capacity % PAGE_SIZE; - ASSERT(capacity % PAGE_SIZE == 0); - - u64 fd = syscall2(SYS_memfd_create, (iptr)"vtgl:rb", MFD_CLOEXEC); - if (fd > -4096UL) os_fatal(s8("os_alloc_ring_buffer: failed to open mem_fd\n")); - syscall2(SYS_ftruncate, fd, capacity); - - rb->widx = 0; - rb->filled = 0; - rb->cap = capacity; - rb->buf = (u8 *)syscall6(SYS_mmap, 0, (iptr)(3 * rb->cap), 0, MAP_ANON|MAP_PRIVATE, -1, 0); - if ((u64)rb->buf > -4096UL) - os_fatal(s8("os_alloc_ring_buffer: initial mmap failed\n")); - syscall3(SYS_madvise, (iptr)rb->buf, 3 * rb->cap, MADV_HUGEPAGE); - - for (i32 i = 0; i < 3; i++) { - u64 memory = syscall6(SYS_mmap, (iptr)(rb->buf + i * rb->cap), rb->cap, PROT_RW, - MAP_FIXED|MAP_SHARED, fd, 0); - if (memory > -4096UL) { - u8 buf[256]; - Stream err = {.buf = buf, .cap = sizeof(buf)}; - stream_push_s8(&err, s8("os_alloc_ring_buffer: mmap(")); - stream_push_u64(&err, i); - stream_push_s8(&err, s8(") failed\n")); - os_fatal(stream_to_s8(&err)); - } - } - syscall1(SYS_close, fd); - - /* NOTE: start in the middle page */ - rb->buf += rb->cap; -} - -static b32 -os_child_exited(iptr pid) -{ - i64 status; - i64 r = syscall4(SYS_wait4, pid, (iptr)&status, WNOHANG, 0); - return r == pid && W_IF_EXITED(status); -} - -static linux_platform_process -os_fork_child(s8 cmd, c8 **envp) -{ - i32 n = 0; - - /* NOTE: we open in non-blocking mode so that we can try and fully drain the pipe - * before processing. Otherwise a single read will be limited to the page size */ - u64 m = syscall4(SYS_openat, AT_FDCWD, (iptr)"/dev/ptmx", O_RDWR|O_NOCTTY|O_NONBLOCK|O_CLOEXEC, 0); - if (m > -4096UL) os_fatal(s8("os_fork_child: failed to open master terminal\n")); - /* NOTE: first unlock the tty, then get a valid pty number */ - if (syscall3(SYS_ioctl, m, TIOCSPTLCK, (iptr)&n) || syscall3(SYS_ioctl, m, TIOCGPTN, (iptr)&n)) - os_fatal(s8("os_fork_child: failed to get a pty number\n")); - - u8 buffer[20] = {"/dev/pts/"}; - Stream sbuf = {.buf = buffer, .cap = 20, .widx = sizeof("/dev/pts/") - 1}; - stream_push_i64(&sbuf, n); - stream_push_byte(&sbuf, 0); - - u64 s = syscall4(SYS_openat, AT_FDCWD, (iptr)sbuf.buf, O_RDWR|O_NOCTTY, 0); - if (s > -4096UL) os_fatal(s8("os_fork_child: failed to open slave terminal\n")); - - u64 pid = syscall2(SYS_clone, SIGCHLD, 0); - if (pid > -4096UL) os_fatal(s8("os_fork_child: failed to fork a child\n")); - - if (pid == 0) { - syscall1(SYS_setsid, 0); - syscall3(SYS_dup3, s, 0, 0); - syscall3(SYS_dup3, s, 1, 0); - syscall3(SYS_dup3, s, 2, 0); - syscall3(SYS_ioctl, s, TIOCSCTTY, 0); - if (s > 2) syscall1(SYS_close, s); - ASSERT(cmd.data[cmd.len] == 0); - u8 *argv[] = {cmd.data, 0}; - syscall3(SYS_execve, (iptr)cmd.data, (iptr)argv, (iptr)envp); - __builtin_unreachable(); - os_fatal(s8("failed to exec child\n")); - } - syscall1(SYS_close, s); - - return (linux_platform_process){.process_id = pid, .handle = m}; -} - -static PLATFORM_SET_TERMINAL_SIZE_FN(os_set_terminal_size) -{ - u16 win_size[4]; - win_size[0] = rows; - win_size[1] = columns; - win_size[2] = window_width; - win_size[3] = window_height; - if (syscall3(SYS_ioctl, child, TIOCSWINSZ, (iptr)win_size) > -4096UL) - os_write_err_msg(s8("os_set_term_size\n")); -} - -static PLATFORM_ADD_FILE_WATCH_FN(linux_add_file_watch) -{ - u64 wd = syscall3(SYS_inotify_add_watch, linux_ctx.inotify_fd, (iptr)path, LINUX_INOTIFY_MASK); - if (wd <= -4096UL) { - statx_buffer sb; - syscall5(SYS_statx, AT_FDCWD, (iptr)path, 0, STATX_INO, (iptr)sb); - - 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 = STATX_INODE(sb); - linux_ctx.file_watches[idx].user_ctx = user_ctx; - } -} - -static void -try_deferred_file_loads(PlatformCtx *ctx) -{ - linux_deferred_file_reload_queue *file = ctx->file_reload_queue.next; - while (file) { - linux_file_watch *fw = ctx->file_watches + file->index; - - statx_buffer sb; - syscall5(SYS_statx, AT_FDCWD, (iptr)fw->path, 0, STATX_INO, (iptr)sb); - - fw->handle = syscall3(SYS_inotify_add_watch, ctx->inotify_fd, (iptr)fw->path, - LINUX_INOTIFY_MASK); - fw->inode = STATX_INODE(sb); - - if ((u64)fw->handle <= -4096UL) { - fw->fn(fw->path, fw->user_ctx); - file->last->next = file->next; - file->next = ctx->file_reload_free_list; - ctx->file_reload_free_list = file; - file = file->last; - } else { - file->failures++; - #if 0 - TODO - if (file->failures > MAX_FILE_RELOAD_TRIES) { - log - remove from list - } - #endif - } - file = file->next; - } -} - -static b32 -defer_file_reload(PlatformCtx *ctx, i32 file_watch_index, statx_buffer *sb) -{ - b32 result = 1; - linux_file_watch *fw = ctx->file_watches + file_watch_index; - - fw->inode = STATX_INODE(*sb); - fw->handle = syscall3(SYS_inotify_add_watch, ctx->inotify_fd, (iptr)fw->path, LINUX_INOTIFY_MASK); - - if ((u64)fw->handle > -4096UL) { - result = 0; - - linux_deferred_file_reload_queue *new = ctx->file_reload_free_list; - if (new) ctx->file_reload_free_list = new->next; - else new = push_struct(&ctx->platform_memory, typeof(*new)); - new->index = file_watch_index; - new->failures = 0; - DLLPushDown(&ctx->file_reload_queue, new); - } - - return result; -} - -static void -dispatch_file_watch_events(PlatformCtx *ctx) -{ - struct { - i32 wd; - u32 mask, cookie, len; - c8 name[]; - } *ie; - - u8 *mem = alloc_(&ctx->platform_memory, 4096, 64, 1); - s8 buf = {.len = 4096, .data = mem}; - - for (;;) { - size rlen = syscall3(SYS_read, ctx->inotify_fd, (iptr)buf.data, buf.len); - if (rlen <= 0) - break; - - for (u8 *data = buf.data; data < buf.data + rlen; data += sizeof(*ie) + ie->len) { - ie = (void *)data; - for (i32 i = 0; i < ctx->file_watch_count; i++) { - linux_file_watch *fw = ctx->file_watches + i; - if (fw->handle != ie->wd) - continue; - - 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 */ - statx_buffer sb; - u64 status = syscall5(SYS_statx, AT_FDCWD, (iptr)fw->path, 0, STATX_INO, (iptr)sb); - - if (status > -4096UL || fw->inode != STATX_INODE(sb)) { - syscall2(SYS_inotify_rm_watch, ctx->inotify_fd, fw->handle); - fw->handle = INVALID_FILE; - file_changed = defer_file_reload(ctx, i, &sb); - } - if (file_changed) - fw->fn(fw->path, fw->user_ctx); - } - } - } -} - -static struct stack_base * -new_stack(size capacity) -{ - u64 p = syscall6(SYS_mmap, 0, capacity, PROT_RW, MAP_ANON|MAP_PRIVATE, -1, 0); - if (p > -4096UL) - os_fatal(s8("new_stack: mmap failed\n")); - i64 count = capacity / sizeof(struct stack_base); - /* NOTE: remember the stack grows down; we want to start at the highest address */ - struct stack_base *result = (struct stack_base *)p + count - 1; - return result; -} - -static void -usage(char *argv0, Stream *err) -{ - stream_push_s8(err, s8("usage: ")); - stream_push_s8(err, c_str_to_s8(argv0)); - stream_push_s8(err, s8(" [-v] [-g COLxROW]\n")); - os_fatal(stream_to_s8(err)); -} - -static s8 -get_default_cmd(char **envp) -{ - s8 result = envp_lookup(s8("SHELL="), envp); - if (result.len == 0) - result = s8("/bin/sh"); - return result; -} - -static SLLVariableVector -parse_environment(Arena *a, char **envp) -{ - SLLVariableVector env = {0}; - for (; *envp; envp++) { - s8 e = c_str_to_s8(*envp); - if (!s8_prefix_of(s8("TERM="), e)) { - Variable *var = push_struct(a, Variable); - var->type = VT_S8; - var->s8 = e; - SLLVariableVectorPush(a, &env, var); - } - } - - Variable *var = push_struct(a, Variable); - var->type = VT_S8; - /* TODO: don't pretend to be xterm ? */ - var->s8 = s8("TERM=xterm"); - SLLVariableVectorPush(a, &env, var); - - return env; -} diff --git a/platform_linux_x11.c b/platform_linux_x11.c @@ -1,544 +0,0 @@ -/* See LICENSE for copyright details */ -#define GL_GLEXT_PROTOTYPES -#include <GLFW/glfw3.h> - -/* TODO: fix glfw */ -typedef void *RROutput; -typedef void *RRCrtc; -typedef void *Display; -typedef void *Window; - -#define GLFW_EXPOSE_NATIVE_X11 -#define GLFW_NATIVE_INCLUDE_NONE -#include <GLFW/glfw3native.h> - -#include "vtgl.h" - -i32 XConnectionNumber(void *display); -i32 XPending(void *display); - -#ifndef _DEBUG -#define do_debug(...) -#include "vtgl.c" -#else -#include <dlfcn.h> - -#define DEBUG_LIB_NAME "./vtgl.so" - -#define LIB_FNS \ - X(vtgl_active_selection) \ - X(vtgl_initialize) \ - X(vtgl_render_frame) \ - X(vtgl_handle_keys) \ - X(vtgl_frame_step) - -#define X(name) static name ## _fn *name; -LIB_FNS -#undef X - -static PLATFORM_FILE_WATCH_CALLBACK_FN(debug_reload_library) -{ - PlatformCtx *ctx = user_ctx; - - if (ctx->input.executable_reloaded) - return; - - /* NOTE(rnp): spin until render thread finishes its work */ - while (!ctx->render_stack->thread_asleep); - - ctx->input.executable_reloaded = 1; - s8 nl = s8("\n"); - /* NOTE: glibc sucks and will crash if this is NULL */ - 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(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 - - stream_push_s8(&ctx->error_stream, s8("Reloaded Main Program\n")); - - os_write_err_msg(stream_to_s8(&ctx->error_stream)); - ctx->error_stream.widx = 0; -} -#endif /* _DEBUG */ - -static void -glfw_error_callback(int code, const char *desc) -{ - u8 buf[256]; - Stream err = {.cap = sizeof(buf), .buf = buf}; - stream_push_s8(&err, s8("GLFW Error (0x")); - stream_push_hex_u64(&err, code); - stream_push_s8(&err, s8("): ")); - os_write_err_msg(stream_to_s8(&err)); - os_write_err_msg(c_str_to_s8((char *)desc)); - os_write_err_msg(s8("\n")); -} - -static void -char_callback(GLFWwindow *win, u32 codepoint) -{ - PlatformCtx *ctx = glfwGetWindowUserPointer(win); - stream_push_s8(&ctx->char_stream, utf8_encode(codepoint)); -} - -/* NOTE: called when the window was resized */ -static void -fb_callback(GLFWwindow *win, i32 w, i32 h) -{ - PlatformCtx *ctx = glfwGetWindowUserPointer(win); - ctx->input.window_size = (iv2){.w = w, .h = h}; -} - -static void -scroll_callback(GLFWwindow *win, f64 xoff, f64 yoff) -{ - PlatformCtx *ctx = glfwGetWindowUserPointer(win); - ctx->input.mouse_scroll.x = xoff; - ctx->input.mouse_scroll.y = yoff; -} - -static void -key_callback(GLFWwindow *win, i32 key, i32 scancode, i32 action, i32 modifiers) -{ - PlatformCtx *ctx = glfwGetWindowUserPointer(win); - TerminalInput *input = &ctx->input; - - /* TODO: base this on X11 keys directly */ - switch (key) { - case GLFW_KEY_LEFT_SHIFT: - button_action(input->keys + KEY_LEFT_SHIFT, action == GLFW_PRESS); - if (action == GLFW_RELEASE) input->modifiers &= ~MOD_SHIFT; - else input->modifiers |= MOD_SHIFT; - break; - case GLFW_KEY_LEFT_CONTROL: - button_action(input->keys + KEY_LEFT_CONTROL, action == GLFW_PRESS); - if (action == GLFW_RELEASE) input->modifiers &= ~MOD_CONTROL; - else input->modifiers |= MOD_CONTROL; - break; - case GLFW_KEY_LEFT_ALT: - button_action(input->keys + KEY_LEFT_ALT, action == GLFW_PRESS); - if (action == GLFW_RELEASE) input->modifiers &= ~MOD_ALT; - else input->modifiers |= MOD_ALT; - break; - case GLFW_KEY_LEFT_SUPER: - button_action(input->keys + KEY_LEFT_SUPER, action == GLFW_PRESS); - if (action == GLFW_RELEASE) input->modifiers &= ~MOD_SUPER; - else input->modifiers |= MOD_SUPER; - break; - case GLFW_KEY_RIGHT_SHIFT: - button_action(input->keys + KEY_RIGHT_SHIFT, action == GLFW_PRESS); - if (action == GLFW_RELEASE) input->modifiers &= ~MOD_SHIFT; - else input->modifiers |= MOD_SHIFT; - break; - case GLFW_KEY_RIGHT_CONTROL: - button_action(input->keys + KEY_RIGHT_CONTROL, action == GLFW_PRESS); - if (action == GLFW_RELEASE) input->modifiers &= ~MOD_CONTROL; - else input->modifiers |= MOD_CONTROL; - break; - case GLFW_KEY_RIGHT_ALT: - button_action(input->keys + KEY_RIGHT_ALT, action == GLFW_PRESS); - if (action == GLFW_RELEASE) input->modifiers &= ~MOD_ALT; - else input->modifiers |= MOD_ALT; - break; - case GLFW_KEY_RIGHT_SUPER: - button_action(input->keys + KEY_RIGHT_SUPER, action == GLFW_PRESS); - if (action == GLFW_RELEASE) input->modifiers &= ~MOD_SUPER; - else input->modifiers |= MOD_SUPER; - break; - case GLFW_KEY_MENU: - button_action(input->keys + KEY_MENU, action == GLFW_PRESS); - break; - } - vtgl_handle_keys(&ctx->memory, &ctx->input, key, action, modifiers); -} - -static void -mouse_button_callback(GLFWwindow *win, i32 button, i32 action, i32 modifiers) -{ - PlatformCtx *ctx = glfwGetWindowUserPointer(win); - TerminalInput *input = &ctx->input; - - switch (button) { - case GLFW_MOUSE_BUTTON_LEFT: - button_action(input->keys + MOUSE_LEFT, action == GLFW_PRESS); - break; - case GLFW_MOUSE_BUTTON_RIGHT: - button_action(input->keys + MOUSE_RIGHT, action == GLFW_PRESS); - break; - case GLFW_MOUSE_BUTTON_MIDDLE: - button_action(input->keys + MOUSE_MIDDLE, action == GLFW_PRESS); - break; - case GLFW_MOUSE_BUTTON_4: - button_action(input->keys + MOUSE_EXTENDED_0, action == GLFW_PRESS); - break; - case GLFW_MOUSE_BUTTON_5: - button_action(input->keys + MOUSE_EXTENDED_1, action == GLFW_PRESS); - break; - } -} - -static void -focus_callback(GLFWwindow *win, i32 focused) -{ - PlatformCtx *ctx = glfwGetWindowUserPointer(win); - ctx->input.window_focused = focused; - /* NOTE: force a refresh as well when the focus changes */ - ctx->input.window_refreshed = 1; -} - -static void -refresh_callback(GLFWwindow *win) -{ - PlatformCtx *ctx = glfwGetWindowUserPointer(win); - ctx->input.window_refreshed = 1; -} - -static GLFWwindow * -init_window(PlatformCtx *ctx, iv2 window_size) -{ - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - - #ifdef _DEBUG - glfwWindowHint(GLFW_CONTEXT_DEBUG, GLFW_TRUE); - #endif - - /* NOTE: we initially hide the window so that it can be freely resized behind the - * back of the window manager prior to showing */ - glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - GLFWwindow *window = glfwCreateWindow(window_size.w, window_size.h, "vtgl", NULL, NULL); - if (!window) { - glfwTerminate(); - os_fatal(s8("Failed to spawn GLFW window\n")); - } - glfwMakeContextCurrent(window); - glfwSetWindowUserPointer(window, ctx); - - /* TODO: swap interval is not needed because we will sleep on waiting for terminal input */ - glfwSwapInterval(1); - - glfwSetCharCallback(window, char_callback); - glfwSetFramebufferSizeCallback(window, fb_callback); - glfwSetKeyCallback(window, key_callback); - glfwSetMouseButtonCallback(window, mouse_button_callback); - glfwSetScrollCallback(window, scroll_callback); - glfwSetWindowFocusCallback(window, focus_callback); - glfwSetWindowRefreshCallback(window, refresh_callback); - - ctx->win_fd = XConnectionNumber(glfwGetX11Display()); - - return window; -} - -static void -update_input(PlatformCtx *ctx) -{ - TerminalInput *input = &ctx->input; - ctx->input.executable_reloaded = 0; - ctx->input.window_refreshed = 0; - - /* NOTE: mouse */ - input->last_mouse = input->mouse; - input->mouse_scroll = (v2){0}; - - f64 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; - - for (u32 i = 0; i < ARRAY_COUNT(input->keys); i++) - input->keys[i].transitions = 0; - - ctx->char_stream.widx = 0; - - i64 timeout[2] = {0, 25e6}; - if (input->pending_updates) { - timeout[1] = 0; - input->pending_updates = 0; - } - - sys_fd_set rfd = {0}; - FD_SET(ctx->child.handle, rfd); - FD_SET(ctx->inotify_fd, rfd); - FD_SET(ctx->win_fd, rfd); - - i32 max_fd = MAX(ctx->inotify_fd, ctx->child.handle); - max_fd = MAX(max_fd, ctx->win_fd); - syscall6(SYS_pselect6, max_fd + 1, (iptr)rfd, 0, 0, (iptr)timeout, 0); - - input->data_available = FD_ISSET(ctx->child.handle, rfd) != 0; - - try_deferred_file_loads(ctx); - if (FD_ISSET(ctx->inotify_fd, rfd)) - dispatch_file_watch_events(ctx); - - glfwPollEvents(); - - /* NOTE: char_stream was filled in char callback */ - input->character_input = stream_to_s8(&ctx->char_stream); -} - -static PLATFORM_CLIPBOARD_FN(x11_get_clipboard) -{ - /* NOTE: this does a bunch of extra copying and other garbage. both GLFW and X11 are - * at fault. The API is designed to do what the terminal wants and not be constrained - * by GLFW and X11 garbage */ - ASSERT(buffer); - char *text = 0; - switch (clipboard) { - case CLIPBOARD_0: text = (c8 *)glfwGetClipboardString(0); break; - case CLIPBOARD_1: text = (c8 *)glfwGetX11SelectionString(); break; - } - if (text) { - /* TODO: we may need to replace '\n' with '\r\n' */ - stream_push_s8(buffer, c_str_to_s8(text)); - } - - return !buffer->errors; -} - -static PLATFORM_CLIPBOARD_FN(x11_set_clipboard) -{ - ASSERT(buffer); - stream_push_byte(buffer, 0); - - if (!buffer->errors) { - switch (clipboard) { - case CLIPBOARD_0: glfwSetClipboardString(0, (c8 *)buffer->buf); break; - case CLIPBOARD_1: glfwSetX11SelectionString((c8 *)buffer->buf); break; - } - } - return !buffer->errors; -} - -static PLATFORM_WINDOW_TITLE_FN(x11_get_window_title) -{ - ASSERT(buffer); - char *title = (c8 *)glfwGetWindowTitle(linux_ctx.window); - if (title) stream_push_s8(buffer, c_str_to_s8(title)); -} - -static PLATFORM_WINDOW_TITLE_FN(x11_set_window_title) -{ - ASSERT(buffer); - stream_push_byte(buffer, 0); - if (!buffer->errors) - glfwSetWindowTitle(linux_ctx.window, (c8 *)buffer->buf); -} - -static void -linux_render_thread_entry(struct stack_base *stack) -{ - { - /* NOTE: set thread name */ - char name[16] = "[render]"; - syscall2(SYS_prctl, PR_SET_NAME, (iptr)name); - /* NOTE: halt until main thread is ready to hand off gl context */ - syscall4(SYS_futex, (iptr)&stack->work_futex, FUTEX_WAIT, 0, 0); - - glfwMakeContextCurrent(stack->window); - } - - for (;;) { - stack->thread_asleep = 1; - syscall4(SYS_futex, (iptr)&stack->work_futex, FUTEX_WAIT, 0, 0); - stack->thread_asleep = 0; - vtgl_render_frame(stack->terminal_memory, stack->input, stack->thread_arena); - glfwSwapBuffers(stack->window); - } - - __builtin_unreachable(); -} - -i32 -main(i32 argc, char *argv[], char *envp[]) -{ - linux_ctx.platform_memory = arena_from_memory_block(os_block_alloc(MB(2))); - linux_ctx.error_stream = stream_alloc(&linux_ctx.platform_memory, KB(256)); - - iv2 cells = {.x = -1, .y = -1}; - - char *argv0 = *argv++; - argc--; - for (i32 i = 0; i < argc; i++) { - char *arg = argv[i]; - if (!arg || !arg[0]) - usage(argv0, &linux_ctx.error_stream); - if (arg[0] != '-') - break; - arg++; - switch (arg[0]) { - case 'g': { - if (!argv[i + 1]) - usage(argv0, &linux_ctx.error_stream); - s8 g_arg = c_str_to_s8(argv[i + 1]); - struct conversion_result cres = s8_parse_i32_until(g_arg, 'x'); - if (cres.status == CR_SUCCESS) - cells.w = cres.i; - - if (cres.unparsed.len > 0 && cres.unparsed.data[0] == 'x') { - s8 remainder = {.len = cres.unparsed.len - 1, - .data = cres.unparsed.data + 1}; - cres = s8_parse_i32(remainder); - if (cres.status == CR_SUCCESS) - cells.h = cres.i; - } - - if (cells.w <= 0 || cells.h <= 0) { - 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(&linux_ctx.error_stream, 2, - (s8 []){c_str_to_s8(argv0), s8(" " VERSION "\n")}); - os_fatal(stream_to_s8(&linux_ctx.error_stream)); - default: - usage(argv0, &linux_ctx.error_stream); - } - } - if (linux_ctx.error_stream.widx) { - os_write_err_msg(stream_to_s8(&linux_ctx.error_stream)); - linux_ctx.error_stream.widx = 0; - } - - linux_ctx.render_stack = new_stack(KB(256)); - linux_ctx.render_stack->entry = linux_render_thread_entry; - linux_ctx.render_stack->thread_asleep = 1; - new_thread(linux_ctx.render_stack); - - { - Arena tmp = linux_ctx.platform_memory; - SLLVariableVector environment_block = parse_environment(&tmp, envp); - - /* TODO: build up argv for the child as well */ - c8 **child_envp = construct_c_str_array(&tmp, environment_block); - linux_ctx.child = os_fork_child(get_default_cmd(envp), child_envp); - } - - { - MemoryBlock terminal_memory = os_block_alloc(MB(32)); - linux_ctx.memory.memory = terminal_memory.memory; - linux_ctx.memory.memory_size = terminal_memory.size; -#ifdef _DEBUG - MemoryBlock debug_memory = os_block_alloc(MB(128)); - linux_ctx.memory.debug_memory = debug_memory.memory; - linux_ctx.memory.debug_memory_size = debug_memory.size; -#endif - } - - linux_ctx.inotify_fd = syscall1(SYS_inotify_init1, O_NONBLOCK|O_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 - - linux_ctx.memory.platform_api.add_file_watch = linux_add_file_watch; - linux_ctx.memory.platform_api.allocate_ring_buffer = os_allocate_ring_buffer; - 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.read_file = os_read_file; - linux_ctx.memory.platform_api.read = os_read; - linux_ctx.memory.platform_api.set_terminal_size = os_set_terminal_size; - 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.write = os_write; - linux_ctx.memory.platform_api.path_separator = '/'; - - if (!glfwInit()) - os_fatal(s8("Failed to init GLFW\n")); - glfwSetErrorCallback(glfw_error_callback); - - GLFWmonitor *mon = glfwGetPrimaryMonitor(); - if (!mon) { - glfwTerminate(); - os_fatal(s8("Failed to find any monitors!\n")); - } - iv2 monitor_size; - glfwGetMonitorWorkarea(mon, NULL, NULL, &monitor_size.w, &monitor_size.h); - - linux_ctx.char_stream = arena_stream(linux_ctx.platform_memory); - - iv2 window_size = {.w = 1280, .h = 720}; - linux_ctx.window = init_window(&linux_ctx, window_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(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(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(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(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(linux_ctx.window); - } - glfwMakeContextCurrent(0); - - linux_ctx.input.window_size = window_size; - - linux_ctx.render_stack->input = &linux_ctx.input; - linux_ctx.render_stack->terminal_memory = &linux_ctx.memory; - linux_ctx.render_stack->thread_arena = arena_from_memory_block(os_block_alloc(MB(8))); - linux_ctx.render_stack->window = linux_ctx.window; - syscall3(SYS_futex, (iptr)&linux_ctx.render_stack->work_futex, FUTEX_WAKE, 1); - - Range last_sel = {0}; - f64 last_time = os_get_time(); - while (!glfwWindowShouldClose(linux_ctx.window)) { - if (os_child_exited(linux_ctx.child.process_id)) - break; - - /* TODO: cpu time excluding waiting for the vblank */ - f64 current_time = os_get_time(); - linux_ctx.input.dt = current_time - last_time; - last_time = current_time; - - update_input(&linux_ctx); - if (vtgl_frame_step(&linux_ctx.memory, &linux_ctx.input)) - syscall3(SYS_futex, (iptr)&linux_ctx.render_stack->work_futex, FUTEX_WAKE, 1); - - 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(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; - } - } - - syscall1(SYS_exit_group, 0); - __builtin_unreachable(); - - return 0; -} diff --git a/terminal.c b/terminal.c @@ -3,7 +3,7 @@ /* TODO: build own wide char tables */ i32 wcwidth(u32 cp); -static const u8 utf8overhangmask[32] = { +global const u8 utf8overhangmask[32] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, @@ -12,11 +12,11 @@ static const u8 utf8overhangmask[32] = { #define SPLIT_LONG 4096L -static iv2 +function iv2 initialize_framebuffer(Framebuffer *fb, iv2 term_size) { - size cell_memory_size = sizeof(Cell) * term_size.h * term_size.w; - size rows_memory_size = sizeof(Row) * term_size.h; + iz cell_memory_size = sizeof(Cell) * term_size.h * term_size.w; + iz rows_memory_size = sizeof(Row) * term_size.h; /* NOTE: make sure cell memory size is a multiple of pointer size */ cell_memory_size += (sizeof(void *) - cell_memory_size % sizeof(void *)); @@ -33,7 +33,7 @@ initialize_framebuffer(Framebuffer *fb, iv2 term_size) return term_size; } -static Range +function Range get_word_around_cell(Term *t, iv2 cell) { Range result = {.start = cell, .end = cell}; @@ -56,7 +56,7 @@ get_word_around_cell(Term *t, iv2 cell) return result; } -static Range +function Range get_char_around_cell(Term *t, iv2 cell) { Range result = {.start = cell, .end = cell}; @@ -79,26 +79,26 @@ get_char_around_cell(Term *t, iv2 cell) return result; } -static s8 -consume(s8 raw, size count) +function s8 +consume(s8 raw, iz count) { raw.data += count; raw.len -= count; return raw; } -static u8 -peek(s8 raw, size i) +function u8 +peek(s8 raw, iz i) { ASSERT(i < raw.len); return raw.data[i]; } -static u32 +function u32 get_utf8(s8 *raw) { u32 state = 0, cp; - size off = 0; + iz off = 0; while (off < raw->len) { if (!utf8_decode(&state, &cp, raw->data[off++])) { *raw = consume(*raw, off); @@ -108,7 +108,7 @@ get_utf8(s8 *raw) return (u32)-1; } -static u32 +function u32 get_ascii(s8 *raw) { ASSERT(raw->len > 0); @@ -117,15 +117,15 @@ get_ascii(s8 *raw) return result; } -static size +function iz line_length(Line *l) { ASSERT(l->start <= l->end); return l->end - l->start; } -static s8 -line_to_s8(Line *l, RingBuf *rb) +function s8 +line_to_s8(Line *l) { ASSERT(l->start <= l->end); @@ -133,7 +133,7 @@ line_to_s8(Line *l, RingBuf *rb) return result; } -static void +function void init_line(Line *l, u8 *position, CellStyle cursor_state) { l->start = position; @@ -142,28 +142,28 @@ init_line(Line *l, u8 *position, CellStyle cursor_state) l->cursor_state = cursor_state; } -static size -feed_line(LineBuf *lb, u8 *position, CellStyle cursor_state) +function iz +feed_line(LineBuffer *lb, u8 *position, CellStyle cursor_state) { - size result = 0; - if (lb->buf[lb->widx].start != position) { - lb->buf[lb->widx++].end = position; - lb->widx = lb->widx >= lb->cap ? 0 : lb->widx; - lb->filled += lb->filled <= lb->widx; - init_line(lb->buf + lb->widx, position, cursor_state); + iz result = 0; + if (lb->data[lb->count].start != position) { + lb->data[lb->count++].end = position; + lb->count = lb->count >= lb->capacity ? 0 : lb->count; + lb->filled += lb->filled <= lb->count; + init_line(lb->data + lb->count, position, cursor_state); result = 1; } return result; } -static void +function void selection_clear(Selection *s) { s->range.end = INVALID_RANGE_END; s->state = SS_NONE; } -static void +function void selection_scroll(Term *t, i32 origin, i32 n) { Selection *s = &t->selection; @@ -183,7 +183,7 @@ selection_scroll(Term *t, i32 origin, i32 n) } } -static b32 +function b32 is_selected(Selection *s, i32 x, i32 y) { if (!is_valid_range(s->range)) @@ -195,7 +195,7 @@ is_selected(Selection *s, i32 x, i32 y) return result; } -static b32 +function b32 selection_intersects_region(Selection *s, iv2 tl, iv2 br) { /* TODO: maybe this can be further simplified (eg. with a k-map) */ @@ -210,7 +210,7 @@ selection_intersects_region(Selection *s, iv2 tl, iv2 br) return result; } -static void +function void fb_clear_region(Term *t, i32 r1, i32 r2, i32 c1, i32 c2) { BEGIN_TIMED_BLOCK(); @@ -250,7 +250,7 @@ fb_clear_region(Term *t, i32 r1, i32 r2, i32 c1, i32 c2) END_TIMED_BLOCK(); } -static void +function void fb_scroll_down(Term *t, i32 top, i32 n) { BEGIN_TIMED_BLOCK(); @@ -271,7 +271,7 @@ end: END_TIMED_BLOCK(); } -static void +function void fb_scroll_up(Term *t, i32 top, i32 n) { BEGIN_TIMED_BLOCK(); @@ -299,14 +299,14 @@ end: END_TIMED_BLOCK(); } -static void +function void swap_screen(Term *t) { t->mode.term ^= TM_ALTSCREEN; t->view_idx = !!(t->mode.term & TM_ALTSCREEN); } -static void +function void cursor_reset(Term *t) { //(Colour){.rgba = 0x1e9e33ff}; @@ -315,7 +315,7 @@ cursor_reset(Term *t) t->cursor.style.attr = ATTR_NULL; } -static void +function void cursor_move_to(Term *t, i32 row, i32 col) { i32 minr = 0, maxr = t->size.h - 1; @@ -328,7 +328,7 @@ cursor_move_to(Term *t, i32 row, i32 col) t->cursor.state &= ~CURSOR_WRAP_NEXT; } -static void +function void cursor_move_abs_to(Term *t, i32 row, i32 col) { if (t->cursor.state & CURSOR_ORIGIN) @@ -336,7 +336,7 @@ cursor_move_abs_to(Term *t, i32 row, i32 col) cursor_move_to(t, row, col); } -static void +function void cursor_alt(Term *t, b32 save) { i32 mode = t->view_idx; @@ -349,7 +349,7 @@ cursor_alt(Term *t, b32 save) } /* NOTE: advance the cursor by <n> cells; handles reverse movement */ -static void +function void cursor_step_column(Term *t, i32 n) { i32 col = t->cursor.pos.x + n; @@ -364,7 +364,7 @@ cursor_step_column(Term *t, i32 n) } /* NOTE: steps the cursor without causing a scroll */ -static void +function void cursor_step_raw(Term *t, i32 step, i32 rows, i32 cols) { rows *= step; @@ -374,7 +374,7 @@ cursor_step_raw(Term *t, i32 step, i32 rows, i32 cols) cursor_move_to(t, t->cursor.pos.y + rows, t->cursor.pos.x + cols); } -static void +function void cursor_up(Term *t, i32 requested_count) { i32 cursor_y = t->cursor.pos.y; @@ -383,7 +383,7 @@ cursor_up(Term *t, i32 requested_count) cursor_move_to(t, t->cursor.pos.y - count, t->cursor.pos.x); } -static void +function void cursor_down(Term *t, i32 requested_count) { i32 cursor_y = t->cursor.pos.y; @@ -392,7 +392,7 @@ cursor_down(Term *t, i32 requested_count) cursor_move_to(t, t->cursor.pos.y + count, t->cursor.pos.x); } -static i32 +function i32 next_tab_position(Term *t, b32 backwards) { static_assert(ARRAY_COUNT(t->tabs) == 8 * sizeof(*t->tabs), @@ -422,7 +422,7 @@ next_tab_position(Term *t, b32 backwards) return result; } -static void +function void term_tab_col(Term *t, i32 col, b32 set) { ASSERT(col < t->size.w); @@ -434,7 +434,7 @@ term_tab_col(Term *t, i32 col, b32 set) else t->tabs[idx] &= ~mask; } -static void +function void reset_tabs(Term *t, u32 column_step) { for (i32 i = 0; i < ARRAY_COUNT(t->tabs); i++) @@ -443,7 +443,7 @@ reset_tabs(Term *t, u32 column_step) term_tab_col(t, i, 1); } -static void +function void term_reset(Term *t) { i32 mode = t->mode.term & TM_ALTSCREEN; @@ -466,11 +466,11 @@ term_reset(Term *t) t->mode.term = mode|TM_AUTO_WRAP|TM_UTF8; } -static void +function void stream_push_csi(Stream *s, CSI *csi) { stream_push_s8(s, s8("ESC [")); - for (size i = 0; i < csi->raw.len; i++) { + for (iz i = 0; i < csi->raw.len; i++) { u8 c = csi->raw.data[i]; if (ISPRINT(c)) { stream_push_byte(s, csi->raw.data[i]); @@ -504,7 +504,7 @@ stream_push_csi(Stream *s, CSI *csi) } /* ED/DECSED: Erase in Display */ -static void +function void erase_in_display(Term *t, CSI *csi) { iv2 cpos = t->cursor.pos; @@ -530,7 +530,7 @@ erase_in_display(Term *t, CSI *csi) } /* EL/DECSEL: Erase in Line */ -static void +function void erase_in_line(Term *t, CSI *csi) { iv2 cpos = t->cursor.pos; @@ -549,21 +549,21 @@ erase_in_line(Term *t, CSI *csi) } /* IL: Insert <count> blank lines */ -static void +function void insert_blank_lines(Term *t, i32 count) { fb_scroll_down(t, t->cursor.pos.y, count); } /* DL: Erase <count> lines */ -static void +function void erase_lines(Term *t, i32 count) { fb_scroll_up(t, t->cursor.pos.y, count); } /* DCH: Delete <count> Characters */ -static void +function void delete_characters(Term *t, i32 requested_count) { iv2 cpos = t->cursor.pos; @@ -597,7 +597,7 @@ delete_characters(Term *t, i32 requested_count) } /* ECH: Erase <count> Characters */ -static void +function void erase_characters(Term *t, i32 count) { iv2 cpos = t->cursor.pos; @@ -605,7 +605,7 @@ erase_characters(Term *t, i32 count) } /* TBC: Tabulation Clear */ -static void +function void clear_term_tab(Term *t, i32 arg) { /* TODO: case 1, 2? */ @@ -622,12 +622,12 @@ clear_term_tab(Term *t, i32 arg) stream_push_i64(&t->error_stream, arg); stream_push_byte(&t->error_stream, '\n'); os_write_err_msg(stream_to_s8(&t->error_stream)); - t->error_stream.widx = 0; + stream_reset(&t->error_stream, 0); } } /* SM/DECSET: Set Mode & RM/DECRST Reset Mode */ -static void +function void set_mode(Term *t, CSI *csi, b32 set, ModeState src, ModeState *dest) { BEGIN_TIMED_BLOCK(); @@ -720,7 +720,7 @@ set_mode(Term *t, CSI *csi, b32 set, ModeState src, ModeState *dest) stream_push_s8(&t->error_stream, s8("set_mode: unhandled mode: ")); stream_push_csi(&t->error_stream, csi); os_write_err_msg(stream_to_s8(&t->error_stream)); - t->error_stream.widx = 0; + stream_reset(&t->error_stream, 0); } } #undef PRIV @@ -728,7 +728,7 @@ set_mode(Term *t, CSI *csi, b32 set, ModeState src, ModeState *dest) } /* NOTE: adapted from the perl script 256colres.pl in xterm src */ -static Colour +function Colour indexed_colour(i32 index) { Colour result; @@ -751,7 +751,7 @@ indexed_colour(i32 index) return result; } -static struct conversion_result +function struct conversion_result direct_colour(i32 *argv, i32 argc, i32 *idx, Stream *err) { struct conversion_result result = {.status = CR_FAILURE}; @@ -810,7 +810,7 @@ direct_colour(i32 *argv, i32 argc, i32 *idx, Stream *err) } /* SGR: Select Graphic Rendition */ -static void +function void set_colours(Term *t, CSI *csi) { BEGIN_TIMED_BLOCK(); @@ -842,7 +842,7 @@ set_colours(Term *t, CSI *csi) stream_push_s8(&t->error_stream, s8("set_colours: ")); stream_push_csi(&t->error_stream, csi); os_write_err_msg(stream_to_s8(&t->error_stream)); - t->error_stream.widx = 0; + stream_reset(&t->error_stream, 0); } break; @@ -856,7 +856,7 @@ set_colours(Term *t, CSI *csi) stream_push_s8(&t->error_stream, s8("set_colours: ")); stream_push_csi(&t->error_stream, csi); os_write_err_msg(stream_to_s8(&t->error_stream)); - t->error_stream.widx = 0; + stream_reset(&t->error_stream, 0); } break; @@ -877,14 +877,14 @@ set_colours(Term *t, CSI *csi) stream_push_byte(&t->error_stream, '\n'); stream_push_csi(&t->error_stream, csi); os_write_err_msg(stream_to_s8(&t->error_stream)); - t->error_stream.widx = 0; + stream_reset(&t->error_stream, 0); } } } END_TIMED_BLOCK(); } -static void +function void set_top_bottom_margins(Term *t, i32 requested_top, i32 requested_bottom) { i32 top = MIN(MAX(1, requested_top), t->size.h); @@ -896,23 +896,29 @@ set_top_bottom_margins(Term *t, i32 requested_top, i32 requested_bottom) } } -static void +function void window_manipulation(Term *t, CSI *csi) { switch (csi->argv[0]) { - case 22: t->platform->get_window_title(&t->saved_title); break; - case 23: t->platform->set_window_title(&t->saved_title); break; + case 22: { + u8 *title = t->os->get_window_title(); + stream_reset(&t->saved_title, 0); + stream_push_s8(&t->saved_title, c_str_to_s8((char *)title)); + /* TODO(rnp): ensure termination */ + stream_push_byte(&t->saved_title, 0); + } break; + case 23: t->os->set_window_title(stream_to_s8(&t->saved_title)); break; default: stream_push_s8(&t->error_stream, s8("unhandled xtwinops: ")); stream_push_i64(&t->error_stream, csi->argv[0]); stream_push_byte(&t->error_stream, '\n'); stream_push_csi(&t->error_stream, csi); os_write_err_msg(stream_to_s8(&t->error_stream)); - t->error_stream.widx = 0; + stream_reset(&t->error_stream, 0); } } -static void +function void push_newline(Term *t, b32 move_to_first_col) { i32 row = t->cursor.pos.y; @@ -923,7 +929,7 @@ push_newline(Term *t, b32 move_to_first_col) cursor_move_to(t, row, move_to_first_col? 0 : t->cursor.pos.x); } -static void +function void push_tab(Term *t, i32 n) { u32 end = ABS(n); @@ -931,7 +937,7 @@ push_tab(Term *t, i32 n) cursor_move_to(t, t->cursor.pos.y, next_tab_position(t, n < 0)); } -static b32 +function b32 parse_csi(s8 *r, CSI *csi) { BEGIN_TIMED_BLOCK(); @@ -970,7 +976,7 @@ end: return result; } -static void +function void handle_csi(Term *t, CSI *csi) { BEGIN_TIMED_BLOCK(); @@ -1012,7 +1018,7 @@ handle_csi(Term *t, CSI *csi) case 'n': { switch (csi->argv[0]) { case 5: /* NOTE: DSR V-1: Operating Status */ - t->platform->write(t->child, s8("\x1B[0n")); + t->os->write(t->child, s8("\x1B[0n")); break; case 6: /* NOTE: DSR V-2: Cursor Position */ { iv2 cpos = t->cursor.pos; @@ -1027,7 +1033,7 @@ handle_csi(Term *t, CSI *csi) stream_push_byte(&buf, ';'); stream_push_i64(&buf, cpos.x + 1); stream_push_byte(&buf, 'R'); - t->platform->write(t->child, stream_to_s8(&buf)); + t->os->write(t->child, stream_to_s8(&buf)); } break; default: goto unknown; } @@ -1071,12 +1077,12 @@ handle_csi(Term *t, CSI *csi) stream_push_s8(&t->error_stream, s8("unknown csi: ")); stream_push_csi(&t->error_stream, csi); os_write_err_msg(stream_to_s8(&t->error_stream)); - t->error_stream.widx = 0; + stream_reset(&t->error_stream, 0); } END_TIMED_BLOCK(); } -static b32 +function b32 parse_osc(s8 *raw, OSC *osc) { BEGIN_TIMED_BLOCK(); @@ -1121,18 +1127,18 @@ parse_osc(s8 *raw, OSC *osc) return result; } -static void +function void reset_csi(CSI *csi, s8 *raw) { *csi = (CSI){0}; csi->raw.data = raw->data; } -static void +function void dump_osc(OSC *osc, Stream *err) { stream_push_s8(err, s8("ESC]")); - for (size i = 0; i < osc->raw.len; i++) { + for (iz i = 0; i < osc->raw.len; i++) { u8 cp = osc->raw.data[i]; if (ISPRINT(cp)) { stream_push_byte(err, cp); @@ -1153,10 +1159,10 @@ dump_osc(OSC *osc, Stream *err) stream_push_i64(err, osc->arg.len); stream_push_s8(err, s8("}\n")); os_write_err_msg(stream_to_s8(err)); - err->widx = 0; + stream_reset(err, 0); } -static void +function void handle_osc(Term *t, s8 *raw, Arena a) { BEGIN_TIMED_BLOCK(); @@ -1166,9 +1172,13 @@ handle_osc(Term *t, s8 *raw, Arena a) Stream buffer = arena_stream(a); switch (osc.cmd) { - case 0: stream_push_s8(&buffer, osc.arg); t->platform->set_window_title(&buffer); break; + case 0: + case 2: { + stream_push_s8(&buffer, osc.arg); + stream_push_byte(&buffer, 0); + t->os->set_window_title(stream_to_s8(&buffer)); + } break; case 1: break; /* IGNORED: set icon name */ - case 2: stream_push_s8(&buffer, osc.arg); t->platform->set_window_title(&buffer); break; default: unknown: stream_push_s8(&t->error_stream, s8("unhandled osc cmd: ")); @@ -1178,7 +1188,7 @@ handle_osc(Term *t, s8 *raw, Arena a) END_TIMED_BLOCK(); } -static i32 +function i32 handle_escape(Term *t, s8 *raw, Arena a) { BEGIN_TIMED_BLOCK(); @@ -1216,7 +1226,7 @@ handle_escape(Term *t, s8 *raw, Arena a) stream_push_byte(&t->error_stream, cs); stream_push_byte(&t->error_stream, '\n'); os_write_err_msg(stream_to_s8(&t->error_stream)); - t->error_stream.widx = 0; + stream_reset(&t->error_stream, 0); break; } } @@ -1257,14 +1267,14 @@ handle_escape(Term *t, s8 *raw, Arena a) stream_push_hex_u64(&t->error_stream, cp); stream_push_s8(&t->error_stream, s8(")\n")); os_write_err_msg(stream_to_s8(&t->error_stream)); - t->error_stream.widx = 0; + stream_reset(&t->error_stream, 0); break; } END_TIMED_BLOCK(); return result; } -static i32 +function i32 push_control(Term *t, s8 *line, u32 cp, Arena a) { i32 result = 0; @@ -1289,7 +1299,7 @@ push_control(Term *t, s8 *line, u32 cp, Arena a) stream_push_hex_u64(&t->error_stream, cp); stream_push_byte(&t->error_stream, '\n'); os_write_err_msg(stream_to_s8(&t->error_stream)); - t->error_stream.widx = 0; + stream_reset(&t->error_stream, 0); break; } if (cp != 0x1B) { @@ -1298,7 +1308,7 @@ push_control(Term *t, s8 *line, u32 cp, Arena a) return result; } -static void +function void push_normal_cp(Term *t, TermView *tv, u32 cp) { BEGIN_TIMED_BLOCK(); @@ -1349,13 +1359,13 @@ push_normal_cp(Term *t, TermView *tv, u32 cp) END_TIMED_BLOCK(); } -static void +function void push_line(Term *t, Line *line, Arena a) { BEGIN_TIMED_BLOCK(); TermView *tv = t->views + t->view_idx; - s8 l = line_to_s8(line, &tv->log); + s8 l = line_to_s8(line); t->cursor.style = line->cursor_state; while (l.len) { @@ -1382,17 +1392,17 @@ push_line(Term *t, Line *line, Arena a) END_TIMED_BLOCK(); } -static size -get_line_idx(LineBuf *lb, size off) +function iz +get_line_idx(LineBuffer *lb, iz off) { ASSERT(-off <= lb->filled); - size result = lb->widx + off; + iz result = lb->count + off; if (result < 0) result += lb->filled; return result; } -static void +function void blit_lines(Term *t, Arena a) { BEGIN_TIMED_BLOCK(); @@ -1400,13 +1410,13 @@ blit_lines(Term *t, Arena a) ASSERT(t->gl.flags & NEEDS_REFILL); term_reset(t); - TermView *tv = t->views + t->view_idx; - size line_count = t->size.h - 1; - size off = t->scroll_offset; + TermView *tv = t->views + t->view_idx; + iz line_count = t->size.h - 1; + iz off = t->scroll_offset; CLAMP(line_count, 0, tv->lines.filled); - for (size idx = -line_count; idx <= 0; idx++) { - size line_idx = get_line_idx(&tv->lines, idx - off); - push_line(t, tv->lines.buf + line_idx, a); + for (iz idx = -line_count; idx <= 0; idx++) { + iz line_idx = get_line_idx(&tv->lines, idx - off); + push_line(t, tv->lines.data + line_idx, a); } t->gl.flags &= ~NEEDS_REFILL; @@ -1414,7 +1424,7 @@ blit_lines(Term *t, Arena a) END_TIMED_BLOCK(); } -static void +function void handle_input(Term *t, Arena a, s8 raw) { BEGIN_TIMED_BLOCK(); @@ -1423,7 +1433,7 @@ handle_input(Term *t, Arena a, s8 raw) /* TODO: SIMD look ahead */ while (raw.len) { - size start_len = raw.len; + iz start_len = raw.len; u32 cp = peek(raw, 0); /* TODO: this could be a performance issue; may need seperate code path for * terminal when not in UTF8 mode */ @@ -1436,7 +1446,7 @@ handle_input(Term *t, Arena a, s8 raw) /* NOTE(rnp): invalid/garbage cp; treat as ASCII control char */ cp = get_ascii(&raw); } else if (cp != (u32)-1) { - tv->lines.buf[tv->lines.widx].has_unicode = 1; + tv->lines.data[tv->lines.count].has_unicode = 1; } } else { cp = get_ascii(&raw); @@ -1467,12 +1477,12 @@ handle_input(Term *t, Arena a, s8 raw) ASSERT(*old == 0x1B); feed_line(&tv->lines, old, t->cursor.style); TermView *nv = t->views + t->view_idx; - size nstart = nv->log.widx; - mem_copy(raw.data, nv->log.buf + nstart, raw.len); + iz nstart = nv->log.write_index; + mem_copy(nv->log.data + nstart, raw.data, raw.len); commit_to_rb(tv, -raw.len); commit_to_rb(nv, raw.len); - raw.data = nv->log.buf + nstart; - init_line(nv->lines.buf + nv->lines.widx, raw.data, + raw.data = nv->log.data + nstart; + init_line(nv->lines.data + nv->lines.count, raw.data, t->cursor.style); tv = nv; } else if (t->cursor.pos.y != old_curs_y) { @@ -1486,13 +1496,13 @@ handle_input(Term *t, Arena a, s8 raw) } end: - tv->lines.buf[tv->lines.widx].end = raw.data; + tv->lines.data[tv->lines.count].end = raw.data; /* TODO: this shouldn't be needed */ - if (tv->lines.buf[tv->lines.widx].end < tv->lines.buf[tv->lines.widx].start) - tv->lines.buf[tv->lines.widx].start -= tv->log.cap; + if (tv->lines.data[tv->lines.count].end < tv->lines.data[tv->lines.count].start) + tv->lines.data[tv->lines.count].start -= tv->log.capacity; - if (!t->escape && line_length(tv->lines.buf + tv->lines.widx) > SPLIT_LONG) + if (!t->escape && line_length(tv->lines.data + tv->lines.count) > SPLIT_LONG) feed_line(&tv->lines, raw.data, t->cursor.style); t->unprocessed_bytes = raw.len; diff --git a/tests/test-common.c b/tests/test-common.c @@ -3,7 +3,7 @@ #include "config.h" /* NOTE: stubs for stuff we aren't testing */ -static void get_gpu_glyph_index(Arena, void *, void *, u32, u32, u32, CachedGlyph **); +function void get_gpu_glyph_index(Arena, void *, void *, u32, u32, u32, CachedGlyph **); KEYBIND_FN(copy) { return 0; } KEYBIND_FN(paste) { return 0; } @@ -13,7 +13,7 @@ KEYBIND_FN(zoom) { return 0; } #include "font.c" #include "terminal.c" -static PLATFORM_WRITE_FN(test_write) +function OS_WRITE_FN(test_write) { /* NOTE(rnp): for testing the caller will provide a stream via the platform * child handle. Then this function writes into it and the caller can compare @@ -23,70 +23,67 @@ static PLATFORM_WRITE_FN(test_write) return !s->errors; } -static PLATFORM_WINDOW_TITLE_FN(test_get_window_title) +function OS_GET_WINDOW_TITLE_FN(test_get_window_title) { - ASSERT(buffer); - stream_push_s8(buffer, s8("test_title")); + return (u8 *)"test_title"; } -static PLATFORM_WINDOW_TITLE_FN(test_set_window_title) +function OS_SET_WINDOW_TITLE_FN(test_set_window_title) { - ASSERT(buffer); - stream_push_byte(buffer, 0); + ASSERT(title.len); } -static size -copy_into_ringbuf(RingBuf *rb, s8 raw) +function iz +copy_into_ringbuf(OSRingBuffer *rb, s8 raw) { - ASSERT(raw.len < rb->cap); - for (size i = 0; i < raw.len; i++) - rb->buf[rb->widx + i] = raw.data[i]; + ASSERT(raw.len < rb->capacity); + mem_copy(rb->data + rb->write_index, raw.data, raw.len); - rb->widx += raw.len; - rb->filled += raw.len; + rb->write_index += raw.len; + rb->filled += raw.len; - CLAMP(rb->filled, 0, rb->cap); - if (rb->widx >= rb->cap) - rb->widx -= rb->cap; + CLAMP(rb->filled, 0, rb->capacity); + if (rb->write_index >= rb->capacity) + rb->write_index -= rb->capacity; ASSERT(rb->filled >= 0); - ASSERT(rb->widx >= 0 && rb->widx < rb->cap); + ASSERT(rb->write_index >= 0 && rb->write_index < rb->capacity); return raw.len; } -static s8 +function s8 launder_static_string(Term *term, s8 static_str) { - RingBuf *rb = &term->views[term->view_idx].log; + OSRingBuffer *rb = &term->views[term->view_idx].log; term->unprocessed_bytes += copy_into_ringbuf(rb, static_str); s8 raw = { .len = term->unprocessed_bytes, - .data = rb->buf + (rb->widx - term->unprocessed_bytes) + .data = rb->data + (rb->write_index - term->unprocessed_bytes) }; return raw; } -static Term * -place_term_into_memory(MemoryBlock memory, i32 rows, i32 columns) +function Term * +place_term_into_memory(OSMemoryBlock memory, i32 rows, i32 columns) { Arena tmp = arena_from_memory_block(memory); Term *t = push_struct(&tmp, Term); t->size = (iv2){.w = 80, .h = 24}; - t->platform = push_struct(&tmp, typeof(*t->platform)); - t->platform->set_window_title = test_set_window_title; - t->platform->get_window_title = test_get_window_title; - t->platform->write = test_write; + t->os = push_struct(&tmp, typeof(*t->os)); + t->os->set_window_title = test_set_window_title; + t->os->get_window_title = test_get_window_title; + t->os->write = test_write; t->arena_for_frame = tmp; - os_allocate_ring_buffer(&t->views[0].log, MB(2)); - line_buf_alloc(&t->views[0].lines, &t->arena_for_frame, t->views[0].log.buf, t->cursor.style, - BACKLOG_LINES); + t->views[0].log = os_allocate_ring_buffer(MB(2)); + t->views[0].lines = line_buffer_alloc(&t->arena_for_frame, t->views[0].log.data, + t->cursor.style, BACKLOG_LINES); - os_allocate_ring_buffer(&t->views[1].log, MB(2)); - line_buf_alloc(&t->views[1].lines, &t->arena_for_frame, t->views[1].log.buf, t->cursor.style, - ALT_BACKLOG_LINES); + t->views[1].log = os_allocate_ring_buffer(MB(2)); + t->views[1].lines = line_buffer_alloc(&t->arena_for_frame, t->views[1].log.data, + t->cursor.style, ALT_BACKLOG_LINES); t->views[0].fb.backing_store = memory_block_from_arena(&t->arena_for_frame, MB(1)); t->views[1].fb.backing_store = memory_block_from_arena(&t->arena_for_frame, MB(1)); @@ -103,8 +100,8 @@ place_term_into_memory(MemoryBlock memory, i32 rows, i32 columns) void *malloc(size_t); void free(void *); -static void -release_term_memory(MemoryBlock backing) +function void +release_term_memory(OSMemoryBlock backing) { Term *t = backing.memory; os_release_ring_buffer(&t->views[0].log); diff --git a/tests/test-fuzz.c b/tests/test-fuzz.c @@ -1,7 +1,7 @@ /* See LICENSE for copyright details */ #include "test-common.c" -static void +function void fuzz_entry_point(s8 data, Stream error_stream) { MemoryBlock term_backing = {.memory = malloc(MB(4)), .size = MB(4)}; diff --git a/tests/test.c b/tests/test.c @@ -74,7 +74,7 @@ GHOSTTY_TESTS #undef X #define X(f) {.implementation = f, .name = s8(#f)}, -static Test tests[] = { +global Test tests[] = { TESTS GHOSTTY_TESTS }; @@ -83,11 +83,11 @@ static Test tests[] = { #define ESC(a) s8("\x1B"#a) #define CSI(a) ESC([a) -static s8 failure_string = s8("\x1B[31mFAILURE\x1B[0m\n"); -static s8 success_string = s8("\x1B[32mSUCCESS\x1B[0m\n"); -static s8 unsupported_string = s8("\x1B[33mUNSUPPORTED\x1B[0m\n"); +global s8 failure_string = s8("\x1B[31mFAILURE\x1B[0m\n"); +global s8 success_string = s8("\x1B[32mSUCCESS\x1B[0m\n"); +global s8 unsupported_string = s8("\x1B[33mUNSUPPORTED\x1B[0m\n"); -static b32 +function b32 check_cells_equal(Cell *a, Cell *b) { b32 result = a->cp == b->cp && @@ -96,7 +96,7 @@ check_cells_equal(Cell *a, Cell *b) return result; } -static TEST_FN(csi_embedded_control) +function TEST_FN(csi_embedded_control) { /* NOTE: print a '1' with default style then start changing the colour, * but backspace within the escape sequence so the cursor is now over the @@ -131,13 +131,13 @@ static TEST_FN(csi_embedded_control) result &= check_cells_equal(&c1, &term->views[term->view_idx].fb.rows[0][0]); result &= check_cells_equal(&c2, &term->views[term->view_idx].fb.rows[1][1]); /* NOTE: we also want to ensure that we cannot split a line in the middle of a CSI */ - LineBuf *lb = &term->views[0].lines; - result &= lb->filled == 0 && *lb->buf[lb->widx].start != '2'; + LineBuffer *lb = &term->views[0].lines; + result &= lb->filled == 0 && *lb->data[lb->count].start != '2'; return result; } -static TEST_FN(colour_setting) +function TEST_FN(colour_setting) { launder_static_string(term, CSI(8m)); launder_static_string(term, CSI(4m)); @@ -159,7 +159,7 @@ static TEST_FN(colour_setting) return result; } -static TEST_FN(cursor_at_line_boundary) +function TEST_FN(cursor_at_line_boundary) { /* NOTE: Test that lines are not split in the middle of utf-8 characters */ s8 long_line = s8alloc(&arena, 8192); @@ -179,8 +179,8 @@ static TEST_FN(cursor_at_line_boundary) s8 raw = launder_static_string(term, (s8){.len = SPLIT_LONG + 1, .data = long_line.data}); long_line = consume(long_line, SPLIT_LONG + 1); - LineBuf *lb = &term->views[term->view_idx].lines; - size line_count = lb->filled; + LineBuffer *lb = &term->views[term->view_idx].lines; + iz line_count = lb->filled; handle_input(term, arena, raw); /* NOTE: ensure line didn't split on red dragon */ @@ -201,12 +201,12 @@ static TEST_FN(cursor_at_line_boundary) raw = launder_static_string(term, long_line); handle_input(term, arena, raw); - result &= line_length(lb->buf) > SPLIT_LONG; + result &= line_length(lb->data) > SPLIT_LONG; return result; } -static TEST_FN(cursor_tabs) +function TEST_FN(cursor_tabs) { /* NOTE: first test advancing to a tabstop */ s8 raw = launder_static_string(term, s8("123\t")); @@ -226,7 +226,7 @@ static TEST_FN(cursor_tabs) return result; } -static TEST_FN(cursor_tabs_across_boundary) +function TEST_FN(cursor_tabs_across_boundary) { /* NOTE: clear tabstops then set one beyond multiple boundaries */ launder_static_string(term, CSI(3g)); @@ -271,12 +271,12 @@ static TEST_FN(cursor_tabs_across_boundary) return result; } -static TEST_FN(working_ringbuffer) +function TEST_FN(working_ringbuffer) { - RingBuf *rb = &term->views[term->view_idx].log; - rb->buf[0] = 0xFE; - TestResult result = (rb->buf[0] == rb->buf[ rb->cap]) && - (rb->buf[0] == rb->buf[-rb->cap]); + OSRingBuffer *rb = &term->views[term->view_idx].log; + rb->data[0] = 0xFE; + TestResult result = (rb->data[0] == rb->data[ rb->capacity]) && + (rb->data[0] == rb->data[-rb->capacity]); return result; } @@ -285,7 +285,7 @@ static TEST_FN(working_ringbuffer) /***********************************************/ /* NOTE: CBT V-1: Left Beyond First Column */ -static TEST_FN(cursor_backwards_tabulation_v1) +function TEST_FN(cursor_backwards_tabulation_v1) { launder_static_string(term, s8("\n")); launder_static_string(term, CSI(?5W)); @@ -301,7 +301,7 @@ static TEST_FN(cursor_backwards_tabulation_v1) } /* NOTE: CBT V-2: Left Starting After Tab Stop */ -static TEST_FN(cursor_backwards_tabulation_v2) +function TEST_FN(cursor_backwards_tabulation_v2) { launder_static_string(term, CSI(?5W)); launder_static_string(term, CSI(1;10H)); @@ -318,7 +318,7 @@ static TEST_FN(cursor_backwards_tabulation_v2) } /* NOTE: CBT V-3: Left Starting on Tabstop */ -static TEST_FN(cursor_backwards_tabulation_v3) +function TEST_FN(cursor_backwards_tabulation_v3) { launder_static_string(term, CSI(?5W)); launder_static_string(term, CSI(1;9H)); @@ -337,7 +337,7 @@ static TEST_FN(cursor_backwards_tabulation_v3) } /* NOTE: CBT V-4: Left Margin with Origin Mode */ -static TEST_FN(cursor_backwards_tabulation_v4) +function TEST_FN(cursor_backwards_tabulation_v4) { TestResult result = UNSUPPORTED; @@ -363,10 +363,10 @@ static TEST_FN(cursor_backwards_tabulation_v4) } /* NOTE: CUB V-1: Pending Wrap is Unset */ -static TEST_FN(cursor_backwards_v1) +function TEST_FN(cursor_backwards_v1) { u8 buffer_store[32]; - Stream buffer = {.buf = buffer_store, .cap = sizeof(buffer_store)}; + Stream buffer = {.data = buffer_store, .capacity = sizeof(buffer_store)}; stream_push_s8(&buffer, s8("\x1B[")); stream_push_u64(&buffer, term->size.w); stream_push_byte(&buffer, 'G'); @@ -387,7 +387,7 @@ static TEST_FN(cursor_backwards_v1) } /* NOTE: CUB V-2: Leftmost Boundary with Reverse Wrap Disabled */ -static TEST_FN(cursor_backwards_v2) +function TEST_FN(cursor_backwards_v2) { launder_static_string(term, CSI(?45l)); launder_static_string(term, s8("A\r\n")); @@ -404,7 +404,7 @@ static TEST_FN(cursor_backwards_v2) } /* NOTE: CUB V-3: Reverse Wrap */ -static TEST_FN(cursor_backwards_v3) +function TEST_FN(cursor_backwards_v3) { TestResult result = UNSUPPORTED; @@ -434,7 +434,7 @@ static TEST_FN(cursor_backwards_v3) } /* NOTE: CUB V-4: Extended Reverse Wrap Single Line */ -static TEST_FN(cursor_backwards_v4) +function TEST_FN(cursor_backwards_v4) { TestResult result = UNSUPPORTED; @@ -458,7 +458,7 @@ static TEST_FN(cursor_backwards_v4) } /* NOTE: CUB V-5: Extended Reverse Wrap Wraps to Bottom */ -static TEST_FN(cursor_backwards_v5) +function TEST_FN(cursor_backwards_v5) { TestResult result = UNSUPPORTED; @@ -491,7 +491,7 @@ static TEST_FN(cursor_backwards_v5) } /* NOTE: CUB V-6: Reverse Wrap Outside of Margins */ -static TEST_FN(cursor_backwards_v6) +function TEST_FN(cursor_backwards_v6) { TestResult result = UNSUPPORTED; @@ -510,7 +510,7 @@ static TEST_FN(cursor_backwards_v6) } /* NOTE: CUB V-7: Reverse Wrap with Pending Wrap State */ -static TEST_FN(cursor_backwards_v7) +function TEST_FN(cursor_backwards_v7) { TestResult result = UNSUPPORTED; @@ -543,7 +543,7 @@ static TEST_FN(cursor_backwards_v7) } /* NOTE: CUD V-1: Cursor Down */ -static TEST_FN(cursor_down_v1) +function TEST_FN(cursor_down_v1) { launder_static_string(term, s8("A")); launder_static_string(term, CSI(2B)); @@ -559,7 +559,7 @@ static TEST_FN(cursor_down_v1) } /* NOTE: CUD V-2: Cursor Down Above Bottom Margin */ -static TEST_FN(cursor_down_v2) +function TEST_FN(cursor_down_v2) { launder_static_string(term, CSI(1;3r)); launder_static_string(term, s8("A")); @@ -576,7 +576,7 @@ static TEST_FN(cursor_down_v2) } /* NOTE: CUD V-3: Cursor Down Below Bottom Margin */ -static TEST_FN(cursor_down_v3) +function TEST_FN(cursor_down_v3) { launder_static_string(term, CSI(1;3r)); launder_static_string(term, s8("A")); @@ -594,7 +594,7 @@ static TEST_FN(cursor_down_v3) } /* NOTE: CUP V-1: Normal Usage */ -static TEST_FN(cursor_position_v1) +function TEST_FN(cursor_position_v1) { launder_static_string(term, CSI(2;3H)); s8 raw = launder_static_string(term, s8("A")); @@ -608,7 +608,7 @@ static TEST_FN(cursor_position_v1) } /* NOTE: CUP V-2: Off the Screen */ -static TEST_FN(cursor_position_v2) +function TEST_FN(cursor_position_v2) { launder_static_string(term, CSI(500;500H)); s8 raw = launder_static_string(term, s8("A")); @@ -624,7 +624,7 @@ static TEST_FN(cursor_position_v2) } /* NOTE: CUP V-3: Relative to Origin */ -static TEST_FN(cursor_position_v3) +function TEST_FN(cursor_position_v3) { launder_static_string(term, CSI(2;3r)); launder_static_string(term, CSI(?6h)); @@ -638,7 +638,7 @@ static TEST_FN(cursor_position_v3) } /* NOTE: CUP V-4: Relative to Origin with Left/Right Margins */ -static TEST_FN(cursor_position_v4) +function TEST_FN(cursor_position_v4) { TestResult result = UNSUPPORTED; @@ -658,7 +658,7 @@ static TEST_FN(cursor_position_v4) } /* NOTE: CUP V-5: Limits with Scroll Region and Origin Mode */ -static TEST_FN(cursor_position_v5) +function TEST_FN(cursor_position_v5) { TestResult result = UNSUPPORTED; @@ -678,10 +678,10 @@ static TEST_FN(cursor_position_v5) } /* NOTE: CUP V-6: Pending Wrap is Unset */ -static TEST_FN(cursor_position_v6) +function TEST_FN(cursor_position_v6) { u8 buffer_store[32]; - Stream buffer = {.buf = buffer_store, .cap = sizeof(buffer_store)}; + Stream buffer = {.data = buffer_store, .capacity = sizeof(buffer_store)}; stream_push_s8(&buffer, s8("\x1B[")); stream_push_u64(&buffer, term->size.w); stream_push_byte(&buffer, 'G'); @@ -701,7 +701,7 @@ static TEST_FN(cursor_position_v6) } /* NOTE: CUU V-1: Cursor Up */ -static TEST_FN(cursor_up_v1) +function TEST_FN(cursor_up_v1) { launder_static_string(term, CSI(3;1H)); launder_static_string(term, s8("A")); @@ -718,7 +718,7 @@ static TEST_FN(cursor_up_v1) } /* NOTE: CUU V-2: Cursor Up Below Top Margin */ -static TEST_FN(cursor_up_v2) +function TEST_FN(cursor_up_v2) { launder_static_string(term, CSI(2;4r)); launder_static_string(term, CSI(3;1H)); @@ -736,7 +736,7 @@ static TEST_FN(cursor_up_v2) } /* NOTE: CUU V-3: Cursor Up Above Top Margin */ -static TEST_FN(cursor_up_v3) +function TEST_FN(cursor_up_v3) { launder_static_string(term, CSI(3;5r)); launder_static_string(term, CSI(3;1H)); @@ -756,7 +756,7 @@ static TEST_FN(cursor_up_v3) /* NOTE: DCH V-1: Simple Delete Character */ -static TEST_FN(delete_characters_v1) +function TEST_FN(delete_characters_v1) { launder_static_string(term, s8("ABC123")); launder_static_string(term, CSI(3G)); @@ -776,7 +776,7 @@ static TEST_FN(delete_characters_v1) } /* NOTE: DCH V-2: SGR State */ -static TEST_FN(delete_characters_v2) +function TEST_FN(delete_characters_v2) { launder_static_string(term, s8("ABC123")); launder_static_string(term, CSI(3G)); @@ -802,7 +802,7 @@ static TEST_FN(delete_characters_v2) } /* NOTE: DCH V-3: Outside Left/Right Scroll Region */ -static TEST_FN(delete_characters_v3) +function TEST_FN(delete_characters_v3) { TestResult result = UNSUPPORTED; @@ -828,7 +828,7 @@ static TEST_FN(delete_characters_v3) } /* NOTE: DCH V-4: Inside Left/Right Scroll Region */ -static TEST_FN(delete_characters_v4) +function TEST_FN(delete_characters_v4) { TestResult result = UNSUPPORTED; @@ -854,7 +854,7 @@ static TEST_FN(delete_characters_v4) } /* NOTE: DCH V-5: Split Wide Character */ -static TEST_FN(delete_characters_v5) +function TEST_FN(delete_characters_v5) { launder_static_string(term, s8("A橋123")); launder_static_string(term, CSI(3G)); @@ -873,7 +873,7 @@ static TEST_FN(delete_characters_v5) } /* NOTE: DECSTBM V-1: Full Screen */ -static TEST_FN(set_top_bottom_margins_v1) +function TEST_FN(set_top_bottom_margins_v1) { launder_static_string(term, s8("ABC\r\nDEF\r\nGHI\r\n")); launder_static_string(term, CSI(r)); @@ -900,7 +900,7 @@ static TEST_FN(set_top_bottom_margins_v1) } /* NOTE: DECSTBM V-2: Top Only */ -static TEST_FN(set_top_bottom_margins_v2) +function TEST_FN(set_top_bottom_margins_v2) { launder_static_string(term, s8("ABC\r\nDEF\r\nGHI\r\n")); launder_static_string(term, CSI(2r)); @@ -926,7 +926,7 @@ static TEST_FN(set_top_bottom_margins_v2) } /* NOTE: DECSTBM V-3: Top and Bottom */ -static TEST_FN(set_top_bottom_margins_v3) +function TEST_FN(set_top_bottom_margins_v3) { launder_static_string(term, s8("ABC\r\nDEF\r\nGHI\r\n")); launder_static_string(term, CSI(1;2r)); @@ -949,7 +949,7 @@ static TEST_FN(set_top_bottom_margins_v3) } /* NOTE: DECSTBM V-4: Top Equal to Bottom */ -static TEST_FN(set_top_bottom_margins_v4) +function TEST_FN(set_top_bottom_margins_v4) { launder_static_string(term, s8("ABC\r\nDEF\r\nGHI\r\n")); launder_static_string(term, CSI(2;2r)); @@ -975,24 +975,24 @@ static TEST_FN(set_top_bottom_margins_v4) } /* NOTE: DSR V-1: Operating Status */ -static TEST_FN(device_status_report_v1) +function TEST_FN(device_status_report_v1) { - Stream buffer = {.buf = malloc(KB(1)), .cap = KB(1)}; + Stream buffer = {.data = malloc(KB(1)), .capacity = KB(1)}; term->child = (iptr)&buffer; s8 raw = launder_static_string(term, CSI(5n)); handle_input(term, arena, raw); TestResult result = s8_equal(s8("\x1B[0n"), stream_to_s8(&buffer)); - free(buffer.buf); + free(buffer.data); return result; } /* NOTE: DSR V-2: Cursor Position */ -static TEST_FN(device_status_report_v2) +function TEST_FN(device_status_report_v2) { - Stream buffer = {.buf = malloc(KB(1)), .cap = KB(1)}; + Stream buffer = {.data = malloc(KB(1)), .capacity = KB(1)}; term->child = (iptr)&buffer; launder_static_string(term, CSI(2;4H)); @@ -1001,7 +1001,7 @@ static TEST_FN(device_status_report_v2) TestResult result = s8_equal(s8("\x1B[2;4R"), stream_to_s8(&buffer)); - free(buffer.buf); + free(buffer.data); return result; } @@ -1023,13 +1023,13 @@ main(void) u32 failure_count = 0; for (u32 i = 0; i < ARRAY_COUNT(tests); i++) { - MemoryBlock term_backing = {.memory = malloc(MB(4)), .size = MB(4)}; + OSMemoryBlock term_backing = {.memory = malloc(MB(4)), .size = MB(4)}; /* TODO(rnp): term sizes as part of the tests */ Term *term = place_term_into_memory(term_backing, 24, 80); TestResult result = tests[i].implementation(term, term->arena_for_frame); stream_push_s8(&log, tests[i].name); stream_push_s8(&log, s8(":")); - size count = tests[i].name.len; + iz count = tests[i].name.len; while (count < max_name_len) { stream_push_byte(&log, ' '); count++; } switch (result) { case FAILURE: stream_push_s8(&log, failure_string); failure_count++; break; diff --git a/util.c b/util.c @@ -1,21 +1,21 @@ /* See LICENSE for copyright details */ /* NOTE: avoids braindead standards committee UB when n is negative or shift is > 31 */ -static u32 +function u32 safe_left_shift(u32 n, u32 shift) { u64 result = (u64)n << (shift & 63); return result & 0xFFFFFFFF; } -static u32 +function u32 round_down_power_of_2(u32 a) { u32 result = 0x80000000UL >> clz_u32(a); return result; } -static v2 +function v2 sub_v2(v2 a, v2 b) { v2 result; @@ -24,49 +24,49 @@ sub_v2(v2 a, v2 b) return result; } -static f32 +function f32 length_v2(v2 a) { f32 result = a.x * a.x + a.y * a.y; return result; } -static b32 +function b32 equal_iv2(iv2 a, iv2 b) { b32 result = a.x == b.x && a.y == b.y; return result; } -static b32 +function b32 equal_uv2(uv2 a, uv2 b) { b32 result = a.x == b.x && a.y == b.y; return result; } -static v2 +function v2 v2_from_iv2(iv2 a) { v2 result = {.x = a.x, .y = a.y}; return result; } -static b32 +function b32 is_valid_range(Range r) { b32 result = !equal_iv2(r.end, INVALID_RANGE_END); return result; } -static b32 +function b32 equal_range(Range a, Range b) { b32 result = equal_iv2(a.start, b.start) && equal_iv2(a.end, b.end); return result; } -static b32 +function b32 point_in_rect(v2 point, Rect rect) { v2 max = {.x = rect.pos.x + rect.size.w, .y = rect.pos.y + rect.size.h}; @@ -74,7 +74,7 @@ point_in_rect(v2 point, Rect rect) return result; } -static Range +function Range normalize_range(Range r) { Range result; @@ -92,7 +92,7 @@ normalize_range(Range r) /* NOTE(rnp): based on nullprogram's lock-free, concurrent, * generic queue in 32 bits */ -static i32 +function i32 work_queue_push(u32 *q, u32 capacity) { ASSERT(ISPOWEROFTWO(capacity)); @@ -106,13 +106,13 @@ work_queue_push(u32 *q, u32 capacity) return next == tail ? -1 : head; } -static void +function void work_queue_push_commit(u32 *q) { atomic_fetch_add(q, 1); } -static i32 +function i32 work_queue_pop(u32 *q, u32 capacity) { ASSERT(ISPOWEROFTWO(capacity)); @@ -123,13 +123,13 @@ work_queue_pop(u32 *q, u32 capacity) return head == tail ? -1 : tail; } -static void +function void work_queue_pop_commit(u32 *q) { atomic_fetch_add(q, 0x10000u); } -static b32 +function b32 work_queue_empty(u32 *q, u32 capacity) { ASSERT(ISPOWEROFTWO(capacity)); @@ -140,7 +140,7 @@ work_queue_empty(u32 *q, u32 capacity) return head == tail; } -static void +function void work_queue_insert(Term *t, u32 type, void *ctx) { i32 index = work_queue_push(&t->work_queue, t->work_queue_capacity); @@ -151,8 +151,8 @@ work_queue_insert(Term *t, u32 type, void *ctx) t->work_queue_items[index].ctx = ctx; } -static void -mem_copy(void *restrict src, void *restrict dest, size len) +function void +mem_copy(void *restrict dest, void *restrict src, iz len) { ASSERT(len >= 0); u8 *s = src, *d = dest; @@ -160,8 +160,8 @@ mem_copy(void *restrict src, void *restrict dest, size len) } #define zero_struct(s) mem_clear(s, 0, sizeof(typeof(*s))) -static void * -mem_clear(void *p_, u8 c, size len) +function void * +mem_clear(void *p_, u8 c, iz len) { u8 *p = p_; while (len) p[--len] = c; @@ -170,11 +170,11 @@ mem_clear(void *p_, u8 c, size len) #define push_struct(a, t) alloc(a, t, 1) #define alloc(a, t, n) (t *)alloc_(a, sizeof(t), _Alignof(t), n) -static void * -alloc_(Arena *a, size len, size align, size count) +function void * +alloc_(Arena *a, iz len, iz align, iz count) { - size padding = -(uintptr_t)a->beg & (align - 1); - size available = a->end - a->beg - padding; + iz padding = -(uintptr_t)a->beg & (align - 1); + iz available = a->end - a->beg - padding; if (available <= 0 || available / len < count) { ASSERT(0); } @@ -184,8 +184,8 @@ alloc_(Arena *a, size len, size align, size count) return mem_clear(p, 0, count * len); } -static Arena -arena_from_memory_block(MemoryBlock memory) +function Arena +arena_from_memory_block(OSMemoryBlock memory) { Arena result; result.beg = memory.memory; @@ -193,17 +193,17 @@ arena_from_memory_block(MemoryBlock memory) return result; } -static MemoryBlock -memory_block_from_arena(Arena *a, size requested_size) +function OSMemoryBlock +memory_block_from_arena(Arena *a, iz requested_size) { - MemoryBlock result; + OSMemoryBlock result; result.memory = alloc_(a, requested_size, 64, 1); result.size = requested_size; return result; } -static Arena -sub_arena(Arena *a, size size) +function Arena +sub_arena(Arena *a, iz size) { Arena result = {0}; result.beg = alloc_(a, size, 64, 1); @@ -211,7 +211,7 @@ sub_arena(Arena *a, size size) return result; } -static TempArena +function TempArena begin_temp_arena(Arena *a) { TempArena result; @@ -220,7 +220,7 @@ begin_temp_arena(Arena *a) return result; } -static void +function void end_temp_arena(TempArena ta) { Arena *a = ta.arena; @@ -230,63 +230,67 @@ end_temp_arena(TempArena ta) /* NOTE: This performs wrapping of the ring buffer as needed; since a line could be in * progress this must also adjust the start and end of the current line */ -static void -commit_to_rb(TermView *tv, size len) +function void +commit_to_rb(TermView *tv, iz len) { - ASSERT(ABS(len) <= tv->log.cap); + ASSERT(ABS(len) <= tv->log.capacity); - tv->log.widx += len; - tv->log.filled += len; + tv->log.write_index += len; + tv->log.filled += len; - CLAMP(tv->log.filled, 0, tv->log.cap); - if (tv->log.widx >= tv->log.cap) { - tv->log.widx -= tv->log.cap; + CLAMP(tv->log.filled, 0, tv->log.capacity); + if (tv->log.write_index >= tv->log.capacity) { + tv->log.write_index -= tv->log.capacity; - size line = tv->lines.widx; - tv->lines.buf[line].start -= tv->log.cap; - tv->lines.buf[line].end -= tv->log.cap; + iz line = tv->lines.count; + tv->lines.data[line].start -= tv->log.capacity; + tv->lines.data[line].end -= tv->log.capacity; } ASSERT(tv->log.filled >= 0); - ASSERT(tv->log.widx >= 0 && tv->log.widx < tv->log.cap); + ASSERT(tv->log.write_index >= 0 && tv->log.write_index < tv->log.capacity); } -static void -line_buf_alloc(LineBuf *lb, Arena *a, u8 *start_position, CellStyle state, size capacity) +function LineBuffer +line_buffer_alloc(Arena *a, u8 *start_position, CellStyle state, iz capacity) { - lb->cap = capacity; - lb->filled = 0; - lb->widx = 0; - lb->buf = alloc(a, Line, capacity); - lb->buf[0].start = start_position; - lb->buf[0].end = start_position; - lb->buf[0].cursor_state = state; + LineBuffer result = {0}; + result.capacity = capacity; + result.data = alloc(a, Line, capacity); + result.data[0].start = start_position; + result.data[0].end = start_position; + result.data[0].cursor_state = state; + return result; } -static s8 -s8alloc(Arena *a, size len) +function s8 +s8alloc(Arena *a, iz len) { return (s8){.len = len, .data = alloc(a, u8, len)}; } -static s8 +function s8 c_str_to_s8(char *s) { s8 result = {.data = (u8 *)s}; - while (*s) { result.len++; s++; } + if (s) { + char *end = s; + while (*end) end++; + result.len = end - s; + } return result; } -static b32 +function b32 s8_equal(s8 a, s8 b) { b32 result = a.len == b.len; - for (size i = 0; result && i < a.len; i++) + for (iz i = 0; result && i < a.len; i++) result = a.data[i] == b.data[i]; return result; } -static b32 +function b32 s8_prefix_of(s8 s, s8 match) { b32 result = 0; @@ -297,10 +301,10 @@ s8_prefix_of(s8 s, s8 match) return result; } -static s8 +function s8 s8_chop_at(s8 raw, u8 delim) { - size i; + iz i; for (i = 0; i < raw.len; i++) { if (raw.data[i] == delim) break; @@ -309,12 +313,12 @@ s8_chop_at(s8 raw, u8 delim) return result; } -static void +function void s8_parse_i32_accum(struct conversion_result *result, s8 raw) { result->status = CR_SUCCESS; - size i = 0; + iz i = 0; i32 scale = 1; if (raw.len && raw.data[0] == '-') { scale = -1; @@ -342,7 +346,7 @@ s8_parse_i32_accum(struct conversion_result *result, s8 raw) result->i *= scale; } -static struct conversion_result +function struct conversion_result s8_parse_i32(s8 raw) { struct conversion_result result = {0}; @@ -350,7 +354,7 @@ s8_parse_i32(s8 raw) return result; } -static struct conversion_result +function struct conversion_result s8_parse_i32_until(s8 raw, u8 delim) { s8 chopped = s8_chop_at(raw, delim); @@ -359,51 +363,58 @@ s8_parse_i32_until(s8 raw, u8 delim) return result; } -static Stream +function Stream arena_stream(Arena a) { - Stream result = {0}; - result.cap = a.end - a.beg; - result.buf = (typeof(result.buf))a.beg; + Stream result = {0}; + result.capacity = a.end - a.beg; + result.data = a.beg; return result; } -static Stream -stream_alloc(Arena *a, u32 cap) +function void +stream_reset(Stream *s, iz index) +{ + s->errors = index < 0 || index > s->capacity; + if (!s->errors) s->count = index; +} + +function Stream +stream_alloc(Arena *a, i32 capacity) { - Stream result = {0}; - result.cap = cap; - result.buf = alloc(a, typeof(*result.buf), cap); + Stream result = {0}; + result.capacity = capacity; + result.data = alloc(a, u8, capacity); return result; } -static s8 +function s8 stream_to_s8(Stream *s) { - s8 result = {.len = s->widx, .data = s->buf}; + s8 result = {.len = s->count, .data = s->data}; return result; } -static void +function void stream_push_byte(Stream *s, u8 cp) { - s->errors |= !(s->cap - s->widx); + s->errors |= !(s->capacity - s->count); if (!s->errors) - s->buf[s->widx++] = cp; + s->data[s->count++] = cp; } -static void +function void stream_push_s8(Stream *s, s8 str) { - s->errors |= (s->cap - s->widx) < str.len; + s->errors |= (s->capacity - s->count) < str.len; if (!s->errors) { - for (size i = 0; i < str.len; i++) - s->buf[s->widx++] = str.data[i]; + mem_copy(s->data + s->count, str.data, str.len); + s->count += str.len; } } /* NOTE: how can he get away with not using a library for this */ -static void +function void stream_push_s8_left_padded(Stream *s, s8 str, u32 width) { for (u32 i = str.len; i < width; i++) @@ -411,13 +422,13 @@ stream_push_s8_left_padded(Stream *s, s8 str, u32 width) stream_push_s8(s, str); } -static void +function void stream_push_s8s(Stream *s, u32 count, s8 *strs) { while (count) { stream_push_s8(s, *strs++); count--; } } -static void +function void stream_push_hex_u64(Stream *s, u64 n) { if (!s->errors) { @@ -435,7 +446,7 @@ stream_push_hex_u64(Stream *s, u64 n) } } -static void +function void stream_push_u64_padded(Stream *s, u64 n, u32 width) { if (!s->errors) { @@ -451,13 +462,13 @@ stream_push_u64_padded(Stream *s, u64 n, u32 width) } } -static void +function void stream_push_u64(Stream *s, u64 n) { stream_push_u64_padded(s, n, 0); } -static void +function void stream_push_i64(Stream *s, i64 n) { s->errors |= n == I64_MIN; @@ -470,7 +481,7 @@ stream_push_i64(Stream *s, i64 n) } } -static void +function void stream_push_iv2(Stream *s, iv2 v) { stream_push_byte(s, '{'); @@ -480,7 +491,7 @@ stream_push_iv2(Stream *s, iv2 v) stream_push_byte(s, '}'); } -static void +function void stream_push_f64(Stream *s, f64 f, i64 prec) { if (f < 0) { @@ -506,7 +517,7 @@ stream_push_f64(Stream *s, f64 f, i64 prec) } } -static SelectionIterator +function SelectionIterator selection_iterator(Range s, Row *rows, u32 term_width) { SelectionIterator result; @@ -518,7 +529,7 @@ selection_iterator(Range s, Row *rows, u32 term_width) return result; } -static Cell * +function Cell * selection_next(SelectionIterator *s) { Cell *result = 0; @@ -535,7 +546,7 @@ selection_next(SelectionIterator *s) return result; } -static s8 +function s8 envp_lookup(s8 name, char **e) { ASSERT(name.data[name.len - 1] == '='); @@ -556,7 +567,7 @@ envp_lookup(s8 name, char **e) return result; } -static c8 ** +function c8 ** construct_c_str_array(Arena *a, SLLVariableVector vars) { c8 **result = alloc(a, c8 *, vars.count + 1); @@ -571,7 +582,7 @@ construct_c_str_array(Arena *a, SLLVariableVector vars) return result; } -static b32 +function b32 any_mouse_down(TerminalInput *input) { b32 result = input->keys[MOUSE_LEFT].ended_down || @@ -580,7 +591,7 @@ any_mouse_down(TerminalInput *input) return result; } -static b32 +function b32 all_mouse_up(TerminalInput *input) { b32 result = !input->keys[MOUSE_LEFT].ended_down && @@ -589,7 +600,7 @@ all_mouse_up(TerminalInput *input) return result; } -static void +function void button_action(ButtonState *button, b32 pressed) { if (pressed != button->ended_down) @@ -597,14 +608,14 @@ button_action(ButtonState *button, b32 pressed) button->ended_down = pressed; } -static b32 +function b32 pressed_last_frame(ButtonState *button) { b32 result = (button->transitions > 1) || (button->ended_down && button->transitions == 1); return result; } -static s8 +function s8 utf8_encode(u32 cp) { static u8 buf[4]; diff --git a/util.h b/util.h @@ -138,7 +138,7 @@ typedef Cell *Row; typedef struct { Cell *cells; Row *rows; - MemoryBlock backing_store; + OSMemoryBlock backing_store; } Framebuffer; typedef struct { @@ -148,16 +148,16 @@ typedef struct { } Line; typedef struct { - size cap; - size filled; - size widx; - Line *buf; -} LineBuf; + iz capacity; + iz filled; + iz count; + Line *data; +} LineBuffer; typedef struct { - RingBuf log; - LineBuf lines; - Framebuffer fb; + OSRingBuffer log; + LineBuffer lines; + Framebuffer fb; } TermView; enum terminal_mode { @@ -471,7 +471,7 @@ typedef struct Term { i32 view_idx; i32 scroll_offset; - size unprocessed_bytes; + iz unprocessed_bytes; iptr child; @@ -490,9 +490,9 @@ typedef struct Term { Stream saved_title; Stream error_stream; - PlatformAPI *platform; + OS *os; } Term; -static f32 dt_for_frame; +global f32 dt_for_frame; #endif /* _UTIL_H_ */ diff --git a/vtgl.c b/vtgl.c @@ -1,5 +1,15 @@ /* See LICENSE for copyright details */ + +/* TODO(rnp): + * [ ]: refactor: instead of having a callback for key input it is probably better for + * the platform to pass an in order list of key events last frame + * [ ]: refactor: remove os_... includes from vtgl.h + * [ ]: refactor: stream_ensure_terminator + * [ ]: refactor: push_s8 (into arena); rename push_s8 to draw_s8 + */ + /* TODO: define this ourselves since we should be loading it at runtime */ +/* TODO(rnp): this is only valid on platforms where we can directly link libGL */ #define GL_GLEXT_PROTOTYPES #include <GL/glcorearb.h> @@ -34,7 +44,7 @@ " gl_Position = u_Pmat * vec4(vertex_position, 0.0, 1.0);\n" \ "}\n" -static void +function void set_projection_matrix(GLCtx *gl, u32 stage) { f32 w = gl->window_size.w; @@ -50,7 +60,7 @@ set_projection_matrix(GLCtx *gl, u32 stage) glProgramUniformMatrix4fv(gl->programs[stage], SHADER_PMAT_LOC, 1, GL_TRUE, pmat); } -static u32 +function u32 compile_shader(Arena a, u32 type, s8 shader) { u32 sid = glCreateShader(type); @@ -74,7 +84,7 @@ compile_shader(Arena a, u32 type, s8 shader) return sid; } -static u32 +function u32 program_from_shader_text(s8 vertex, s8 fragment, Arena a) { u32 pid = glCreateProgram(); @@ -110,7 +120,7 @@ typedef struct { u32 stage; } queue_shader_reload_ctx; -static void +function void update_uniforms(GLCtx *gl, enum shader_stages stage) { switch (stage) { @@ -126,10 +136,10 @@ update_uniforms(GLCtx *gl, enum shader_stages stage) } } -static void -reload_shader(GLCtx *gl, PlatformAPI *platform, u8 *path, u32 stage, s8 info, Arena a) +function void +reload_shader(GLCtx *gl, OS *os, u8 *path, u32 stage, s8 info, Arena a) { - s8 fs_text = platform->read_file(path, &a); + s8 fs_text = os->read_file(path, &a); if (fs_text.len) { u32 program = program_from_shader_text(s8(VERTEX_SHADER_TEXT), fs_text, a); if (program) { @@ -142,45 +152,45 @@ reload_shader(GLCtx *gl, PlatformAPI *platform, u8 *path, u32 stage, s8 info, Ar if (info.len) os_write_err_msg(info); } -static s8 fs_name[SHADER_COUNT] = { +global s8 fs_name[SHADER_COUNT] = { [SHADER_RENDER] = s8("frag_render.glsl"), [SHADER_RECTS] = s8("frag_for_rects.glsl"), [SHADER_POST] = s8("frag_post.glsl"), }; -static void -reload_all_shaders(GLCtx *gl, PlatformAPI *platform, Arena a) +function void +reload_all_shaders(GLCtx *gl, OS *os, Arena a) { Stream fs_path = stream_alloc(&a, KB(4)); stream_push_s8(&fs_path, g_shader_path_prefix); - if (fs_path.widx && fs_path.buf[fs_path.widx - 1] != platform->path_separator) - stream_push_byte(&fs_path, platform->path_separator); + if (fs_path.count && fs_path.data[fs_path.count - 1] != os->path_separator) + stream_push_byte(&fs_path, os->path_separator); - i32 sidx = fs_path.widx; + i32 sidx = fs_path.count; for (u32 i = 0; i < SHADER_COUNT; i++) { stream_push_s8(&fs_path, fs_name[i]); stream_push_byte(&fs_path, 0); - reload_shader(gl, platform, fs_path.buf, i, (s8){0}, a); - fs_path.widx = sidx; + reload_shader(gl, os, fs_path.data, i, (s8){0}, a); + stream_reset(&fs_path, sidx); } os_write_err_msg(s8("Reloaded Shaders\n")); } -static PLATFORM_FILE_WATCH_CALLBACK_FN(queue_shader_reload) +function OS_FILE_WATCH_CALLBACK_FN(queue_shader_reload) { queue_shader_reload_ctx *ctx = user_ctx; ctx->path = path; work_queue_insert(ctx->t, WQ_RELOAD_SHADER, ctx); } -static v4 +function v4 normalize_colour(Colour c) { return (v4){.r = c.r / 255.0f, .g = c.g / 255.0f, .b = c.b / 255.0f, .a = c.a / 255.0f}; } -static void +function void clear_colour(void) { Colour c = g_colours.data[g_colours.bgidx]; @@ -189,14 +199,14 @@ clear_colour(void) glClear(GL_COLOR_BUFFER_BIT); } -static v2 +function v2 get_cell_size(FontAtlas *fa) { v2 result = {.w = fa->info.w, .h = fa->info.h}; return result; } -static v2 +function v2 get_occupied_size(Term *t) { v2 cs = get_cell_size(&t->fa); @@ -204,7 +214,7 @@ get_occupied_size(Term *t) return result; } -static v2 +function v2 get_terminal_top_left(Term *t) { v2 os = get_occupied_size(t); @@ -213,8 +223,8 @@ get_terminal_top_left(Term *t) return result; } -static void -resize_terminal(Term *t, PlatformAPI *platform, iv2 window_size) +function void +resize_terminal(Term *t, OS *os, iv2 window_size) { v2 ws = v2_from_iv2(window_size); ws.w -= 2 * g_term_margin.w; @@ -231,7 +241,7 @@ resize_terminal(Term *t, PlatformAPI *platform, iv2 window_size) stream_push_u64(&t->error_stream, t->size.w); stream_push_s8(&t->error_stream, s8("; clamping\n")); os_write_err_msg(stream_to_s8(&t->error_stream)); - t->error_stream.widx = 0; + stream_reset(&t->error_stream, 0); } if (!equal_iv2(old_size, t->size)) { @@ -240,14 +250,14 @@ resize_terminal(Term *t, PlatformAPI *platform, iv2 window_size) t->gl.flags |= NEEDS_REFILL; } - platform->set_terminal_size(t->child, t->size.h, t->size.w, ws.w, ws.h); + os->set_terminal_size(t->child, t->size.h, t->size.w, ws.w, ws.h); t->gl.flags |= RESIZE_RENDERER; t->gl.flags &= ~NEEDS_RESIZE; } -static void -resize(Term *t, PlatformAPI *platform, iv2 window_size) +function void +resize(Term *t, OS *os, iv2 window_size) { GLCtx *gl = &t->gl; gl->window_size = window_size; @@ -292,7 +302,7 @@ resize(Term *t, PlatformAPI *platform, iv2 window_size) gl->flags &= ~RESIZE_RENDERER; } -static RenderCtx +function RenderCtx make_render_ctx(Arena *a, GLCtx *gl, FontAtlas *fa) { RenderCtx result; @@ -303,7 +313,7 @@ make_render_ctx(Arena *a, GLCtx *gl, FontAtlas *fa) return result; } -static iv2 +function iv2 get_gpu_texture_position(v2 cs, u32 gpu_tile_index) { uv2 gpu_position = {.x = gpu_tile_index & 0xFFFF, .y = gpu_tile_index >> 16}; @@ -311,7 +321,7 @@ get_gpu_texture_position(v2 cs, u32 gpu_tile_index) return result; } -static u32 +function u32 get_gpu_glyph_index(Arena a, GLCtx *gl, FontAtlas *fa, u32 codepoint, u32 font_id, enum face_style style, CachedGlyph **out) { @@ -334,7 +344,7 @@ get_gpu_glyph_index(Arena a, GLCtx *gl, FontAtlas *fa, u32 codepoint, u32 font_i } /* NOTE: this function assumes we are drawing quads */ -static void +function void flush_render_push_buffer(RenderCtx *rc) { BEGIN_TIMED_BLOCK(); @@ -359,7 +369,7 @@ flush_render_push_buffer(RenderCtx *rc) END_TIMED_BLOCK(); } -static u32 +function u32 get_render_push_buffer_idx(RenderCtx *rc, u32 count) { if (rc->rpb->count + count > RENDER_PUSH_BUFFER_CAP) @@ -369,7 +379,7 @@ get_render_push_buffer_idx(RenderCtx *rc, u32 count) return result; } -static void +function void push_rect_full(RenderCtx *rc, Rect r, v4 colour, v2 min_tex_coord, v2 max_tex_coord) { BEGIN_TIMED_BLOCK(); @@ -397,7 +407,7 @@ push_rect_full(RenderCtx *rc, Rect r, v4 colour, v2 min_tex_coord, v2 max_tex_co END_TIMED_BLOCK(); } -static void +function void push_rect_textured(RenderCtx *rc, Rect r, v4 colour, b32 flip_texture) { v2 min_tex_coord, max_tex_coord; @@ -411,7 +421,7 @@ push_rect_textured(RenderCtx *rc, Rect r, v4 colour, b32 flip_texture) push_rect_full(rc, r, colour, min_tex_coord, max_tex_coord); } -static void +function void push_rect(RenderCtx *rc, Rect r, v4 colour) { f32 max_x = 1.0f / rc->gl->glyph_bitmap_dim.x; @@ -419,7 +429,7 @@ push_rect(RenderCtx *rc, Rect r, v4 colour) push_rect_full(rc, r, colour, (v2){0}, (v2){.x = max_x, .y = max_y}); } -static v2 +function v2 push_s8(RenderCtx *rc, v2 pos, v4 colour, u32 font_id, s8 s) { BEGIN_TIMED_BLOCK(); @@ -458,7 +468,7 @@ push_s8(RenderCtx *rc, v2 pos, v4 colour, u32 font_id, s8 s) return text_size; } -static v2 +function v2 measure_text(RenderCtx *rc, u32 font_id, s8 text) { BEGIN_TIMED_BLOCK(); @@ -489,7 +499,7 @@ measure_text(RenderCtx *rc, u32 font_id, s8 text) * to the screen as a full window quad. Therefore render_framebuffer must take care * to handle all necessary padding (window and cell). Outside of here everyone should * simply care about the terminal in terms of rows and columns (t->size). */ -static void +function void render_framebuffer(Term *t, RenderCell *render_buf, TerminalInput *input, Arena arena) { BEGIN_TIMED_BLOCK(); @@ -530,7 +540,7 @@ render_framebuffer(Term *t, RenderCell *render_buf, TerminalInput *input, Arena END_TIMED_BLOCK(); } -static void +function void render_cursor(Term *t, b32 focused, Arena a) { BEGIN_TIMED_BLOCK(); @@ -539,9 +549,9 @@ render_cursor(Term *t, b32 focused, Arena a) Cell *c = &t->views[t->view_idx].fb.rows[curs.y][curs.x]; RenderCell *rc = alloc(&a, RenderCell, 3); - size rc_off = 1; - size length = sizeof(RenderCell); - size offset = sizeof(RenderCell) * (curs.y * t->size.w + curs.x); + iz rc_off = 1; + iz length = sizeof(RenderCell); + iz offset = sizeof(RenderCell) * (curs.y * t->size.w + curs.x); CachedGlyph *cg; rc[1].gpu_glyph = get_gpu_glyph_index(a, &t->gl, &t->fa, c->cp, 0, c->bg & FS_MASK, &cg); @@ -574,7 +584,7 @@ render_cursor(Term *t, b32 focused, Arena a) END_TIMED_BLOCK(); } -static iv2 +function iv2 mouse_to_cell_space(Term *t, v2 mouse) { v2 cell_size = get_cell_size(&t->fa); @@ -590,7 +600,7 @@ mouse_to_cell_space(Term *t, v2 mouse) return result; } -static void +function void stream_push_selection(Stream *s, Row *rows, Range sel, u32 term_width) { s->errors |= !is_valid_range(sel); @@ -604,18 +614,18 @@ stream_push_selection(Stream *s, Row *rows, Range sel, u32 term_width) stream_push_s8(s, utf8_encode(c->cp)); if (!ISSPACE(c->cp)) - last_non_space_idx = s->widx; + last_non_space_idx = s->count; if (si.next.y != si.cursor.y) { - s->widx = last_non_space_idx; + stream_reset(s, last_non_space_idx); stream_push_byte(s, '\n'); } } - s->widx = last_non_space_idx; + stream_reset(s, last_non_space_idx); } -static void +function void begin_selection(Term *t, u32 click_count, v2 mouse) { Selection *sel = &t->selection; @@ -633,7 +643,7 @@ begin_selection(Term *t, u32 click_count, v2 mouse) t->gl.queued_render = 1; } -static void +function void update_selection(Term *t, TerminalInput *input) { if (!input->keys[MOUSE_LEFT].ended_down) @@ -676,21 +686,22 @@ KEYBIND_FN(copy) { Stream buf = arena_stream(t->arena_for_frame); stream_push_selection(&buf, t->views[t->view_idx].fb.rows, t->selection.range, t->size.w); - platform->set_clipboard(&buf, a.i); + stream_push_byte(&buf, 0); + os->set_clipboard(buf.data, buf.count - 1, a.i); return 1; } KEYBIND_FN(paste) { - Stream buf = arena_stream(t->arena_for_frame); b32 bracketed = t->mode.win & WM_BRACKPASTE; - if (bracketed) stream_push_s8(&buf, s8("\033[200~")); - b32 success = platform->get_clipboard(&buf, a.i); - if (bracketed) stream_push_s8(&buf, s8("\033[201~")); - - if (success) - platform->write(t->child, stream_to_s8(&buf)); + /* TODO(rnp): we may need to replace '\n' with '\r\n' */ + s8 text = c_str_to_s8((char *)os->get_clipboard(a.i)); + if (text.len) { + if (bracketed) os->write(t->child, s8("\033[200~")); + os->write(t->child, text); + if (bracketed) os->write(t->child, s8("\033[201~")); + } return 1; } @@ -722,7 +733,7 @@ KEYBIND_FN(zoom) return 1; } -static void +function void report_mouse(Term *t, TerminalInput *input, b32 released, b32 beginning) { if ((t->mode.win & WM_MOUSE_X10) && released) @@ -785,10 +796,10 @@ report_mouse(Term *t, TerminalInput *input, b32 released, b32 beginning) return; } - t->platform->write(t->child, stream_to_s8(&buf)); + t->os->write(t->child, stream_to_s8(&buf)); } -static void +function void begin_terminal_interaction(Term *t, TerminalInput *input, u32 click_count) { if (t->mode.win & WM_MOUSE_MASK) { @@ -799,8 +810,8 @@ begin_terminal_interaction(Term *t, TerminalInput *input, u32 click_count) } } -static b32 -terminal_interaction(Term *t, PlatformAPI *platform, TerminalInput *input, u32 click_count) +function b32 +terminal_interaction(Term *t, OS *os, TerminalInput *input, u32 click_count) { b32 should_end_interaction = all_mouse_up(input); @@ -810,24 +821,24 @@ terminal_interaction(Term *t, PlatformAPI *platform, TerminalInput *input, u32 c } else { update_selection(t, input); if (pressed_last_frame(input->keys + MOUSE_MIDDLE)) - paste(t, platform, (Arg){.i = CLIPBOARD_1}); + paste(t, os, (Arg){.i = OS_CLIPBOARD_SECONDARY}); b32 shift_down = input->modifiers & MOD_SHIFT; if (input->mouse_scroll.y) { if (t->mode.term & TM_ALTSCREEN) { iptr child = t->child; if (input->mouse_scroll.y > 0) { - if (shift_down) platform->write(child, s8("\x1B[5;2~")); - else platform->write(child, s8("\x19")); + if (shift_down) os->write(child, s8("\x1B[5;2~")); + else os->write(child, s8("\x19")); } else { - if (shift_down) platform->write(child, s8("\x1B[6;2~")); - else platform->write(child, s8("\x05")); + if (shift_down) os->write(child, s8("\x1B[6;2~")); + else os->write(child, s8("\x05")); } } else { Arg a = {.i = (i32)input->mouse_scroll.y}; if (shift_down) a.i *= 5; - scroll(t, platform, a); + scroll(t, os, a); } } } @@ -837,22 +848,22 @@ terminal_interaction(Term *t, PlatformAPI *platform, TerminalInput *input, u32 c DEBUG_EXPORT VTGL_HANDLE_KEYS_FN(vtgl_handle_keys) { - Term *t = memory->memory; - PlatformAPI *platform = &memory->platform_api; - iptr child = t->child; + Term *t = memory->memory; + OS *os = &memory->os; + iptr child = t->child; #ifdef _DEBUG - if (key == KEY_F1 && action == ACT_PRESS) { + if (key == KEY_F1 && action == BUTTON_PRESS) { dump_lines_to_file(t); return; } - if (key == KEY_F11 && action == ACT_PRESS) { + if (key == KEY_F11 && action == BUTTON_PRESS) { /* TODO: probably move this into the debug frame start */ DebugState *ds = memory->debug_memory; ds->paused = !ds->paused; return; } - if (key == KEY_F12 && action == ACT_PRESS) { + if (key == KEY_F12 && action == BUTTON_PRESS) { t->gl.flags ^= DRAW_DEBUG_OVERLAY; input->window_refreshed = 1; return; @@ -864,88 +875,80 @@ DEBUG_EXPORT VTGL_HANDLE_KEYS_FN(vtgl_handle_keys) for (u32 i = 0; i < ARRAY_COUNT(g_hotkeys); i++) { struct hotkey *hk = g_hotkeys + i; if (hk->key == enc) { - b32 handled = hk->fn(t, &memory->platform_api, hk->arg); + b32 handled = hk->fn(t, &memory->os, hk->arg); if (handled) return; } } /* NOTE: send control sequences */ - if (modifiers & MOD_CONTROL && action != ACT_RELEASE) { + if (modifiers & MOD_CONTROL && action != BUTTON_RELEASE) { /* TODO: this is wrong. look up where 8-bit modifiers should be sent */ if (0 && t->mode.win & WM_8BIT) { if (key < 0x7F) { - platform->write(child, utf8_encode(key | 0x80)); + os->write(child, utf8_encode(key | 0x80)); return; } } else if (BETWEEN(key, 0x40, 0x5F)) { - platform->write(child, utf8_encode(key - 0x40)); + os->write(child, utf8_encode(key - 0x40)); return; } } /* TODO: construct a hash table of bound keys */ switch (ENCODE_KEY(action, 0, key)) { - case ENCODE_KEY(ACT_PRESS, 0, KEY_ESCAPE): - case ENCODE_KEY(ACT_REPEAT, 0, KEY_ESCAPE): - platform->write(child, s8("\x1B")); - break; - case ENCODE_KEY(ACT_PRESS, 0, KEY_TAB): - case ENCODE_KEY(ACT_REPEAT, 0, KEY_TAB): - platform->write(child, s8("\t")); - break; - case ENCODE_KEY(ACT_PRESS, 0, KEY_ENTER): - case ENCODE_KEY(ACT_REPEAT, 0, KEY_ENTER): - platform->write(child, s8("\r")); - break; - case ENCODE_KEY(ACT_PRESS, 0, KEY_BACKSPACE): - case ENCODE_KEY(ACT_REPEAT, 0, KEY_BACKSPACE): - platform->write(child, s8("\x7F")); - break; - case ENCODE_KEY(ACT_PRESS, 0, KEY_UP): - case ENCODE_KEY(ACT_REPEAT, 0, KEY_UP): - if (t->mode.win & WM_APPCURSOR) - platform->write(child, s8("\x1BOA")); - else - platform->write(child, s8("\x1B[A")); - break; - case ENCODE_KEY(ACT_PRESS, 0, KEY_DOWN): - case ENCODE_KEY(ACT_REPEAT, 0, KEY_DOWN): - if (t->mode.win & WM_APPCURSOR) - platform->write(child, s8("\x1BOB")); - else - platform->write(child, s8("\x1B[B")); - break; - case ENCODE_KEY(ACT_PRESS, 0, KEY_RIGHT): - case ENCODE_KEY(ACT_REPEAT, 0, KEY_RIGHT): - if (t->mode.win & WM_APPCURSOR) - platform->write(child, s8("\x1BOC")); - else - platform->write(child, s8("\x1B[C")); - break; - case ENCODE_KEY(ACT_PRESS, 0, KEY_LEFT): - case ENCODE_KEY(ACT_REPEAT, 0, KEY_LEFT): - if (t->mode.win & WM_APPCURSOR) - platform->write(child, s8("\x1BOD")); - else - platform->write(child, s8("\x1B[D")); - break; - case ENCODE_KEY(ACT_PRESS, 0, KEY_PAGE_UP): - case ENCODE_KEY(ACT_REPEAT, 0, KEY_PAGE_UP): - if (modifiers & MOD_CONTROL) platform->write(child, s8("\x1B[5;5~")); - else if (modifiers & MOD_SHIFT) platform->write(child, s8("\x1B[5;2~")); - else platform->write(child, s8("\x1B[5~")); - break; - case ENCODE_KEY(ACT_PRESS, 0, KEY_PAGE_DOWN): - case ENCODE_KEY(ACT_REPEAT, 0, KEY_PAGE_DOWN): - if (modifiers & MOD_CONTROL) platform->write(child, s8("\x1B[6;5~")); - else if (modifiers & MOD_SHIFT) platform->write(child, s8("\x1B[6;2~")); - else platform->write(child, s8("\x1B[6~")); - break; + case ENCODE_KEY(BUTTON_PRESS, 0, KEY_ESCAPE): + case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_ESCAPE): { + os->write(child, s8("\x1B")); + } break; + case ENCODE_KEY(BUTTON_PRESS, 0, KEY_TAB): + case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_TAB): { + os->write(child, s8("\t")); + } break; + case ENCODE_KEY(BUTTON_PRESS, 0, KEY_ENTER): + case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_ENTER): { + os->write(child, s8("\r")); + } break; + case ENCODE_KEY(BUTTON_PRESS, 0, KEY_BACKSPACE): + case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_BACKSPACE): { + os->write(child, s8("\x7F")); + } break; + case ENCODE_KEY(BUTTON_PRESS, 0, KEY_UP): + case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_UP): { + if (t->mode.win & WM_APPCURSOR) os->write(child, s8("\x1BOA")); + else os->write(child, s8("\x1B[A")); + } break; + case ENCODE_KEY(BUTTON_PRESS, 0, KEY_DOWN): + case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_DOWN): { + if (t->mode.win & WM_APPCURSOR) os->write(child, s8("\x1BOB")); + else os->write(child, s8("\x1B[B")); + } break; + case ENCODE_KEY(BUTTON_PRESS, 0, KEY_RIGHT): + case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_RIGHT): { + if (t->mode.win & WM_APPCURSOR) os->write(child, s8("\x1BOC")); + else os->write(child, s8("\x1B[C")); + } break; + case ENCODE_KEY(BUTTON_PRESS, 0, KEY_LEFT): + case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_LEFT): { + if (t->mode.win & WM_APPCURSOR) os->write(child, s8("\x1BOD")); + else os->write(child, s8("\x1B[D")); + } break; + case ENCODE_KEY(BUTTON_PRESS, 0, KEY_PAGE_UP): + case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_PAGE_UP): { + if (modifiers & MOD_CONTROL) os->write(child, s8("\x1B[5;5~")); + else if (modifiers & MOD_SHIFT) os->write(child, s8("\x1B[5;2~")); + else os->write(child, s8("\x1B[5~")); + } break; + case ENCODE_KEY(BUTTON_PRESS, 0, KEY_PAGE_DOWN): + case ENCODE_KEY(BUTTON_REPEAT, 0, KEY_PAGE_DOWN): { + if (modifiers & MOD_CONTROL) os->write(child, s8("\x1B[6;5~")); + else if (modifiers & MOD_SHIFT) os->write(child, s8("\x1B[6;2~")); + else os->write(child, s8("\x1B[6~")); + } break; } } -static b32 +function b32 should_start_interaction(TerminalInput *input) { b32 result = input->mouse_scroll.y || input->mouse_scroll.x || @@ -955,7 +958,7 @@ should_start_interaction(TerminalInput *input) return result; } -static void +function void begin_interaction(Term *t, InteractionState *is, TerminalInput *input) { is->click_count++; @@ -983,14 +986,14 @@ begin_interaction(Term *t, InteractionState *is, TerminalInput *input) } } -static void +function void end_interaction(InteractionState *is, TerminalInput *input) { is->active = (Interaction){.type = IS_NONE}; } -static void -handle_interactions(Term *t, TerminalInput *input, PlatformAPI *platform) +function void +handle_interactions(Term *t, TerminalInput *input, OS *os) { InteractionState *is = &t->interaction; @@ -1016,13 +1019,13 @@ handle_interactions(Term *t, TerminalInput *input, PlatformAPI *platform) case IS_DRAG: /* TODO */ break; case IS_DEBUG: /* TODO */ break; case IS_TERM: { - if (terminal_interaction(t, platform, input, is->click_count)) + if (terminal_interaction(t, os, input, is->click_count)) end_interaction(is, input); } break; } } -static void +function void gl_debug_logger(u32 src, u32 type, u32 id, u32 lvl, i32 len, const char *msg, const void *data) { (void)src; (void)type; (void)id; @@ -1038,10 +1041,10 @@ gl_debug_logger(u32 src, u32 type, u32 id, u32 lvl, i32 len, const char *msg, co stream_push_s8(err, (s8){.len = len, .data = (u8 *)msg}); stream_push_byte(err, '\n'); os_write_err_msg(stream_to_s8(err)); - err->widx = 0; + stream_reset(err, 0); } -static u32 +function u32 gen_2D_texture(iv2 size, u32 format, u32 filter, u32 *rgba) { /* TODO: logging */ @@ -1059,15 +1062,15 @@ DEBUG_EXPORT VTGL_INITIALIZE_FN(vtgl_initialize) Term *t = (Term *)memory->memory; Arena a = {.beg = (u8 *)(t + 1), .end = memory->memory + memory->memory_size}; - t->platform = &memory->platform_api; + t->os = &memory->os; t->cursor.state = CURSOR_NORMAL; cursor_reset(t); - memory->platform_api.allocate_ring_buffer(&t->views[0].log, BACKLOG_SIZE); - line_buf_alloc(&t->views[0].lines, &a, t->views[0].log.buf, t->cursor.style, BACKLOG_LINES); - memory->platform_api.allocate_ring_buffer(&t->views[1].log, ALT_BACKLOG_SIZE); - line_buf_alloc(&t->views[1].lines, &a, t->views[1].log.buf, t->cursor.style, ALT_BACKLOG_LINES); + t->views[0].log = memory->os.allocate_ring_buffer(BACKLOG_SIZE); + t->views[0].lines = line_buffer_alloc(&a, t->views[0].log.data, t->cursor.style, BACKLOG_LINES); + t->views[1].log = memory->os.allocate_ring_buffer(ALT_BACKLOG_SIZE); + t->views[1].lines = line_buffer_alloc(&a, t->views[1].log.data, t->cursor.style, ALT_BACKLOG_LINES); t->views[0].fb.backing_store = memory_block_from_arena(&a, MB(2)); t->views[1].fb.backing_store = memory_block_from_arena(&a, MB(2)); @@ -1101,8 +1104,8 @@ DEBUG_EXPORT VTGL_INITIALIZE_FN(vtgl_initialize) for (u32 i = 0; i < SHADER_COUNT; i++) { Stream path = arena_stream(a); stream_push_s8(&path, g_shader_path_prefix); - if (path.widx && path.buf[path.widx - 1] != memory->platform_api.path_separator) - stream_push_byte(&path, memory->platform_api.path_separator); + if (path.count && path.data[path.count - 1] != memory->os.path_separator) + stream_push_byte(&path, memory->os.path_separator); queue_shader_reload_ctx *src = reload_ctxs + i; src->info = shader_infos[i]; @@ -1110,8 +1113,8 @@ DEBUG_EXPORT VTGL_INITIALIZE_FN(vtgl_initialize) src->t = t; stream_push_s8(&path, fs_name[i]); stream_push_byte(&path, 0); - memory->platform_api.add_file_watch(path.buf, queue_shader_reload, src); - a.beg = path.buf + path.widx; + memory->os.add_file_watch(path.data, queue_shader_reload, src); + a.beg = path.data + path.count; } t->error_stream = stream_alloc(&a, KB(256)); @@ -1198,7 +1201,7 @@ DEBUG_EXPORT VTGL_INITIALIZE_FN(vtgl_initialize) glActiveTexture(GL_TEXTURE0); - reload_all_shaders(&t->gl, &memory->platform_api, a); + reload_all_shaders(&t->gl, &memory->os, a); return requested_size; } @@ -1228,11 +1231,10 @@ DEBUG_EXPORT VTGL_RENDER_FRAME_FN(vtgl_render_frame) switch (entry->type) { case WQ_RELOAD_SHADER: { queue_shader_reload_ctx *ctx = entry->ctx; - reload_shader(&t->gl, &memory->platform_api, ctx->path, ctx->stage, - ctx->info, arena); + reload_shader(&t->gl, &memory->os, ctx->path, ctx->stage, ctx->info, arena); } break; case WQ_RELOAD_ALL_SHADERS: { - reload_all_shaders(&t->gl, &memory->platform_api, arena); + reload_all_shaders(&t->gl, &memory->os, arena); } break; default: INVALID_CODE_PATH; } @@ -1259,7 +1261,7 @@ DEBUG_EXPORT VTGL_RENDER_FRAME_FN(vtgl_render_frame) while (atomic_exchange_n(&t->resize_lock, 1) != 0); if (t->gl.flags & RESIZE_RENDERER) - resize(t, &memory->platform_api, input->window_size); + resize(t, &memory->os, input->window_size); if (t->gl.queued_render) { t->gl.queued_render = 0; @@ -1331,7 +1333,7 @@ DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step) /* NOTE(rnp): we skip the resize this time through so that we don't add * input latency waiting for the render thread to release this lock */ if (atomic_exchange_n(&t->resize_lock, 1) == 0) { - resize_terminal(t, &memory->platform_api, input->window_size); + resize_terminal(t, &memory->os, input->window_size); t->resize_lock = 0; } else { input->pending_updates = 1; @@ -1348,10 +1350,10 @@ DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step) t->scroll_offset = 0; t->gl.flags |= NEEDS_REFILL; } - memory->platform_api.write(t->child, input->character_input); + memory->os.write(t->child, input->character_input); } - handle_interactions(t, input, &memory->platform_api); + handle_interactions(t, input, &memory->os); END_NAMED_BLOCK(mouse_and_keyboard_input); if (t->gl.flags & NEEDS_REFILL) { @@ -1361,17 +1363,17 @@ DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step) BEGIN_NAMED_BLOCK(input_from_child); if (input->data_available) { - RingBuf *rb = &t->views[t->view_idx].log; - s8 buffer = {.len = rb->cap - t->unprocessed_bytes, .data = rb->buf + rb->widx}; + OSRingBuffer *rb = &t->views[t->view_idx].log; + s8 buffer = {.len = rb->capacity - t->unprocessed_bytes, .data = rb->data + rb->write_index}; - size bytes_read = memory->platform_api.read(t->child, buffer); - ASSERT(bytes_read <= rb->cap); + iz bytes_read = memory->os.read(t->child, buffer); + ASSERT(bytes_read <= rb->capacity); commit_to_rb(t->views + t->view_idx, bytes_read); t->unprocessed_bytes += bytes_read; s8 raw = { .len = t->unprocessed_bytes, - .data = rb->buf + (rb->widx - t->unprocessed_bytes) + .data = rb->data + (rb->write_index - t->unprocessed_bytes) }; handle_input(t, t->arena_for_frame, raw); t->gl.queued_render = 1; diff --git a/vtgl.h b/vtgl.h @@ -17,17 +17,16 @@ #define static_assert _Static_assert #endif -#define atomic_and(ptr, n) __atomic_and_fetch(ptr, n, __ATOMIC_RELEASE); -#define atomic_fetch_add(ptr, n) __atomic_fetch_add(ptr, n, __ATOMIC_RELEASE); -#define atomic_load(ptr) __atomic_load_n(ptr, __ATOMIC_ACQUIRE) -#define atomic_exchange_n(ptr, val) __atomic_exchange_n(ptr, val, __ATOMIC_SEQ_CST) +#define function static +#define global static +#define local_persist static #define PI 3.1415926535897932384f #define KB(a) ((a) << 10ULL) #define MB(a) ((a) << 20ULL) -#define ARRAY_COUNT(a) (size)(sizeof(a) / sizeof(*a)) +#define ARRAY_COUNT(a) (iz)(sizeof(a) / sizeof(*a)) #define ABS(a) ((a) < 0 ? (-a) : (a)) #define BETWEEN(x, a, b) ((x) >= (a) && (x) <= (b)) #define CLAMP(x, a, b) ((x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)) @@ -70,8 +69,8 @@ typedef uint32_t b32; typedef int64_t i64; typedef uint64_t u64; typedef ptrdiff_t iptr; -typedef ptrdiff_t size; -typedef size_t usize; +typedef ptrdiff_t iz; +typedef size_t uz; #include "intrinsics.c" @@ -83,31 +82,21 @@ typedef size_t usize; #define DEBUG_EXPORT static #endif -typedef struct { void *memory; size size; } MemoryBlock; - typedef struct { u8 *beg, *end; } Arena; typedef struct { Arena *arena; u8 *old_beg; } TempArena; -typedef struct { size len; u8 *data; } s8; +typedef struct { iz len; u8 *data; } s8; #define s8(s) (s8){.len = ARRAY_COUNT(s) - 1, .data = (u8 *)s} typedef s8 os_mapped_file; typedef struct { - u8 *buf; - u32 cap; - u32 widx; - b32 errors; + u8 *data; + iz capacity; + iz count; + b32 errors; } Stream; -/* NOTE: virtual memory ring buffer */ -typedef struct { - size cap; - size filled; - size widx; - u8 *buf; -} RingBuf; - typedef union { struct { i32 x, y; }; struct { i32 w, h; }; @@ -123,186 +112,133 @@ typedef union { typedef struct { iv2 start, end; } Range; #define INVALID_RANGE_END (iv2){.x = -1, .y = -1} -#define INVALID_FILE (-1) -enum file_attribute { - FA_READ = 1 << 0, - FA_WRITE = 1 << 1, - FA_APPEND = 1 << 2, -}; - -/* 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); - -#define PLATFORM_ALLOCATE_RING_BUFFER_FN(name) void name(RingBuf *rb, size capacity) -typedef PLATFORM_ALLOCATE_RING_BUFFER_FN(platform_allocate_ring_buffer_fn); - -#define PLATFORM_CLIPBOARD_FN(name) b32 name(Stream *buffer, u32 clipboard) -typedef PLATFORM_CLIPBOARD_FN(platform_clipboard_fn); - -#define PLATFORM_READ_FILE_FN(name) s8 name(u8 *path, Arena *a) -typedef PLATFORM_READ_FILE_FN(platform_read_file_fn); - -/* TODO: this should possibly just take a stream buffer */ -#define PLATFORM_READ_FN(name) size name(iptr file, s8 buffer) -typedef PLATFORM_READ_FN(platform_read_fn); - -#define PLATFORM_SET_TERMINAL_SIZE_FN(name) void name(iptr child, i32 rows, i32 columns, \ - i32 window_width, i32 window_height) -typedef PLATFORM_SET_TERMINAL_SIZE_FN(platform_set_terminal_size_fn); - -#define PLATFORM_WINDOW_TITLE_FN(name) void name(Stream *buffer) -typedef PLATFORM_WINDOW_TITLE_FN(platform_window_title_fn); - -#define PLATFORM_WRITE_FN(name) b32 name(iptr file, s8 raw) -typedef PLATFORM_WRITE_FN(platform_write_fn); - -typedef struct { - platform_add_file_watch_fn *add_file_watch; - platform_allocate_ring_buffer_fn *allocate_ring_buffer; - platform_clipboard_fn *get_clipboard; - platform_clipboard_fn *set_clipboard; - platform_read_file_fn *read_file; - platform_read_fn *read; - platform_set_terminal_size_fn *set_terminal_size; - platform_window_title_fn *get_window_title; - platform_window_title_fn *set_window_title; - platform_write_fn *write; - - u8 path_separator; -} PlatformAPI; - -/* NOTE: CLIPBOARD_1 need not be supported on all platforms */ -enum { - CLIPBOARD_0, - CLIPBOARD_1, -}; +#include "os.h" /* TODO: for now these are just based on the GLFW keycodes directly. It might * be better for the platform to define these values themselves */ -#define ACT_RELEASE 0 -#define ACT_PRESS 1 -#define ACT_REPEAT 2 - -#define KEY_SPACE 32 -#define KEY_APOSTROPHE 39 /* ' */ -#define KEY_COMMA 44 /* , */ -#define KEY_MINUS 45 /* - */ -#define KEY_PERIOD 46 /* . */ -#define KEY_SLASH 47 /* / */ -#define KEY_0 48 -#define KEY_1 49 -#define KEY_2 50 -#define KEY_3 51 -#define KEY_4 52 -#define KEY_5 53 -#define KEY_6 54 -#define KEY_7 55 -#define KEY_8 56 -#define KEY_9 57 -#define KEY_SEMICOLON 59 /* ; */ -#define KEY_EQUAL 61 /* = */ -#define KEY_A 65 -#define KEY_B 66 -#define KEY_C 67 -#define KEY_D 68 -#define KEY_E 69 -#define KEY_F 70 -#define KEY_G 71 -#define KEY_H 72 -#define KEY_I 73 -#define KEY_J 74 -#define KEY_K 75 -#define KEY_L 76 -#define KEY_M 77 -#define KEY_N 78 -#define KEY_O 79 -#define KEY_P 80 -#define KEY_Q 81 -#define KEY_R 82 -#define KEY_S 83 -#define KEY_T 84 -#define KEY_U 85 -#define KEY_V 86 -#define KEY_W 87 -#define KEY_X 88 -#define KEY_Y 89 -#define KEY_Z 90 -#define KEY_LEFT_BRACKET 91 /* [ */ -#define KEY_BACKSLASH 92 /* \ */ -#define KEY_RIGHT_BRACKET 93 /* ] */ -#define KEY_GRAVE_ACCENT 96 /* ` */ -#define KEY_WORLD_1 161 /* non-US #1 */ -#define KEY_WORLD_2 162 /* non-US #2 */ - -/* Function keys */ -#define KEY_ESCAPE 256 -#define KEY_ENTER 257 -#define KEY_TAB 258 -#define KEY_BACKSPACE 259 -#define KEY_INSERT 260 -#define KEY_DELETE 261 -#define KEY_RIGHT 262 -#define KEY_LEFT 263 -#define KEY_DOWN 264 -#define KEY_UP 265 -#define KEY_PAGE_UP 266 -#define KEY_PAGE_DOWN 267 -#define KEY_HOME 268 -#define KEY_END 269 -#define KEY_CAPS_LOCK 280 -#define KEY_SCROLL_LOCK 281 -#define KEY_NUM_LOCK 282 -#define KEY_PRINT_SCREEN 283 -#define KEY_PAUSE 284 -#define KEY_F1 290 -#define KEY_F2 291 -#define KEY_F3 292 -#define KEY_F4 293 -#define KEY_F5 294 -#define KEY_F6 295 -#define KEY_F7 296 -#define KEY_F8 297 -#define KEY_F9 298 -#define KEY_F10 299 -#define KEY_F11 300 -#define KEY_F12 301 -#define KEY_F13 302 -#define KEY_F14 303 -#define KEY_F15 304 -#define KEY_F16 305 -#define KEY_F17 306 -#define KEY_F18 307 -#define KEY_F19 308 -#define KEY_F20 309 -#define KEY_F21 310 -#define KEY_F22 311 -#define KEY_F23 312 -#define KEY_F24 313 -#define KEY_F25 314 -#define KEY_KP_0 320 -#define KEY_KP_1 321 -#define KEY_KP_2 322 -#define KEY_KP_3 323 -#define KEY_KP_4 324 -#define KEY_KP_5 325 -#define KEY_KP_6 326 -#define KEY_KP_7 327 -#define KEY_KP_8 328 -#define KEY_KP_9 329 -#define KEY_KP_DECIMAL 330 -#define KEY_KP_DIVIDE 331 -#define KEY_KP_MULTIPLY 332 -#define KEY_KP_SUBTRACT 333 -#define KEY_KP_ADD 334 -#define KEY_KP_ENTER 335 -#define KEY_KP_EQUAL 336 - -enum input_keys { +typedef enum { + BUTTON_RELEASE, + BUTTON_PRESS, + BUTTON_REPEAT, +} ButtonAction; + +typedef enum { + KEY_SPACE = 32, + KEY_APOSTROPHE = 39, /* ' */ + KEY_COMMA = 44, /* , */ + KEY_MINUS = 45, /* - */ + KEY_PERIOD = 46, /* . */ + KEY_SLASH = 47, /* / */ + KEY_0 = 48, + KEY_1 = 49, + KEY_2 = 50, + KEY_3 = 51, + KEY_4 = 52, + KEY_5 = 53, + KEY_6 = 54, + KEY_7 = 55, + KEY_8 = 56, + KEY_9 = 57, + KEY_SEMICOLON = 59, /* ; */ + KEY_EQUAL = 61, /* = */ + KEY_A = 65, + KEY_B = 66, + KEY_C = 67, + KEY_D = 68, + KEY_E = 69, + KEY_F = 70, + KEY_G = 71, + KEY_H = 72, + KEY_I = 73, + KEY_J = 74, + KEY_K = 75, + KEY_L = 76, + KEY_M = 77, + KEY_N = 78, + KEY_O = 79, + KEY_P = 80, + KEY_Q = 81, + KEY_R = 82, + KEY_S = 83, + KEY_T = 84, + KEY_U = 85, + KEY_V = 86, + KEY_W = 87, + KEY_X = 88, + KEY_Y = 89, + KEY_Z = 90, + KEY_LEFT_BRACKET = 91, /* [ */ + KEY_BACKSLASH = 92, /* \ */ + KEY_RIGHT_BRACKET = 93, /* ] */ + KEY_GRAVE_ACCENT = 96, /* ` */ + KEY_WORLD_1 = 161, /* non-US #1 */ + KEY_WORLD_2 = 162, /* non-US #2 */ + + /* Function keys */ + KEY_ESCAPE = 256, + KEY_ENTER = 257, + KEY_TAB = 258, + KEY_BACKSPACE = 259, + KEY_INSERT = 260, + KEY_DELETE = 261, + KEY_RIGHT = 262, + KEY_LEFT = 263, + KEY_DOWN = 264, + KEY_UP = 265, + KEY_PAGE_UP = 266, + KEY_PAGE_DOWN = 267, + KEY_HOME = 268, + KEY_END = 269, + KEY_CAPS_LOCK = 280, + KEY_SCROLL_LOCK = 281, + KEY_NUM_LOCK = 282, + KEY_PRINT_SCREEN = 283, + KEY_PAUSE = 284, + KEY_F1 = 290, + KEY_F2 = 291, + KEY_F3 = 292, + KEY_F4 = 293, + KEY_F5 = 294, + KEY_F6 = 295, + KEY_F7 = 296, + KEY_F8 = 297, + KEY_F9 = 298, + KEY_F10 = 299, + KEY_F11 = 300, + KEY_F12 = 301, + KEY_F13 = 302, + KEY_F14 = 303, + KEY_F15 = 304, + KEY_F16 = 305, + KEY_F17 = 306, + KEY_F18 = 307, + KEY_F19 = 308, + KEY_F20 = 309, + KEY_F21 = 310, + KEY_F22 = 311, + KEY_F23 = 312, + KEY_F24 = 313, + KEY_F25 = 314, + KEY_KP_0 = 320, + KEY_KP_1 = 321, + KEY_KP_2 = 322, + KEY_KP_3 = 323, + KEY_KP_4 = 324, + KEY_KP_5 = 325, + KEY_KP_6 = 326, + KEY_KP_7 = 327, + KEY_KP_8 = 328, + KEY_KP_9 = 329, + KEY_KP_DECIMAL = 330, + KEY_KP_DIVIDE = 331, + KEY_KP_MULTIPLY = 332, + KEY_KP_SUBTRACT = 333, + KEY_KP_ADD = 334, + KEY_KP_ENTER = 335, + KEY_KP_EQUAL = 336, +} KeyboardKey; + +typedef enum { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE, @@ -320,21 +256,21 @@ enum input_keys { KEY_MENU, INPUT_KEY_COUNT, -}; +} InputKey; -enum modifiers { +typedef enum { MOD_SHIFT = (1 << 0), MOD_CONTROL = (1 << 1), MOD_ALT = (1 << 2), MOD_SUPER = (1 << 3), MOD_MASK = (MOD_SHIFT|MOD_CONTROL|MOD_ALT|MOD_SUPER), -}; +} InputModifier; typedef struct { /* TODO: is this even supported or does GLFW only call you once per poll? */ - u16 transitions; - u16 ended_down; + u8 transitions; + u8 ended_down; } ButtonState; typedef struct TerminalInput { @@ -348,7 +284,7 @@ typedef struct TerminalInput { b32 window_focused; b32 pending_updates; - u32 modifiers; + InputModifier modifiers; v2 mouse; v2 last_mouse; @@ -367,7 +303,7 @@ typedef struct TerminalMemory { u64 debug_memory_size; void *debug_memory; - PlatformAPI platform_api; + OS os; } TerminalMemory; /************************************************************/ @@ -386,18 +322,20 @@ typedef VTGL_RENDER_FRAME_FN(vtgl_render_frame_fn); typedef VTGL_ACTIVE_SELECTION_FN(vtgl_active_selection_fn); #define VTGL_HANDLE_KEYS_FN(name) void name(TerminalMemory *memory, TerminalInput *input, \ - i32 key, i32 action, u32 modifiers) + KeyboardKey key, ButtonAction action, InputModifier modifiers) typedef VTGL_HANDLE_KEYS_FN(vtgl_handle_keys_fn); +#include "os.h" + #include "util.h" #include "util.c" #include "debug.h" #ifdef __ARM_ARCH_ISA_A64 -#include "platform_linux_aarch64.c" +#include "os_linux_aarch64.c" #elif defined(__linux__) && (defined(__x86_64__) || defined(_M_X64)) -#include "platform_linux_amd64.c" +#include "os_linux_amd64.c" #else #error Unsupported Platform! #endif