vtgl

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

platform_linux_x11.c (17372B)


      1 /* See LICENSE for copyright details */
      2 #define GL_GLEXT_PROTOTYPES
      3 #include <GLFW/glfw3.h>
      4 
      5 /* TODO: fix glfw */
      6 typedef void *RROutput;
      7 typedef void *RRCrtc;
      8 typedef void *Display;
      9 typedef void *Window;
     10 
     11 #define GLFW_EXPOSE_NATIVE_X11
     12 #define GLFW_NATIVE_INCLUDE_NONE
     13 #include <GLFW/glfw3native.h>
     14 
     15 #include "vtgl.h"
     16 
     17 i32 XConnectionNumber(void *display);
     18 i32 XPending(void *display);
     19 
     20 #ifndef _DEBUG
     21 #define do_debug(...)
     22 #include "vtgl.c"
     23 #else
     24 #include <dlfcn.h>
     25 
     26 #define DEBUG_LIB_NAME "./vtgl.so"
     27 
     28 #define LIB_FNS                  \
     29 	X(vtgl_active_selection) \
     30 	X(vtgl_initialize)       \
     31 	X(vtgl_render_frame)     \
     32 	X(vtgl_handle_keys)      \
     33 	X(vtgl_frame_step)
     34 
     35 #define X(name) static name ## _fn *name;
     36 LIB_FNS
     37 #undef X
     38 
     39 static PLATFORM_FILE_WATCH_CALLBACK_FN(debug_reload_library)
     40 {
     41 	PlatformCtx *ctx = user_ctx;
     42 
     43 	if (ctx->input.executable_reloaded)
     44 		return;
     45 
     46 	/* NOTE(rnp): spin until render thread finishes its work */
     47 	while (!ctx->render_stack->thread_asleep);
     48 
     49 	ctx->input.executable_reloaded = 1;
     50 	s8 nl = s8("\n");
     51 	/* NOTE: glibc sucks and will crash if this is NULL */
     52 	if (ctx->library_handle)
     53 		dlclose(ctx->library_handle);
     54 	ctx->library_handle = dlopen((c8 *)path, RTLD_NOW|RTLD_LOCAL);
     55 	if (!ctx->library_handle)
     56 		stream_push_s8s(&ctx->error_stream, 3,
     57 		                (s8 []){s8("dlopen: "), c_str_to_s8(dlerror()), nl});
     58 
     59 	#define X(name) \
     60 		name = dlsym(ctx->library_handle, #name); \
     61 		if (!name) stream_push_s8s(&ctx->error_stream, 3, (s8 []){s8("dlsym: "), \
     62 			                                                  c_str_to_s8(dlerror()), nl});
     63 	LIB_FNS
     64 	#undef X
     65 
     66 	stream_push_s8(&ctx->error_stream, s8("Reloaded Main Program\n"));
     67 
     68 	os_write_err_msg(stream_to_s8(&ctx->error_stream));
     69 	ctx->error_stream.widx = 0;
     70 }
     71 #endif /* _DEBUG */
     72 
     73 static void
     74 glfw_error_callback(int code, const char *desc)
     75 {
     76 	u8 buf[256];
     77 	Stream err = {.cap = sizeof(buf), .buf = buf};
     78 	stream_push_s8(&err, s8("GLFW Error (0x"));
     79 	stream_push_hex_u64(&err, code);
     80 	stream_push_s8(&err, s8("): "));
     81 	os_write_err_msg(stream_to_s8(&err));
     82 	os_write_err_msg(c_str_to_s8((char *)desc));
     83 	os_write_err_msg(s8("\n"));
     84 }
     85 
     86 static void
     87 char_callback(GLFWwindow *win, u32 codepoint)
     88 {
     89 	PlatformCtx *ctx = glfwGetWindowUserPointer(win);
     90 	stream_push_s8(&ctx->char_stream, utf8_encode(codepoint));
     91 }
     92 
     93 /* NOTE: called when the window was resized */
     94 static void
     95 fb_callback(GLFWwindow *win, i32 w, i32 h)
     96 {
     97 	PlatformCtx *ctx       = glfwGetWindowUserPointer(win);
     98 	ctx->input.window_size = (iv2){.w = w, .h = h};
     99 }
    100 
    101 static void
    102 scroll_callback(GLFWwindow *win, f64 xoff, f64 yoff)
    103 {
    104 	PlatformCtx *ctx          = glfwGetWindowUserPointer(win);
    105 	ctx->input.mouse_scroll.x = xoff;
    106 	ctx->input.mouse_scroll.y = yoff;
    107 }
    108 
    109 static void
    110 key_callback(GLFWwindow *win, i32 key, i32 scancode, i32 action, i32 modifiers)
    111 {
    112 	PlatformCtx *ctx     = glfwGetWindowUserPointer(win);
    113 	TerminalInput *input = &ctx->input;
    114 
    115 	/* TODO: base this on X11 keys directly */
    116 	switch (key) {
    117 	case GLFW_KEY_LEFT_SHIFT:
    118 		button_action(input->keys + KEY_LEFT_SHIFT, action == GLFW_PRESS);
    119 		if (action == GLFW_RELEASE) input->modifiers &= ~MOD_SHIFT;
    120 		else                        input->modifiers |=  MOD_SHIFT;
    121 		break;
    122 	case GLFW_KEY_LEFT_CONTROL:
    123 		button_action(input->keys + KEY_LEFT_CONTROL, action == GLFW_PRESS);
    124 		if (action == GLFW_RELEASE) input->modifiers &= ~MOD_CONTROL;
    125 		else                        input->modifiers |=  MOD_CONTROL;
    126 		break;
    127 	case GLFW_KEY_LEFT_ALT:
    128 		button_action(input->keys + KEY_LEFT_ALT, action == GLFW_PRESS);
    129 		if (action == GLFW_RELEASE) input->modifiers &= ~MOD_ALT;
    130 		else                        input->modifiers |=  MOD_ALT;
    131 		break;
    132 	case GLFW_KEY_LEFT_SUPER:
    133 		button_action(input->keys + KEY_LEFT_SUPER, action == GLFW_PRESS);
    134 		if (action == GLFW_RELEASE) input->modifiers &= ~MOD_SUPER;
    135 		else                        input->modifiers |=  MOD_SUPER;
    136 		break;
    137 	case GLFW_KEY_RIGHT_SHIFT:
    138 		button_action(input->keys + KEY_RIGHT_SHIFT, action == GLFW_PRESS);
    139 		if (action == GLFW_RELEASE) input->modifiers &= ~MOD_SHIFT;
    140 		else                        input->modifiers |=  MOD_SHIFT;
    141 		break;
    142 	case GLFW_KEY_RIGHT_CONTROL:
    143 		button_action(input->keys + KEY_RIGHT_CONTROL, action == GLFW_PRESS);
    144 		if (action == GLFW_RELEASE) input->modifiers &= ~MOD_CONTROL;
    145 		else                        input->modifiers |=  MOD_CONTROL;
    146 		break;
    147 	case GLFW_KEY_RIGHT_ALT:
    148 		button_action(input->keys + KEY_RIGHT_ALT, action == GLFW_PRESS);
    149 		if (action == GLFW_RELEASE) input->modifiers &= ~MOD_ALT;
    150 		else                        input->modifiers |=  MOD_ALT;
    151 		break;
    152 	case GLFW_KEY_RIGHT_SUPER:
    153 		button_action(input->keys + KEY_RIGHT_SUPER, action == GLFW_PRESS);
    154 		if (action == GLFW_RELEASE) input->modifiers &= ~MOD_SUPER;
    155 		else                        input->modifiers |=  MOD_SUPER;
    156 		break;
    157 	case GLFW_KEY_MENU:
    158 		button_action(input->keys + KEY_MENU, action == GLFW_PRESS);
    159 		break;
    160 	}
    161 	vtgl_handle_keys(&ctx->memory, &ctx->input, key, action, modifiers);
    162 }
    163 
    164 static void
    165 mouse_button_callback(GLFWwindow *win, i32 button, i32 action, i32 modifiers)
    166 {
    167 	PlatformCtx *ctx     = glfwGetWindowUserPointer(win);
    168 	TerminalInput *input = &ctx->input;
    169 
    170 	switch (button) {
    171 	case GLFW_MOUSE_BUTTON_LEFT:
    172 		button_action(input->keys + MOUSE_LEFT, action == GLFW_PRESS);
    173 		break;
    174 	case GLFW_MOUSE_BUTTON_RIGHT:
    175 		button_action(input->keys + MOUSE_RIGHT, action == GLFW_PRESS);
    176 		break;
    177 	case GLFW_MOUSE_BUTTON_MIDDLE:
    178 		button_action(input->keys + MOUSE_MIDDLE, action == GLFW_PRESS);
    179 		break;
    180 	case GLFW_MOUSE_BUTTON_4:
    181 		button_action(input->keys + MOUSE_EXTENDED_0, action == GLFW_PRESS);
    182 		break;
    183 	case GLFW_MOUSE_BUTTON_5:
    184 		button_action(input->keys + MOUSE_EXTENDED_1, action == GLFW_PRESS);
    185 		break;
    186 	}
    187 }
    188 
    189 static void
    190 focus_callback(GLFWwindow *win, i32 focused)
    191 {
    192 	PlatformCtx *ctx            = glfwGetWindowUserPointer(win);
    193 	ctx->input.window_focused   = focused;
    194 	/* NOTE: force a refresh as well when the focus changes */
    195 	ctx->input.window_refreshed = 1;
    196 }
    197 
    198 static void
    199 refresh_callback(GLFWwindow *win)
    200 {
    201 	PlatformCtx *ctx            = glfwGetWindowUserPointer(win);
    202 	ctx->input.window_refreshed = 1;
    203 }
    204 
    205 static GLFWwindow *
    206 init_window(PlatformCtx *ctx, iv2 window_size)
    207 {
    208 	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    209 	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    210 	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
    211 	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    212 
    213 	#ifdef _DEBUG
    214 	glfwWindowHint(GLFW_CONTEXT_DEBUG, GLFW_TRUE);
    215 	#endif
    216 
    217 	/* NOTE: we initially hide the window so that it can be freely resized behind the
    218 	 * back of the window manager prior to showing */
    219 	glfwWindowHint(GLFW_VISIBLE,   GLFW_FALSE);
    220 	glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
    221 
    222 	GLFWwindow *window = glfwCreateWindow(window_size.w, window_size.h, "vtgl", NULL, NULL);
    223 	if (!window) {
    224 		glfwTerminate();
    225 		os_fatal(s8("Failed to spawn GLFW window\n"));
    226 	}
    227 	glfwMakeContextCurrent(window);
    228 	glfwSetWindowUserPointer(window, ctx);
    229 
    230 	/* TODO: swap interval is not needed because we will sleep on waiting for terminal input */
    231 	glfwSwapInterval(1);
    232 
    233 	glfwSetCharCallback(window,            char_callback);
    234 	glfwSetFramebufferSizeCallback(window, fb_callback);
    235 	glfwSetKeyCallback(window,             key_callback);
    236 	glfwSetMouseButtonCallback(window,     mouse_button_callback);
    237 	glfwSetScrollCallback(window,          scroll_callback);
    238 	glfwSetWindowFocusCallback(window,     focus_callback);
    239 	glfwSetWindowRefreshCallback(window,   refresh_callback);
    240 
    241 	ctx->win_fd = XConnectionNumber(glfwGetX11Display());
    242 
    243 	return window;
    244 }
    245 
    246 static void
    247 update_input(PlatformCtx *ctx)
    248 {
    249 	TerminalInput *input = &ctx->input;
    250 	ctx->input.executable_reloaded = 0;
    251 	ctx->input.window_refreshed    = 0;
    252 
    253 	/* NOTE: mouse */
    254 	input->last_mouse   = input->mouse;
    255 	input->mouse_scroll = (v2){0};
    256 
    257 	f64 mouse_x, mouse_y;
    258 	glfwGetCursorPos(ctx->window, &mouse_x, &mouse_y);
    259 	input->mouse.x = mouse_x;
    260 	input->mouse.y = input->window_size.h - mouse_y;
    261 
    262 	for (u32 i = 0; i < ARRAY_COUNT(input->keys); i++)
    263 		input->keys[i].transitions = 0;
    264 
    265 	ctx->char_stream.widx = 0;
    266 
    267 	i64 timeout[2] = {0, 25e6};
    268 	if (input->pending_updates) {
    269 		timeout[1]             = 0;
    270 		input->pending_updates = 0;
    271 	}
    272 
    273 	sys_fd_set rfd = {0};
    274 	FD_SET(ctx->child.handle, rfd);
    275 	FD_SET(ctx->inotify_fd,   rfd);
    276 	FD_SET(ctx->win_fd,       rfd);
    277 
    278 	i32 max_fd = MAX(ctx->inotify_fd, ctx->child.handle);
    279 	max_fd     = MAX(max_fd, ctx->win_fd);
    280 	syscall6(SYS_pselect6, max_fd + 1, (iptr)rfd, 0, 0, (iptr)timeout, 0);
    281 
    282 	input->data_available = FD_ISSET(ctx->child.handle, rfd) != 0;
    283 
    284 	try_deferred_file_loads(ctx);
    285 	if (FD_ISSET(ctx->inotify_fd, rfd))
    286 		dispatch_file_watch_events(ctx);
    287 
    288 	glfwPollEvents();
    289 
    290 	/* NOTE: char_stream was filled in char callback */
    291 	input->character_input = stream_to_s8(&ctx->char_stream);
    292 }
    293 
    294 static PLATFORM_CLIPBOARD_FN(x11_get_clipboard)
    295 {
    296 	/* NOTE: this does a bunch of extra copying and other garbage. both GLFW and X11 are
    297 	 * at fault. The API is designed to do what the terminal wants and  not be constrained
    298 	 * by GLFW and X11 garbage */
    299 	ASSERT(buffer);
    300 	char *text = 0;
    301 	switch (clipboard) {
    302 	case CLIPBOARD_0: text = (c8 *)glfwGetClipboardString(0);   break;
    303 	case CLIPBOARD_1: text = (c8 *)glfwGetX11SelectionString(); break;
    304 	}
    305 	if (text) {
    306 		/* TODO: we may need to replace '\n' with '\r\n' */
    307 		stream_push_s8(buffer, c_str_to_s8(text));
    308 	}
    309 
    310 	return !buffer->errors;
    311 }
    312 
    313 static PLATFORM_CLIPBOARD_FN(x11_set_clipboard)
    314 {
    315 	ASSERT(buffer);
    316 	stream_push_byte(buffer, 0);
    317 
    318 	if (!buffer->errors) {
    319 		switch (clipboard) {
    320 		case CLIPBOARD_0: glfwSetClipboardString(0, (c8 *)buffer->buf); break;
    321 		case CLIPBOARD_1: glfwSetX11SelectionString((c8 *)buffer->buf); break;
    322 		}
    323 	}
    324 	return !buffer->errors;
    325 }
    326 
    327 static PLATFORM_WINDOW_TITLE_FN(x11_get_window_title)
    328 {
    329 	ASSERT(buffer);
    330 	char *title = (c8 *)glfwGetWindowTitle(linux_ctx.window);
    331 	if (title) stream_push_s8(buffer, c_str_to_s8(title));
    332 }
    333 
    334 static PLATFORM_WINDOW_TITLE_FN(x11_set_window_title)
    335 {
    336 	ASSERT(buffer);
    337 	stream_push_byte(buffer, 0);
    338 	if (!buffer->errors)
    339 		glfwSetWindowTitle(linux_ctx.window, (c8 *)buffer->buf);
    340 }
    341 
    342 static void
    343 linux_render_thread_entry(struct stack_base *stack)
    344 {
    345 	{
    346 		/* NOTE: set thread name */
    347 		char name[16] = "[render]";
    348 		syscall2(SYS_prctl, PR_SET_NAME, (iptr)name);
    349 		/* NOTE: halt until main thread is ready to hand off gl context */
    350 		syscall4(SYS_futex, (iptr)&stack->work_futex, FUTEX_WAIT, 0, 0);
    351 
    352 		glfwMakeContextCurrent(stack->window);
    353 	}
    354 
    355 	for (;;) {
    356 		stack->thread_asleep = 1;
    357 		syscall4(SYS_futex, (iptr)&stack->work_futex, FUTEX_WAIT, 0, 0);
    358 		stack->thread_asleep = 0;
    359 		vtgl_render_frame(stack->terminal_memory, stack->input, stack->thread_arena);
    360 		glfwSwapBuffers(stack->window);
    361 	}
    362 
    363 	__builtin_unreachable();
    364 }
    365 
    366 i32
    367 main(i32 argc, char *argv[], char *envp[])
    368 {
    369 	linux_ctx.platform_memory = arena_from_memory_block(os_block_alloc(MB(2)));
    370 	linux_ctx.error_stream    = stream_alloc(&linux_ctx.platform_memory, KB(256));
    371 
    372 	iv2 cells = {.x = -1, .y = -1};
    373 
    374 	char *argv0 = *argv++;
    375 	argc--;
    376 	for (i32 i = 0; i < argc; i++) {
    377 		char *arg = argv[i];
    378 		if (!arg || !arg[0])
    379 			usage(argv0, &linux_ctx.error_stream);
    380 		if (arg[0] != '-')
    381 			break;
    382 		arg++;
    383 		switch (arg[0]) {
    384 		case 'g': {
    385 			if (!argv[i + 1])
    386 				usage(argv0, &linux_ctx.error_stream);
    387 			s8 g_arg = c_str_to_s8(argv[i + 1]);
    388 			struct conversion_result cres = s8_parse_i32_until(g_arg, 'x');
    389 			if (cres.status == CR_SUCCESS)
    390 				cells.w = cres.i;
    391 
    392 			if (cres.unparsed.len > 0 && cres.unparsed.data[0] == 'x') {
    393 				s8 remainder = {.len = cres.unparsed.len - 1,
    394 				                .data = cres.unparsed.data + 1};
    395 				cres = s8_parse_i32(remainder);
    396 				if (cres.status == CR_SUCCESS)
    397 					cells.h = cres.i;
    398 			}
    399 
    400 			if (cells.w <= 0 || cells.h <= 0) {
    401 				stream_push_s8(&linux_ctx.error_stream, s8("ignoring malformed geometry: "));
    402 				stream_push_s8(&linux_ctx.error_stream, c_str_to_s8(argv[i + 1]));
    403 				stream_push_byte(&linux_ctx.error_stream, '\n');
    404 			}
    405 			argv++;
    406 			argc--;
    407 		} break;
    408 		case 'v':
    409 			stream_push_s8s(&linux_ctx.error_stream, 2,
    410 			                (s8 []){c_str_to_s8(argv0), s8(" " VERSION "\n")});
    411 			os_fatal(stream_to_s8(&linux_ctx.error_stream));
    412 		default:
    413 			usage(argv0, &linux_ctx.error_stream);
    414 		}
    415 	}
    416 	if (linux_ctx.error_stream.widx) {
    417 		os_write_err_msg(stream_to_s8(&linux_ctx.error_stream));
    418 		linux_ctx.error_stream.widx = 0;
    419 	}
    420 
    421 	linux_ctx.render_stack                = new_stack(KB(256));
    422 	linux_ctx.render_stack->entry         = linux_render_thread_entry;
    423 	linux_ctx.render_stack->thread_asleep = 1;
    424 	new_thread(linux_ctx.render_stack);
    425 
    426 	{
    427 		Arena tmp = linux_ctx.platform_memory;
    428 		SLLVariableVector environment_block = parse_environment(&tmp, envp);
    429 
    430 		/* TODO: build up argv for the child as well */
    431 		c8 **child_envp = construct_c_str_array(&tmp, environment_block);
    432 		linux_ctx.child = os_fork_child(get_default_cmd(envp), child_envp);
    433 	}
    434 
    435 	{
    436 		MemoryBlock terminal_memory  = os_block_alloc(MB(32));
    437 		linux_ctx.memory.memory      = terminal_memory.memory;
    438 		linux_ctx.memory.memory_size = terminal_memory.size;
    439 #ifdef _DEBUG
    440 		MemoryBlock debug_memory           = os_block_alloc(MB(128));
    441 		linux_ctx.memory.debug_memory      = debug_memory.memory;
    442 		linux_ctx.memory.debug_memory_size = debug_memory.size;
    443 #endif
    444 	}
    445 
    446 	linux_ctx.inotify_fd = syscall1(SYS_inotify_init1, O_NONBLOCK|O_CLOEXEC);
    447 
    448 #ifdef _DEBUG
    449 	debug_reload_library((u8 *)DEBUG_LIB_NAME, &linux_ctx);
    450 	linux_add_file_watch((u8 *)DEBUG_LIB_NAME, debug_reload_library, &linux_ctx);
    451 #endif
    452 
    453 	linux_ctx.memory.platform_api.add_file_watch       = linux_add_file_watch;
    454 	linux_ctx.memory.platform_api.allocate_ring_buffer = os_allocate_ring_buffer;
    455 	linux_ctx.memory.platform_api.get_clipboard        = x11_get_clipboard;
    456 	linux_ctx.memory.platform_api.set_clipboard        = x11_set_clipboard;
    457 	linux_ctx.memory.platform_api.read_file            = os_read_file;
    458 	linux_ctx.memory.platform_api.read                 = os_read;
    459 	linux_ctx.memory.platform_api.set_terminal_size    = os_set_terminal_size;
    460 	linux_ctx.memory.platform_api.get_window_title     = x11_get_window_title;
    461 	linux_ctx.memory.platform_api.set_window_title     = x11_set_window_title;
    462 	linux_ctx.memory.platform_api.write                = os_write;
    463 	linux_ctx.memory.platform_api.path_separator       = '/';
    464 
    465 	if (!glfwInit())
    466 		os_fatal(s8("Failed to init GLFW\n"));
    467 	glfwSetErrorCallback(glfw_error_callback);
    468 
    469 	GLFWmonitor *mon = glfwGetPrimaryMonitor();
    470 	if (!mon) {
    471 		glfwTerminate();
    472 		os_fatal(s8("Failed to find any monitors!\n"));
    473 	}
    474 	iv2 monitor_size;
    475 	glfwGetMonitorWorkarea(mon, NULL, NULL, &monitor_size.w, &monitor_size.h);
    476 
    477 	linux_ctx.char_stream = arena_stream(linux_ctx.platform_memory);
    478 
    479 	iv2 window_size  = {.w = 1280, .h = 720};
    480 	linux_ctx.window = init_window(&linux_ctx, window_size);
    481 
    482 	iv2 requested_size = vtgl_initialize(&linux_ctx.memory, linux_ctx.child.handle, cells, monitor_size);
    483 	if (requested_size.w > 0 && requested_size.h > 0 &&
    484 	    (requested_size.w != window_size.w || requested_size.h != window_size.h))
    485 	{
    486 		glfwSetWindowAttrib(linux_ctx.window, GLFW_FLOATING, GLFW_TRUE);
    487 		i32 x       = ABS(window_size.w - requested_size.w) / 2;
    488 		i32 y       = ABS(window_size.h - requested_size.h) / 2;
    489 		window_size = requested_size;
    490 		glfwSetWindowMonitor(linux_ctx.window, 0, x, y, window_size.w, window_size.h, GLFW_DONT_CARE);
    491 		/* NOTE: resizable must be set after the window is shown; otherwise tiling window
    492 		 * managers will forcibly resize us even if we are supposed to be floating */
    493 		glfwShowWindow(linux_ctx.window);
    494 		glfwSetWindowAttrib(linux_ctx.window, GLFW_RESIZABLE, GLFW_TRUE);
    495 	} else {
    496 		/* NOTE: on the other hand we should let the window be resized if no size was
    497 		 * explicitly requested */
    498 		glfwSetWindowAttrib(linux_ctx.window, GLFW_RESIZABLE, GLFW_TRUE);
    499 		glfwSetWindowPos(linux_ctx.window,
    500 		                 ABS(monitor_size.w - window_size.w) / 2,
    501 		                 ABS(monitor_size.h - window_size.h) / 2);
    502 		glfwShowWindow(linux_ctx.window);
    503 	}
    504 	glfwMakeContextCurrent(0);
    505 
    506 	linux_ctx.input.window_size = window_size;
    507 
    508 	linux_ctx.render_stack->input           = &linux_ctx.input;
    509 	linux_ctx.render_stack->terminal_memory = &linux_ctx.memory;
    510 	linux_ctx.render_stack->thread_arena    = arena_from_memory_block(os_block_alloc(MB(8)));
    511 	linux_ctx.render_stack->window          = linux_ctx.window;
    512 	syscall3(SYS_futex, (iptr)&linux_ctx.render_stack->work_futex, FUTEX_WAKE, 1);
    513 
    514 	Range last_sel   = {0};
    515 	f64   last_time  = os_get_time();
    516 	while (!glfwWindowShouldClose(linux_ctx.window)) {
    517 		if (os_child_exited(linux_ctx.child.process_id))
    518 			break;
    519 
    520 		/* TODO: cpu time excluding waiting for the vblank */
    521 		f64 current_time   = os_get_time();
    522 		linux_ctx.input.dt = current_time - last_time;
    523 		last_time          = current_time;
    524 
    525 		update_input(&linux_ctx);
    526 		if (vtgl_frame_step(&linux_ctx.memory, &linux_ctx.input))
    527 			syscall3(SYS_futex, (iptr)&linux_ctx.render_stack->work_futex, FUTEX_WAKE, 1);
    528 
    529 		Range current_sel = vtgl_active_selection(&linux_ctx.memory, 0);
    530 		if (is_valid_range(current_sel) && !equal_range(current_sel, last_sel)) {
    531 			Stream buf = arena_stream(linux_ctx.platform_memory);
    532 			vtgl_active_selection(&linux_ctx.memory, &buf);
    533 			stream_push_byte(&buf, 0);
    534 			if (!buf.errors)
    535 				glfwSetX11SelectionString((c8 *)buf.buf);
    536 			last_sel = current_sel;
    537 		}
    538 	}
    539 
    540 	syscall1(SYS_exit_group, 0);
    541 	__builtin_unreachable();
    542 
    543 	return 0;
    544 }