vtgl

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

os_linux_x11.c (16043B)


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