#if 0 /* #/ ================================================================ #/ #/ reduced_system_layer.c #/ #/ This is a reduced system layer. #/ It allows you to create a window, draw graphics in it, handle #/ input events, write samples to audio output, send and receive #/ UDP packets. #/ #/ ---------------------------------------------------------------- #/ #/ DESIGN PRINCIPLES #/ #/ - Minimalistic feature set. For graphics, you have access to the #/ pixel buffer, and that's it. #/ #/ - No implicit control flow. No callbacks. You write your own #/ main and call everything explicitly. But the number of things #/ you have to call to do something is as little as possible. #/ #/ - Optimized to use in a single source file. #/ Installation process? Ctrl+C, Ctrl+V, done. #/ #/ If you have an idea how to reduce the feature set further, #/ let me know! #/ #/ ---------------------------------------------------------------- #/ #/ To-Do list #/ #/ - Examples #/ - Conway's Game of Life #/ - Julia Set #/ - Labyrinth #/ - Landscape #/ - Features #/ - Sound #/ - Clipboard daemon #/ #/ ALSA #/ https://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_min_8c-example.html #/ #/ ---------------------------------------------------------------- #/ #/ (C) 2024 Mitya Selivanov , MIT License #/ #/ ================================================================ #/ #/ Self-compilation shell script #/ SRC=${0##*./} BIN=${SRC%.*} gcc \ -Wall -Wextra -Werror -pedantic \ -Wno-old-style-declaration \ -Wno-missing-braces \ -Wno-unused-variable \ -Wno-unused-but-set-variable \ -Wno-unused-parameter \ -Wno-overlength-strings \ -O3 \ -fsanitize=undefined,address,leak \ -D REDUCED_SYSTEM_LAYER_EXAMPLE \ -lX11 -lm \ -o $BIN $SRC && \ ./$BIN $@ && rm $BIN exit $? # */ #endif // ================================================================ // // Types // // ================================================================ #ifndef TYPES_HEADER_GUARD_ #define TYPES_HEADER_GUARD_ typedef signed char i8; typedef signed short i16; typedef signed i32; typedef signed long long i64; typedef unsigned char u8; typedef unsigned short u16; typedef unsigned u32; typedef unsigned long long u64; typedef char c8; typedef int c32; typedef signed char b8; typedef float f32; typedef double f64; #endif // TYPES_HEADER_GUARD_ // ================================================================ // // Basic declarations // // ================================================================ #ifndef REDUCED_SYSTEM_LAYER_HEADER_GUARD_ #define REDUCED_SYSTEM_LAYER_HEADER_GUARD_ // ================================================================ // // PLATFORM API // // ================================================================ #if !defined(__wasm__) #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #endif #ifdef __cplusplus extern "C" { #endif enum { MAX_NUM_PIXELS = 10 * 1024 * 1024, MAX_INPUT_SIZE = 256, MAX_CLIPBOARD_SIZE = 10 * 1024 * 1024, MAX_NUM_AUDIO_SAMPLES = 0, MAX_NUM_SOCKETS = 64, MAX_NUM_KEYS = 512, AUDIO_NUM_CHANNELS = 2, AUDIO_SAMPLE_RATE = 44100, IPv4_UDP = 1, IPv6_UDP = 2, MOD_CTRL = 1, MOD_SHIFT = 2, MOD_ALT = 3, MOD_CAPS = 4, MOD_NUM = 5, MOD_SCROLL = 6, MOD_META = 7, KEY_BACKSPACE = 8, KEY_TAB = 9, KEY_ENTER = 10, KEY_LCTRL = 11, KEY_RCTRL = 12, KEY_LSHIFT = 13, KEY_RSHIFT = 14, KEY_LALT = 15, KEY_RALT = 16, KEY_LEFT = 17, KEY_RIGHT = 18, KEY_UP = 19, KEY_DOWN = 20, KEY_PAUSE = 21, KEY_INSERT = 22, KEY_HOME = 23, KEY_END = 24, KEY_PAGEUP = 25, KEY_PAGEDOWN = 26, KEY_ESCAPE = 27, KEY_PRINTSCREEN = 28, BUTTON_LEFT = 29, BUTTON_MIDDLE = 30, BUTTON_RIGHT = 31, KEY_SPACE = 32, KEY_LMETA = 33, KEY_RMETA = 34, KEY_QUOTE = 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_COLON = 59, KEY_EQUAL = 61, KEY_LBRACE = 91, KEY_BACKSLASH = 92, KEY_RBRACE = 93, KEY_TILDE = 96, KEY_A = 97, KEY_B = 98, KEY_C = 99, KEY_D = 100, KEY_E = 101, KEY_F = 102, KEY_G = 103, KEY_H = 104, KEY_I = 105, KEY_J = 106, KEY_K = 107, KEY_L = 108, KEY_M = 109, KEY_N = 110, KEY_O = 111, KEY_P = 112, KEY_Q = 113, KEY_R = 114, KEY_S = 115, KEY_T = 116, KEY_U = 117, KEY_V = 118, KEY_W = 119, KEY_X = 120, KEY_Y = 121, KEY_Z = 122, KEY_DELETE = 127, KEY_KP_0 = 128, KEY_KP_1 = 129, KEY_KP_2 = 130, KEY_KP_3 = 131, KEY_KP_4 = 132, KEY_KP_5 = 133, KEY_KP_6 = 134, KEY_KP_7 = 135, KEY_KP_8 = 136, KEY_KP_9 = 137, KEY_KP_ENTER = 138, KEY_KP_DIVIDE = 139, KEY_KP_MULTIPLY = 140, KEY_KP_ADD = 141, KEY_KP_SUBTRACT = 142, KEY_KP_DECIMAL = 143, KEY_KP_SEPARATOR = 144, KEY_F1 = 145, KEY_F2 = 146, KEY_F3 = 147, KEY_F4 = 148, KEY_F5 = 149, KEY_F6 = 150, KEY_F7 = 151, KEY_F8 = 152, KEY_F9 = 153, KEY_F10 = 154, KEY_F11 = 155, KEY_F12 = 156, KEY_F13 = 157, KEY_F14 = 158, KEY_F15 = 159, KEY_F16 = 160, KEY_F17 = 161, KEY_F18 = 162, KEY_F19 = 163, KEY_F20 = 164, KEY_F21 = 165, KEY_F22 = 166, KEY_F23 = 167, KEY_F24 = 168, }; typedef struct { b8 ctrl : 1; b8 shift : 1; b8 alt : 1; b8 caps : 1; b8 num : 1; b8 scroll : 1; b8 meta : 1; u16 key; c32 c; } Input_Key; typedef struct { c8 * title; i32 frame_width; i32 frame_height; u32 * pixels; i64 input_size; Input_Key *input; i64 clipboard_size; c8 * clipboard; b8 done; b8 graceful_exit; b8 has_focus; b8 has_cursor; i32 cursor_x; i32 cursor_y; i32 cursor_dx; i32 cursor_dy; i64 wheel_dy; b8 key_down[MAX_NUM_KEYS]; b8 key_pressed[MAX_NUM_KEYS]; } Platform; typedef struct { u16 protocol; u16 port; union { u32 v4_address_as_u32; u8 v4_address[4]; u8 v6_address[16]; }; } IP_Address; // UTF-8 // NOTE We need UTF-8 because we use Xutf8LookupString on Xlib. i32 utf8_size(c32 c); c32 utf8_read(i64 len, c8 *s); i32 utf8_write(c32 c, c8 *buffer); // Time and sleep i64 p_time(void); void p_yield(void); void p_sleep_for(i64 duration); // Window void p_init(void); void p_cleanup(void); i32 p_handle_events(void); i32 p_wait_events(void); void p_render_frame(void); // User-defined frame updating procedure // NOTE This procedure is required for the WebAssembly compatibility. void update_and_render_frame(void); // Convenient helper procedure for the event loop void p_event_loop(void); // Clipboard void p_clipboard_write(i64 size, c8 *data); // Sound void p_handle_audio(i64 time_elapsed); void p_queue_sound(i64 delay, i64 num_samples, f32 *samples); // UDP sockets i64 p_recv(u16 slot, IP_Address address, i64 size, u8 *data, u16 *local_port, IP_Address *remote_address); i64 p_send(u16 slot, IP_Address address, i64 size, u8 *data, u16 *local_port); extern Platform platform; #ifdef __wasm__ i32 main(i32 argc, c8 **argv); f64 sqrt(f64 x); f64 floor(f64 x); f64 ceil(f64 x); #ifndef NULL #define NULL ((void *) 0) #endif #endif #ifdef __cplusplus } #endif // ================================================================ #endif // REDUCED_SYSTEM_LAYER_HEADER_GUARD_ // ================================================================ // // EXAMPLE CODE // // ================================================================ #ifdef REDUCED_SYSTEM_LAYER_EXAMPLE void update_and_render_frame(void) { p_handle_events(); i64 w = platform.frame_width / 2; i64 h = platform.frame_height / 2; i64 x = w / 2; i64 y = h / 2; for (i64 j = 0; j < platform.frame_height; ++j) for (i64 i = 0; i < platform.frame_width; ++i) if (i < x || i >= x + w || j < y || j >= y + h) platform.pixels[j * platform.frame_width + i] = 0xa0a0a0; else platform.pixels[j * platform.frame_width + i] = 0x131112; p_render_frame(); p_sleep_for(0); } i32 main(i32 argc, c8 **argv) { (void) argc; (void) argv; platform = (Platform) { .title = "Example", .frame_width = 1280, .frame_height = 720, }; p_event_loop(); return 0; } #endif // ================================================================ // // PLATFORM IMPLEMENTATION // // ================================================================ #ifndef REDUCED_SYSTEM_LAYER_HEADER #ifndef REDUCED_SYSTEM_LAYER_IMPL_GUARD_ #define REDUCED_SYSTEM_LAYER_IMPL_GUARD_ Platform platform = {0}; void p_event_loop(void) { p_init(); #if !defined(__wasm__) while (!platform.done) update_and_render_frame(); p_cleanup(); #endif } // ================================================================ // // Utilities // // ================================================================ c32 utf8_read(i64 len, c8 *s) { if (len >= 1 && (s[0] & 0x80) == 0) return s[0]; if (len >= 2 && (s[0] & 0xe0) == 0xc0 && (s[1] & 0xc0) == 0x80) return (s[1] & 0x3f) | ((s[0] & 0x1f) << 6); if (len >= 3 && (s[0] & 0xf0) == 0xe0 && (s[1] & 0xc0) == 0x80 && (s[2] & 0xc0) == 0x80) return (s[2] & 0x3f) | ((s[1] & 0x3f) << 6) | ((s[0] & 0x0f) << 12); if (len >= 4 && (s[0] & 0xf8) == 0xf0 && (s[1] & 0xc0) == 0x80 && (s[2] & 0xc0) == 0x80 && (s[3] & 0xc0) == 0x80) return (s[3] & 0x3f) | ((s[2] & 0x3f) << 6) | ((s[1] & 0x3f) << 12) | ((s[0] & 0x07) << 18); return 0; } i32 utf8_size(c32 c) { if ((c & 0x00007f) == c) return 1; if ((c & 0x0007ff) == c) return 2; if ((c & 0x00ffff) == c) return 3; if ((c & 0x1fffff) == c) return 4; return 0; } i32 utf8_write(c32 c, c8 *buffer) { if ((c & 0x7f) == c) { buffer[0] = (c8) c; return 1; } if ((c & 0x7ff) == c) { buffer[0] = 0xc0 | ((c >> 6) & 0x1f); buffer[1] = 0x80 | ( c & 0x3f); return 2; } if ((c & 0xffff) == c) { buffer[0] = 0xc0 | ((c >> 12) & 0x0f); buffer[1] = 0x80 | ((c >> 6) & 0x3f); buffer[2] = 0x80 | ( c & 0x3f); return 3; } if ((c & 0x1fffff) == c) { buffer[0] = 0xc0 | ((c >> 18) & 0x07); buffer[1] = 0x80 | ((c >> 12) & 0x3f); buffer[2] = 0x80 | ((c >> 6) & 0x3f); buffer[3] = 0x80 | ( c & 0x3f); return 4; } return 0; } // ================================================================ // // Unix // // ================================================================ #if defined(__unix__) #include #include #include i64 p_time(void) { struct timespec t; timespec_get(&t, TIME_UTC); return 1000 * t.tv_sec + t.tv_nsec / 1000000; } void p_yield(void) { sched_yield(); } void p_sleep_for(i64 duration) { if (duration <= 0) { usleep(0); return; } if (duration >= 1000) // seconds sleep(duration / 1000); if ((duration % 1000) > 0) // microseconds usleep((duration % 1000) * 1000); } #endif // defined(__unix__) // ================================================================ // // UDP sockets // // ================================================================ #if defined(__unix__) #include #include #include #include #include #include #include typedef struct { b8 ready; i32 socket; u16 local_port; IP_Address address; } Socket_Slot; b8 _sockets_init = 0; Socket_Slot _sockets[MAX_NUM_SOCKETS] = {0}; void sockets_initialize(void) { if (_sockets_init) return; signal(SIGPIPE, SIG_IGN); _sockets_init = 1; } void sockets_cleanup(void) { for (i64 i = 0; i < MAX_NUM_SOCKETS; ++i) if (_sockets[i].ready) { close(_sockets[i].socket); _sockets[i].ready = 0; } } b8 sockets_open(u16 slot, IP_Address address, u16 *local_port) { sockets_initialize(); b8 change_address = !_sockets[slot].ready || _sockets[slot].address.protocol != address.protocol || (address.port != 0 && _sockets[slot].local_port != address.port) || (memcmp(_sockets[slot].address.v6_address, &(u8[sizeof address.v6_address]) {0}, sizeof address.v6_address) != 0 && memcmp(_sockets[slot].address.v6_address, address.v6_address, sizeof address.v6_address) != 0); if (change_address && _sockets[slot].ready) { close(_sockets[slot].socket); _sockets[slot].ready = 0; } struct sockaddr *p; i32 p_len; struct sockaddr_in a4 = {0}; struct sockaddr_in6 a6 = {0}; if (address.protocol == IPv4_UDP) { p = (struct sockaddr *) &a4; p_len = sizeof a4; } else { p = (struct sockaddr *) &a6; p_len = sizeof a6; } if (!_sockets[slot].ready) { _sockets[slot].socket = socket(address.protocol == IPv4_UDP ? AF_INET : AF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (_sockets[slot].socket == -1) { fprintf(stderr, "ERROR: socket failed (errno %d)\n", errno); return 0; } if (address.protocol == IPv4_UDP) { a4.sin_family = AF_INET; a4.sin_port = htons(address.port); a4.sin_addr.s_addr = address.v4_address_as_u32; } else { a6.sin6_family = AF_INET6; a6.sin6_port = htons(address.port); memcpy(a6.sin6_addr.s6_addr, address.v6_address, sizeof a6.sin6_addr.s6_addr); } if (bind(_sockets[slot].socket, p, p_len) == -1) { close(_sockets[slot].socket); fprintf(stderr, "ERROR: bind failed (errno %d)\n", errno); return 0; } if (getsockname(_sockets[slot].socket, p, &(socklen_t) {p_len}) == -1) { close(_sockets[slot].socket); fprintf(stderr, "ERROR: getsockname failed (errno %d)\n", errno); return 0; } if (p->sa_family == AF_INET) _sockets[slot].local_port = ntohs(a4.sin_port); else _sockets[slot].local_port = ntohs(a6.sin6_port); _sockets[slot].ready = 1; _sockets[slot].address = address; } if (local_port != NULL) *local_port = _sockets[slot].local_port; return 1; } i64 p_recv(u16 slot, IP_Address address, i64 size, u8 *data, u16 *local_port, IP_Address *remote_address) { assert(slot < MAX_NUM_SOCKETS); assert(address.protocol == IPv4_UDP || address.protocol == IPv6_UDP); if (!sockets_open(slot, address, local_port)) return 0; if (size <= 0) return 0; struct sockaddr *p; i32 p_len; struct sockaddr_in a4 = {0}; struct sockaddr_in6 a6 = {0}; if (address.protocol == IPv4_UDP) { p = (struct sockaddr *) &a4; p_len = sizeof a4; } else { p = (struct sockaddr *) &a6; p_len = sizeof a6; } i64 received = recvfrom( _sockets[slot].socket, data, size, MSG_DONTWAIT, p, &(socklen_t) {p_len} ); if (received < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) return 0; fprintf(stderr, "ERROR: recvfrom failed (errno %d)\n", errno); return 0; } if (remote_address != NULL) { memset(remote_address, 0, sizeof *remote_address); remote_address->protocol = address.protocol; if (address.protocol == IPv4_UDP) { remote_address->port = ntohs(a4.sin_port); remote_address->v4_address_as_u32 = a4.sin_addr.s_addr; } else { remote_address->port = ntohs(a6.sin6_port); memcpy(remote_address->v6_address, a6.sin6_addr.s6_addr, sizeof remote_address->v6_address); } } return received; } i64 p_send(u16 slot, IP_Address address, i64 size, u8 *data, u16 *local_port) { assert(slot < MAX_NUM_SOCKETS); assert(address.protocol == IPv4_UDP || address.protocol == IPv6_UDP); IP_Address local_address = address; local_address.port = 0; if (!sockets_open(slot, local_address, local_port)) return 0; if (size <= 0) return 0; struct sockaddr *p; i32 p_len; struct sockaddr_in a4 = {0}; struct sockaddr_in6 a6 = {0}; if (address.protocol == IPv4_UDP) { p = (struct sockaddr *) &a4; p_len = sizeof a4; a4.sin_family = AF_INET; a4.sin_port = htons(address.port); a4.sin_addr.s_addr = address.v4_address_as_u32; } else { p = (struct sockaddr *) &a6; p_len = sizeof a6; a6.sin6_family = AF_INET6; a6.sin6_port = htons(address.port); memcpy(a6.sin6_addr.s6_addr, address.v6_address, sizeof a6.sin6_addr.s6_addr); } i64 sent = sendto( _sockets[0].socket, data, size, MSG_DONTWAIT, p, p_len ); if (sent < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) return 0; fprintf(stderr, "ERROR: sendto failed (errno %d)\n", errno); return 0; } return sent; } #endif // defined(__unix__) // ================================================================ // // X11 // // ================================================================ #if defined(__linux__) #include #include #include static i16 _key_table[MAX_NUM_KEYS] = {0}; static b8 _key_repeat[MAX_NUM_KEYS] = {0}; static u32 _buffer[MAX_NUM_PIXELS] = {0}; static Input_Key _input[MAX_INPUT_SIZE] = {0}; static c8 _clipboard_buffer[MAX_CLIPBOARD_SIZE] = {0}; static XImage _image = {0}; static Display * _display = NULL; static GC _gc = NULL; static XIM _im = NULL; static XIC _ic = NULL; static Window _window = 0; static Atom _wm_delete_window = 0; static Atom _clipboard = 0; static Atom _targets = 0; static Atom _utf8_string = 0; static Atom _target = None; void p_init(void) { _display = XOpenDisplay(NULL); assert(_display != NULL); _key_table[XKeysymToKeycode(_display, XK_Left)] = KEY_LEFT; _key_table[XKeysymToKeycode(_display, XK_Right)] = KEY_RIGHT; _key_table[XKeysymToKeycode(_display, XK_Up)] = KEY_UP; _key_table[XKeysymToKeycode(_display, XK_Down)] = KEY_DOWN; _key_table[XKeysymToKeycode(_display, XK_1)] = KEY_1; _key_table[XKeysymToKeycode(_display, XK_2)] = KEY_2; _key_table[XKeysymToKeycode(_display, XK_3)] = KEY_3; _key_table[XKeysymToKeycode(_display, XK_4)] = KEY_4; _key_table[XKeysymToKeycode(_display, XK_5)] = KEY_5; _key_table[XKeysymToKeycode(_display, XK_6)] = KEY_6; _key_table[XKeysymToKeycode(_display, XK_7)] = KEY_7; _key_table[XKeysymToKeycode(_display, XK_8)] = KEY_8; _key_table[XKeysymToKeycode(_display, XK_9)] = KEY_9; _key_table[XKeysymToKeycode(_display, XK_0)] = KEY_0; _key_table[XKeysymToKeycode(_display, XK_A)] = KEY_A; _key_table[XKeysymToKeycode(_display, XK_B)] = KEY_B; _key_table[XKeysymToKeycode(_display, XK_C)] = KEY_C; _key_table[XKeysymToKeycode(_display, XK_D)] = KEY_D; _key_table[XKeysymToKeycode(_display, XK_E)] = KEY_E; _key_table[XKeysymToKeycode(_display, XK_F)] = KEY_F; _key_table[XKeysymToKeycode(_display, XK_G)] = KEY_G; _key_table[XKeysymToKeycode(_display, XK_H)] = KEY_H; _key_table[XKeysymToKeycode(_display, XK_I)] = KEY_I; _key_table[XKeysymToKeycode(_display, XK_J)] = KEY_J; _key_table[XKeysymToKeycode(_display, XK_K)] = KEY_K; _key_table[XKeysymToKeycode(_display, XK_L)] = KEY_L; _key_table[XKeysymToKeycode(_display, XK_M)] = KEY_M; _key_table[XKeysymToKeycode(_display, XK_N)] = KEY_N; _key_table[XKeysymToKeycode(_display, XK_O)] = KEY_O; _key_table[XKeysymToKeycode(_display, XK_P)] = KEY_P; _key_table[XKeysymToKeycode(_display, XK_Q)] = KEY_Q; _key_table[XKeysymToKeycode(_display, XK_R)] = KEY_R; _key_table[XKeysymToKeycode(_display, XK_S)] = KEY_S; _key_table[XKeysymToKeycode(_display, XK_T)] = KEY_T; _key_table[XKeysymToKeycode(_display, XK_U)] = KEY_U; _key_table[XKeysymToKeycode(_display, XK_V)] = KEY_V; _key_table[XKeysymToKeycode(_display, XK_W)] = KEY_W; _key_table[XKeysymToKeycode(_display, XK_X)] = KEY_X; _key_table[XKeysymToKeycode(_display, XK_Y)] = KEY_Y; _key_table[XKeysymToKeycode(_display, XK_Z)] = KEY_Z; _key_table[XKeysymToKeycode(_display, XK_space)] = KEY_SPACE; _key_table[XKeysymToKeycode(_display, XK_braceleft)] = KEY_LBRACE; _key_table[XKeysymToKeycode(_display, XK_braceright)] = KEY_RBRACE; _key_table[XKeysymToKeycode(_display, XK_colon)] = KEY_COLON; _key_table[XKeysymToKeycode(_display, XK_quotedbl)] = KEY_QUOTE; _key_table[XKeysymToKeycode(_display, XK_asciitilde)] = KEY_TILDE; _key_table[XKeysymToKeycode(_display, XK_backslash)] = KEY_BACKSLASH; _key_table[XKeysymToKeycode(_display, XK_comma)] = KEY_COMMA; _key_table[XKeysymToKeycode(_display, XK_greater)] = KEY_PERIOD; _key_table[XKeysymToKeycode(_display, XK_question)] = KEY_SLASH; _key_table[XKeysymToKeycode(_display, XK_minus)] = KEY_MINUS; _key_table[XKeysymToKeycode(_display, XK_equal)] = KEY_EQUAL; _key_table[XKeysymToKeycode(_display, XK_F1)] = KEY_F1; _key_table[XKeysymToKeycode(_display, XK_F2)] = KEY_F2; _key_table[XKeysymToKeycode(_display, XK_F3)] = KEY_F3; _key_table[XKeysymToKeycode(_display, XK_F4)] = KEY_F4; _key_table[XKeysymToKeycode(_display, XK_F5)] = KEY_F5; _key_table[XKeysymToKeycode(_display, XK_F6)] = KEY_F6; _key_table[XKeysymToKeycode(_display, XK_F7)] = KEY_F7; _key_table[XKeysymToKeycode(_display, XK_F8)] = KEY_F8; _key_table[XKeysymToKeycode(_display, XK_F9)] = KEY_F9; _key_table[XKeysymToKeycode(_display, XK_F10)] = KEY_F10; _key_table[XKeysymToKeycode(_display, XK_F11)] = KEY_F11; _key_table[XKeysymToKeycode(_display, XK_F12)] = KEY_F12; _key_table[XKeysymToKeycode(_display, XK_Control_L)] = KEY_LCTRL; _key_table[XKeysymToKeycode(_display, XK_Control_R)] = KEY_RCTRL; _key_table[XKeysymToKeycode(_display, XK_Shift_L)] = KEY_LSHIFT; _key_table[XKeysymToKeycode(_display, XK_Shift_R)] = KEY_RSHIFT; _key_table[XKeysymToKeycode(_display, XK_Alt_L)] = KEY_LALT; _key_table[XKeysymToKeycode(_display, XK_Alt_R)] = KEY_RALT; _key_table[XKeysymToKeycode(_display, XK_Meta_L)] = KEY_LMETA; _key_table[XKeysymToKeycode(_display, XK_Meta_R)] = KEY_RMETA; _key_table[XKeysymToKeycode(_display, XK_Escape)] = KEY_ESCAPE; _key_table[XKeysymToKeycode(_display, XK_BackSpace)] = KEY_BACKSPACE; _key_table[XKeysymToKeycode(_display, XK_Tab)] = KEY_TAB; _key_table[XKeysymToKeycode(_display, XK_Return)] = KEY_ENTER; _key_table[XKeysymToKeycode(_display, XK_Print)] = KEY_PRINTSCREEN; _key_table[XKeysymToKeycode(_display, XK_Delete)] = KEY_DELETE; _key_table[XKeysymToKeycode(_display, XK_Pause)] = KEY_PAUSE; _key_table[XKeysymToKeycode(_display, XK_Insert)] = KEY_INSERT; _key_table[XKeysymToKeycode(_display, XK_Home)] = KEY_HOME; _key_table[XKeysymToKeycode(_display, XK_End)] = KEY_END; _key_table[XKeysymToKeycode(_display, XK_Page_Up)] = KEY_PAGEUP; _key_table[XKeysymToKeycode(_display, XK_Page_Down)] = KEY_PAGEDOWN; _key_table[XKeysymToKeycode(_display, XK_KP_0)] = KEY_KP_0; _key_table[XKeysymToKeycode(_display, XK_KP_1)] = KEY_KP_1; _key_table[XKeysymToKeycode(_display, XK_KP_2)] = KEY_KP_2; _key_table[XKeysymToKeycode(_display, XK_KP_3)] = KEY_KP_3; _key_table[XKeysymToKeycode(_display, XK_KP_4)] = KEY_KP_4; _key_table[XKeysymToKeycode(_display, XK_KP_5)] = KEY_KP_5; _key_table[XKeysymToKeycode(_display, XK_KP_6)] = KEY_KP_6; _key_table[XKeysymToKeycode(_display, XK_KP_7)] = KEY_KP_7; _key_table[XKeysymToKeycode(_display, XK_KP_8)] = KEY_KP_8; _key_table[XKeysymToKeycode(_display, XK_KP_9)] = KEY_KP_9; _key_table[XKeysymToKeycode(_display, XK_KP_Enter)] = KEY_KP_ENTER; _key_table[XKeysymToKeycode(_display, XK_KP_Divide)] = KEY_KP_DIVIDE; _key_table[XKeysymToKeycode(_display, XK_KP_Multiply)] = KEY_KP_MULTIPLY; _key_table[XKeysymToKeycode(_display, XK_KP_Add)] = KEY_KP_ADD; _key_table[XKeysymToKeycode(_display, XK_KP_Subtract)] = KEY_KP_SUBTRACT; _key_table[XKeysymToKeycode(_display, XK_KP_Decimal)] = KEY_KP_DECIMAL; _key_table[XKeysymToKeycode(_display, XK_KP_Separator)] = KEY_KP_SEPARATOR; i32 screen = DefaultScreen(_display); i32 depth = DefaultDepth (_display, screen); Visual *visual = DefaultVisual(_display, screen); _gc = DefaultGC(_display, screen); assert(_gc != NULL); XSetGraphicsExposures(_display, _gc, False); i32 _display_width = DisplayWidth (_display, screen); i32 _display_height = DisplayHeight(_display, screen); if (platform.frame_width <= 0) platform.frame_width = 400; if (platform.frame_height <= 0) platform.frame_height = 300; i32 x = (_display_width - platform.frame_width) / 2; i32 y = (_display_height - platform.frame_height) / 2; _window = XCreateWindow(_display, XDefaultRootWindow(_display), x, y, platform.frame_width, platform.frame_height, 0, depth, InputOutput, visual, CWEventMask, &(XSetWindowAttributes) { .event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | VisibilityChangeMask | FocusChangeMask | StructureNotifyMask | SubstructureNotifyMask, }); _im = XOpenIM(_display, NULL, NULL, NULL); assert(_im != NULL); _ic = XCreateIC(_im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, _window, NULL); assert(_ic != NULL); platform.pixels = _buffer; platform.input = _input; platform.clipboard = _clipboard_buffer; _image = (XImage) { .width = platform.frame_width, .height = platform.frame_height, .depth = depth, .xoffset = 0, .format = ZPixmap, .data = (c8 *) _buffer, .byte_order = LSBFirst, .bitmap_unit = 32, .bitmap_bit_order = LSBFirst, .bitmap_pad = 32, .bits_per_pixel = 32, .bytes_per_line = 4 * platform.frame_width, .red_mask = 0xff0000, .green_mask = 0x00ff00, .blue_mask = 0x0000ff, }; XInitImage(&_image); _wm_delete_window = XInternAtom(_display, "WM_DELETE_WINDOW", False); _clipboard = XInternAtom(_display, "CLIPBOARD", False); _targets = XInternAtom(_display, "TARGETS", False); _utf8_string = XInternAtom(_display, "UTF8_STRING", False); XSetICFocus(_ic); XSetWMProtocols(_display, _window, &_wm_delete_window, 1); if (platform.title != NULL) XStoreName(_display, _window, platform.title); XMapWindow(_display, _window); XPutImage(_display, _window, _gc, &_image, 0, 0, 0, 0, platform.frame_width, platform.frame_height); XFlush(_display); } void p_cleanup(void) { if (!platform.graceful_exit) return; if (_window != 0) XDestroyWindow(_display, _window); if (_display != NULL) XCloseDisplay (_display); _display = NULL; _window = 0; sockets_cleanup(); } i32 p_handle_events(void) { i32 num_events = 0; memset(platform.key_pressed, 0, sizeof platform.key_pressed); memset(_key_repeat, 0, sizeof _key_repeat); platform.input_size = 0; platform.cursor_dx = 0; platform.cursor_dy = 0; platform.wheel_dy = 0; XEvent ev; b8 requested_clipboard = 0; while (XEventsQueued(_display, QueuedAlready) > 0) { ++num_events; XNextEvent(_display, &ev); XFilterEvent(&ev, _window); switch (ev.type) { case DestroyNotify: platform.done = 1; break; case MotionNotify: platform.cursor_dx += ev.xmotion.x - platform.cursor_x; platform.cursor_dy += ev.xmotion.y - platform.cursor_y; platform.cursor_x = ev.xmotion.x; platform.cursor_y = ev.xmotion.y; break; case ButtonPress: platform.cursor_x = ev.xbutton.x; platform.cursor_y = ev.xbutton.y; switch (ev.xbutton.button) { case Button1: platform.key_down[BUTTON_LEFT] = 1; platform.key_pressed[BUTTON_LEFT] = 1; break; case Button2: platform.key_down[BUTTON_MIDDLE] = 1; platform.key_pressed[BUTTON_MIDDLE] = 1; break; case Button3: platform.key_down[BUTTON_RIGHT] = 1; platform.key_pressed[BUTTON_RIGHT] = 1; break; case Button4: ++platform.wheel_dy; break; case Button5: --platform.wheel_dy; break; default:; } if (!requested_clipboard) { XConvertSelection(_display, _clipboard, _targets, _clipboard, _window, CurrentTime); requested_clipboard = 1; } break; case ButtonRelease: platform.cursor_x = ev.xbutton.x; platform.cursor_y = ev.xbutton.y; switch (ev.xbutton.button) { case Button1: platform.key_down[BUTTON_LEFT] = 0; break; case Button2: platform.key_down[BUTTON_MIDDLE] = 0; break; case Button3: platform.key_down[BUTTON_RIGHT] = 0; break; default:; } break; case KeyPress: { i16 k = _key_table[ev.xkey.keycode]; platform.cursor_x = ev.xkey.x; platform.cursor_y = ev.xkey.y; platform.key_down[k] = 1; if (!_key_repeat[k]) platform.key_pressed[k] = 1; platform.key_down[MOD_CTRL] = !!(ev.xkey.state & ControlMask); platform.key_down[MOD_SHIFT] = !!(ev.xkey.state & ShiftMask); platform.key_down[MOD_ALT] = !!(ev.xkey.state & Mod1Mask); platform.key_down[MOD_CAPS] = !!(ev.xkey.state & LockMask); platform.key_down[MOD_NUM] = !!(ev.xkey.state & Mod2Mask); platform.key_down[MOD_SCROLL] = !!(ev.xkey.state & Mod3Mask); platform.key_down[MOD_META] = !!(ev.xkey.state & Mod4Mask); if (platform.input_size < MAX_INPUT_SIZE) { if (k < 32 || k >= 128) platform.input[platform.input_size++] = (Input_Key) { .ctrl = !!(ev.xkey.state & ControlMask), .shift = !!(ev.xkey.state & ShiftMask), .alt = !!(ev.xkey.state & Mod1Mask), .caps = !!(ev.xkey.state & LockMask), .num = !!(ev.xkey.state & Mod2Mask), .scroll = !!(ev.xkey.state & Mod3Mask), .meta = !!(ev.xkey.state & Mod4Mask), .key = k, .c = 0, }; else { c8 buf[16]; i32 len = Xutf8LookupString(_ic, &ev.xkey, buf, sizeof buf - 1, NULL, NULL); if (len > 0) platform.input[platform.input_size++] = (Input_Key) { .ctrl = !!(ev.xkey.state & ControlMask), .shift = !!(ev.xkey.state & ShiftMask), .alt = !!(ev.xkey.state & Mod1Mask), .caps = !!(ev.xkey.state & LockMask), .num = !!(ev.xkey.state & Mod2Mask), .scroll = !!(ev.xkey.state & Mod3Mask), .meta = !!(ev.xkey.state & Mod4Mask), .key = k, .c = utf8_read(len, buf), }; } } if (!requested_clipboard) { XConvertSelection(_display, _clipboard, _targets, _clipboard, _window, CurrentTime); requested_clipboard = 1; } } break; case KeyRelease: { i16 k = _key_table[ev.xkey.keycode]; platform.cursor_x = ev.xkey.x; platform.cursor_y = ev.xkey.y; platform.key_down[k] = 0; _key_repeat[k] = 1; platform.key_down[MOD_CTRL] = !!(ev.xkey.state & ControlMask); platform.key_down[MOD_SHIFT] = !!(ev.xkey.state & ShiftMask); platform.key_down[MOD_ALT] = !!(ev.xkey.state & Mod1Mask); platform.key_down[MOD_CAPS] = !!(ev.xkey.state & LockMask); platform.key_down[MOD_NUM] = !!(ev.xkey.state & Mod2Mask); platform.key_down[MOD_SCROLL] = !!(ev.xkey.state & Mod3Mask); platform.key_down[MOD_META] = !!(ev.xkey.state & Mod4Mask); } break; case SelectionRequest: if (ev.xselectionrequest.requestor != _window && ev.xselectionrequest.selection == _clipboard && XGetSelectionOwner(_display, _clipboard) == _window) { if (ev.xselectionrequest.property != None) { if (ev.xselectionrequest.target == _targets) XChangeProperty( ev.xselectionrequest.display, ev.xselectionrequest.requestor, ev.xselectionrequest.property, XA_ATOM, 32, PropModeReplace, (u8 *) &_utf8_string, 1 ); else if (ev.xselectionrequest.target == _utf8_string) XChangeProperty( ev.xselectionrequest.display, ev.xselectionrequest.requestor, ev.xselectionrequest.property, ev.xselectionrequest.target, 8, PropModeReplace, (u8 *) platform.clipboard, platform.clipboard_size ); } XSendEvent(_display, ev.xselectionrequest.requestor, 0, 0, (XEvent *) &(XSelectionEvent) { .type = SelectionNotify, .serial = ev.xselectionrequest.serial, .send_event = ev.xselectionrequest.send_event, .display = ev.xselectionrequest.display, .requestor = ev.xselectionrequest.requestor, .selection = ev.xselectionrequest.selection, .target = ev.xselectionrequest.target, .property = ev.xselectionrequest.property, .time = ev.xselectionrequest.time, }); } break; case SelectionNotify: if (ev.xselection.property != None) { i64 len = 0; u8 *data = NULL; XGetWindowProperty( _display, _window, _clipboard, 0, MAX_CLIPBOARD_SIZE / 4, False, AnyPropertyType, &(Atom) {0}, &(int) {0}, (unsigned long *) &len, &(unsigned long) {0}, &data ); if (ev.xselection.target == _targets) { _target = None; Atom *list = (Atom *) data; for (i64 i = 0; i < len / 4; i++) if (list[i] == XA_STRING) _target = XA_STRING; else if (list[i] == _utf8_string) { _target = _utf8_string; break; } if (_target != None) XConvertSelection(_display, _clipboard, _target, _clipboard, _window, CurrentTime); } else if (ev.xselection.target == _target) { if (len > MAX_CLIPBOARD_SIZE) len = MAX_CLIPBOARD_SIZE; platform.clipboard_size = len; if (len > 0) memcpy(platform.clipboard, data, len); } if (data) XFree(data); } break; case EnterNotify: platform.has_cursor = 1; break; case LeaveNotify: platform.has_cursor = 0; break; case FocusOut: platform.has_focus = 0; break; case FocusIn: platform.has_focus = 1; if (!requested_clipboard) { XConvertSelection(_display, _clipboard, _targets, _clipboard, _window, CurrentTime); requested_clipboard = 1; } break; case MappingNotify: XRefreshKeyboardMapping(&ev.xmapping); break; case ClientMessage: if ((Atom) ev.xclient.data.l[0] == _wm_delete_window) platform.done = 1; break; default:; } } XWindowAttributes attrs; XGetWindowAttributes(_display, _window, &attrs); if ((platform.frame_width != attrs.width || platform.frame_height != attrs.height) && attrs.width * attrs.height * 4 <= (i32) sizeof _buffer) { if (attrs.width > 0 && attrs.height > 0) { _image.width = attrs.width; _image.height = attrs.height; _image.bytes_per_line = 4 * attrs.width; } platform.frame_width = attrs.width; platform.frame_height = attrs.height; } return num_events; } i32 p_wait_events(void) { i32 num_events = 0; do { num_events = p_handle_events(); sched_yield(); } while (num_events == 0); return num_events; } void p_render_frame(void) { if (platform.done) return; XPutImage(_display, _window, _gc, &_image, 0, 0, 0, 0, platform.frame_width, platform.frame_height); XFlush(_display); } void p_clipboard_write(i64 size, c8 *data) { assert(size <= MAX_CLIPBOARD_SIZE); XSetSelectionOwner(_display, _clipboard, _window, CurrentTime); platform.clipboard_size = size < MAX_CLIPBOARD_SIZE ? size : MAX_CLIPBOARD_SIZE; if (platform.clipboard_size > 0) memcpy(platform.clipboard, data, platform.clipboard_size); } #endif // defined(__linux__) // ================================================================ // // ALSA // // ================================================================ #if defined(__linux__) void p_handle_audio(i64 time_elapsed) { (void) time_elapsed; assert(0); } void p_queue_sound(i64 delay, i64 num_samples, f32 *samples) { (void) delay; (void) num_samples; (void) samples; assert(0); } #endif // defined(__linux__) // ================================================================ // // WebAssembly // // ================================================================ #ifdef __wasm__ static u32 _buffer[MAX_NUM_PIXELS] = {0}; static Input_Key _input[MAX_INPUT_SIZE] = {0}; static i32 _num_events = 0; static i32 _input_size = 0; static b8 _key_pressed[MAX_NUM_KEYS] = {0}; i32 p_handle_events(void) { platform.input_size = _input_size; _input_size = 0; for (i64 i = 0; i < MAX_NUM_KEYS; ++i) platform.key_pressed[i] = _key_pressed[i]; for (i64 i = 0; i < MAX_NUM_KEYS; ++i) _key_pressed[i] = 0; i32 n = _num_events; _num_events = 0; return n; } i32 p_wait_events_impl(void); i32 p_wait_events(void) { p_wait_events_impl(); return p_handle_events(); } void p_render_frame_impl(void); void p_render_frame(void) { // Make canvas pixels opaque. i64 size = platform.frame_width * platform.frame_height; for (i64 i = 0; i < size; ++i) platform.pixels[i] |= 0xff000000; p_render_frame_impl(); } __attribute__((export_name("js_memset"))) void js_memset(void *dst, i32 val, u32 num) { if (dst == NULL) return; for (u32 i = 0; i < num; ++i) ((u8 *) dst)[i] = (u8) val; } __attribute__((export_name("js_memcpy"))) void js_memcpy(void *dst, void const *src, u32 num) { if (dst == NULL || src == NULL) return; for (u32 i = 0; i < num; ++i) ((u8 *) dst)[i] = ((u8 const *) src)[i]; } __attribute__((export_name("js_main"))) void js_main(c8 *href) { main(1, &href); } __attribute__((export_name("js_init"))) void js_init(void) { platform.pixels = _buffer; platform.input = _input; platform.done = 1; } __attribute__((export_name("js_title"))) void *js_title(void) { return platform.title; } __attribute__((export_name("js_pixels"))) void *js_pixels(void) { return platform.pixels; } __attribute__((export_name("js_frame"))) void js_frame(i32 frame_width, i32 frame_height) { if (platform.frame_width != frame_width || platform.frame_height != frame_height) { ++_num_events; platform.frame_width = frame_width; platform.frame_height = frame_height; } platform.done = 0; update_and_render_frame(); } __attribute__((export_name("js_mousemove"))) void js_mousemove(i32 x, i32 y) { ++_num_events; platform.cursor_dx = x - platform.cursor_x; platform.cursor_dy = y - platform.cursor_y; platform.cursor_x = x; platform.cursor_y = y; } __attribute__((export_name("js_mousedown"))) void js_mousedown(u32 buttons) { ++_num_events; if ((buttons & 1) && !platform.key_down[BUTTON_LEFT]) _key_pressed[BUTTON_LEFT] = 1; if ((buttons & 2) && !platform.key_down[BUTTON_RIGHT]) _key_pressed[BUTTON_RIGHT] = 1; if ((buttons & 4) && !platform.key_down[BUTTON_MIDDLE]) _key_pressed[BUTTON_MIDDLE] = 1; if (buttons & 1) platform.key_down[BUTTON_LEFT] = 1; if (buttons & 2) platform.key_down[BUTTON_RIGHT] = 1; if (buttons & 4) platform.key_down[BUTTON_MIDDLE] = 1; } __attribute__((export_name("js_mouseup"))) void js_mouseup(u32 buttons) { ++_num_events; if (!(buttons & 1)) platform.key_down[BUTTON_LEFT] = 0; if (!(buttons & 2)) platform.key_down[BUTTON_RIGHT] = 0; if (!(buttons & 4)) platform.key_down[BUTTON_MIDDLE] = 0; } __attribute__((export_name("js_keydown"))) void js_keydown(u32 key, u32 mod) { ++_num_events; _key_pressed[key] = 1; platform.key_down[key] = 1; if (platform.input_size < MAX_INPUT_SIZE) platform.input[_input_size++] = (Input_Key) { .key = key, .c = key, }; } __attribute__((export_name("js_keyup"))) void js_keyup(u32 key, u32 mod) { ++_num_events; platform.key_down[key] = 0; } #endif // __wasm__ // ================================================================ #endif // REDUCED_SYSTEM_LAYER_IMPL_GUARD_ #endif // REDUCED_SYSTEM_LAYER_HEADER