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 }