#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. #/ #/ ---------------------------------------------------------------- #/ #/ To-Do list #/ #/ - Examples #/ - Conway's Game of Life #/ - Julia Set #/ - Labyrinth #/ - Chat #/ - Window #/ - Wayland #/ - Windows graphics #/ - Drop files #/ - Sound #/ - Windows audio #/ - Recording #/ - Networking #/ - Windows sockets #/ - Graphics #/ - UI #/ - Icons #/ - Vector math #/ - Anti-aliasing #/ - Logging #/ - Test suite #/ - Long term #/ - Allocator #/ - Parsing #/ - Printing #/ - File system #/ - Secure random #/ - Process #/ - Shared memory #/ - Shared mutex #/ - Stackless coroutines #/ - Big integer #/ - Mersenne Twister 64 #/ - Arithmetic coding #/ - Threads - https://nachtimwald.com/2019/04/05/cross-platform-thread-wrapper #/ - Web sockets #/ - HTTP #/ - Cryptography - https://github.com/jedisct1/supercop #/ #/ Done #/ #/ - Examples #/ - Echo #/ - UI #/ - Particles #/ - Graph #/ - Sine Wave #/ - Window #/ - X11 #/ - WebAssembly #/ - Sound #/ - ALSA #/ - WebAssembly audio #/ - Networking #/ - Unix UDP sockets #/ - UTF-8 #/ - Testing #/ - Graphics #/ - Font #/ - Adaptive resolution #/ - Oklab color #/ - Relative coordinates #/ - Blending #/ - Self-contained impl #/ #/ ---------------------------------------------------------------- #/ #/ (C) 2025 Mitya Selivanov #/ #/ ================================================================ #/ #/ 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 -lasound \ -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 unsigned char b8; typedef float f32; typedef double f64; typedef struct { f64 x, y; } vec2; typedef struct { f64 x, y, z; } vec3; typedef struct { f64 x, y, z, w; } vec4; typedef struct { f32 x, y; } vec2_f32; typedef struct { f32 x, y, z; } vec3_f32; typedef struct { f32 x, y, z, w; } vec4_f32; typedef struct { i64 x, y; } vec2_i64; typedef struct { i64 x, y, z; } vec3_i64; typedef struct { i64 x, y, z, w; } vec4_i64; typedef struct { f64 v[ 4]; } mat2; typedef struct { f64 v[ 9]; } mat3; typedef struct { f64 v[16]; } mat4; #endif // TYPES_HEADER_GUARD_ // ================================================================ // // Basic declarations // // ================================================================ #ifndef REDUCED_SYSTEM_LAYER_HEADER_GUARD_ #define REDUCED_SYSTEM_LAYER_HEADER_GUARD_ // ================================================================ // // Options // // ================================================================ #ifndef MAX_NUM_PIXELS #define MAX_NUM_PIXELS (10 * 1024 * 1024) #endif #ifndef MAX_INPUT_SIZE #define MAX_INPUT_SIZE 256 #endif #ifndef MAX_CLIPBOARD_SIZE #define MAX_CLIPBOARD_SIZE (10 * 1024 * 1024) #endif #ifndef MAX_NUM_SOCKETS #if defined(__wasm__) #define MAX_NUM_SOCKETS 0 #else #define MAX_NUM_SOCKETS 64 #endif #endif #ifndef MAX_NUM_KEYS #define MAX_NUM_KEYS 512 #endif #ifndef MIN_PIXEL_SIZE #define MIN_PIXEL_SIZE 1 #endif #ifndef MAX_PIXEL_SIZE #define MAX_PIXEL_SIZE 16 #endif #ifndef DEFAULT_PIXEL_SIZE #if defined(__wasm__) #define DEFAULT_PIXEL_SIZE 3 #else #define DEFAULT_PIXEL_SIZE 1 #endif #endif #ifndef MIN_FRAME_DURATION #define MIN_FRAME_DURATION 9 #endif #ifndef MAX_FRAME_DURATION #if defined(__wasm__) #define MAX_FRAME_DURATION 83 #else #define MAX_FRAME_DURATION 41 #endif #endif #ifndef NUM_SOUND_CHANNELS #define NUM_SOUND_CHANNELS 2 #endif #ifndef SOUND_SAMPLE_RATE #define SOUND_SAMPLE_RATE 44100 #endif #ifndef SOUND_AVAIL_MIN #define SOUND_AVAIL_MIN 512 #endif #ifndef MAX_NUM_SOUND_FRAMES #define MAX_NUM_SOUND_FRAMES (10 * SOUND_SAMPLE_RATE * NUM_SOUND_CHANNELS) #endif #ifndef DROP_FILE_BUFFER_SIZE #define DROP_FILE_BUFFER_SIZE (20 * 1024 * 1024) #endif // ================================================================ // // PLATFORM API // // ================================================================ #if !defined(__wasm__) #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #endif #ifdef __cplusplus extern "C" { #endif enum { IPv4_UDP = 1, IPv6_UDP = 2, }; enum { 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 { i64 name_size; c8 *name; i64 data_size; u8 *data; } Drop_File; typedef struct { c8 *title; i32 frame_width; i32 frame_height; b8 exact_resolution; b8 graceful_exit; b8 done; b8 has_focus; b8 has_cursor; b8 files_dropped; i32 real_width; i32 real_height; i32 pixel_size; i64 input_size; i64 clipboard_size; i32 cursor_x; i32 cursor_y; i32 cursor_dx; i32 cursor_dy; i64 wheel_dx; i64 wheel_dy; i64 sound_clock_time; i64 sound_clock_carry; i64 num_sound_samples_elapsed; i64 num_drop_files; Drop_File *drop_files; vec4_f32 pixels [MAX_NUM_PIXELS]; f32 sound_ring [MAX_NUM_SOUND_FRAMES]; Input_Key input [MAX_INPUT_SIZE]; c8 clipboard [MAX_CLIPBOARD_SIZE]; b8 key_down [MAX_NUM_KEYS]; b8 key_pressed [MAX_NUM_KEYS]; u8 drop_buffer [DROP_FILE_BUFFER_SIZE]; } 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); // Color u32 rgb_u32_from_f32(vec3_f32 c); vec3_f32 rgb_f32_from_u32(u32 color); // 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_sound(void); void p_queue_sound(i64 delay_in_samples, i64 num_samples, f32 *frames); // 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 g_platform; #if defined(__wasm__) i32 main(i32 argc, c8 **argv); f64 floor(f64 x); f64 ceil(f64 x); f64 sqrt(f64 x); f64 cbrt(f64 x); f64 pow(f64 x, f64 y); f64 log(f64 x); f64 log2(f64 x); f64 log10(f64 x); f64 exp(f64 x); f64 sin(f64 x); f64 cos(f64 x); f64 tan(f64 x); f64 asin(f64 x); f64 acos(f64 x); f64 atan(f64 x); f64 atan2(f64 y, f64 x); #endif // defined(__wasm__) #ifndef NULL #define NULL ((void *) 0) #endif #ifndef M_PI #define M_PI (3.14159265358979323846) #endif #ifndef M_E #define M_E (2.71828182845904523536) #endif #ifndef M_LOG_2 #define M_LOG_2 (0.69314718055994530941) #endif #ifndef M_LOG_10 #define M_LOG_10 (2.30258509299404568402) #endif #ifndef M_LOG2_E #define M_LOG2_E (1.44269504088896340736) #endif #ifndef M_LOG10_E #define M_LOG10_E (0.43429448190325182765) #endif #ifndef M_LOG_PI #define M_LOG_PI (1.14472988584940017414) #endif #ifndef M_SQRT_2 #define M_SQRT_2 (1.41421356237309504880) #endif #ifndef M_SQRT_3 #define M_SQRT_3 (1.73205080756887729352) #endif #ifndef M_SQRT_PI #define M_SQRT_PI (1.77245385090551602730) #endif #ifndef M_SQRT_E #define M_SQRT_E (1.64872127070012814685) #endif #ifndef M_CBRT_2 #define M_CBRT_2 (1.25992104989487316476) #endif #ifndef M_CBRT_3 #define M_CBRT_3 (1.44224957030740838232) #endif #ifndef M_CBRT_PI #define M_CBRT_PI (1.46459188756152326302) #endif #ifndef M_CBRT_E #define M_CBRT_E (1.39561242508608952863) #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 = g_platform.frame_width / 2; i64 h = g_platform.frame_height / 2; i64 x = w / 2; i64 y = h / 2; for (i64 j = 0; j < g_platform.frame_height; ++j) for (i64 i = 0; i < g_platform.frame_width; ++i) if (i < x || i >= x + w || j < y || j >= y + h) g_platform.pixels[j * g_platform.frame_width + i] = (vec4_f32) { .8f, .8f, .8f, 1.f }; else g_platform.pixels[j * g_platform.frame_width + i] = (vec4_f32) { .27f, .21f, .24f, 1.f }; p_render_frame(); p_sleep_for(0); } i32 main(i32 argc, c8 **argv) { (void) argc; (void) argv; g_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 g_platform = {0}; void p_event_loop(void) { p_init(); #if !defined(__wasm__) while (!g_platform.done) update_and_render_frame(); p_cleanup(); #endif } // ================================================================ // // Utilities // // ================================================================ // TEMP #if defined(__wasm__) #define LOG_ERROR(...) do { } while (0) #else #include #define LOG_ERROR(...) \ do { \ fprintf(stderr, "%s:%d, %s: ", __FILE__, __LINE__, __func__); \ fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, "\n"); \ } while (0) #endif 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; } u32 rgb_u32_from_f32(vec3_f32 c) { i32 ir = (i32) floor(c.x * 255. + .5); i32 ig = (i32) floor(c.y * 255. + .5); i32 ib = (i32) floor(c.z * 255. + .5); u32 r = ir < 0 ? 0u : ir > 255 ? 255u : (u32) ir; u32 g = ig < 0 ? 0u : ig > 255 ? 255u : (u32) ig; u32 b = ib < 0 ? 0u : ib > 255 ? 255u : (u32) ib; return (r << 16) | (g << 8) | b; } vec3_f32 rgb_f32_from_u32(u32 color) { return (vec3_f32) { .x = (f32) ((color >> 16) & 0xff) / 255., .y = (f32) ((color >> 8) & 0xff) / 255., .z = (f32) ( color & 0xff) / 255., }; } static i64 sound_samples_elapsed_(void) { if (g_platform.sound_clock_time == 0) { g_platform.sound_clock_time = p_time(); g_platform.sound_clock_carry = 0; return 0; } i64 time_elapsed = p_time() - g_platform.sound_clock_time; i64 delta = time_elapsed * SOUND_SAMPLE_RATE + g_platform.sound_clock_carry; i64 num_samples = delta / 1000; g_platform.sound_clock_time += time_elapsed; g_platform.sound_clock_carry = delta % 1000; return num_samples; } // TEMP #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-function" static void drop_files_clean_(void) { g_platform.num_drop_files = 0; g_platform.drop_files = NULL; } static i64 align_size_(i64 x) { return (x + 7) & ~7ll; } static i64 drop_files_data_size_(i64 num) { if (num > g_platform.num_drop_files) { LOG_ERROR("Sanity"); return 0; } if (num > 0 && g_platform.drop_files == NULL) { LOG_ERROR("Sanity"); return 0; } i64 data_size = 0; for (i64 i = 0; i < num; ++i) data_size += align_size_(g_platform.drop_files[i].name_size) + align_size_(g_platform.drop_files[i].data_size); return data_size; } static void drop_files_set_num_(i64 num) { if (num <= g_platform.num_drop_files) { LOG_ERROR("Sanity"); return; } i64 available = DROP_FILE_BUFFER_SIZE - drop_files_data_size_(g_platform.num_drop_files); if (available < num * (i64) sizeof(Drop_File)) return; i64 src_size = (i64) sizeof(Drop_File) * g_platform.num_drop_files; i64 dst_size = (i64) sizeof(Drop_File) * num; u8 *src = g_platform.drop_buffer + (DROP_FILE_BUFFER_SIZE - src_size); u8 *dst = g_platform.drop_buffer + (DROP_FILE_BUFFER_SIZE - num * (i64) sizeof(Drop_File)); for (i64 i = 0; i < src_size; ++i) dst[i] = src[i]; for (i64 i = src_size; i < dst_size; ++i) dst[i] = 0; g_platform.num_drop_files = num; g_platform.drop_files = (Drop_File *) dst; } static void drop_files_set_name_(i64 index, i64 name_size, c8 *name) { if (g_platform.drop_files == NULL || index + 1 != g_platform.num_drop_files) { LOG_ERROR("Sanity"); return; } i64 offset = drop_files_data_size_(index); i64 available = DROP_FILE_BUFFER_SIZE - offset - g_platform.num_drop_files * sizeof(Drop_File); if (available < name_size + 1) { LOG_ERROR("Sanity"); return; } g_platform.drop_files[index].name_size = name_size + 1; g_platform.drop_files[index].name = (c8 *) g_platform.drop_buffer + offset; for (i64 i = 0; i < name_size; ++i) g_platform.drop_files[index].name[i] = name[i]; g_platform.drop_files[index].name[name_size] = '\0'; } static void drop_files_set_data_(i64 index, i64 data_size, u8 *data) { if (g_platform.drop_files == NULL || index + 1 != g_platform.num_drop_files) { LOG_ERROR("Sanity"); return; } g_platform.drop_files[index].data_size = 0; i64 offset = drop_files_data_size_(index + 1); i64 available = DROP_FILE_BUFFER_SIZE - offset - g_platform.num_drop_files * sizeof(Drop_File); if (available < data_size) { LOG_ERROR("Sanity"); return; } g_platform.drop_files[index].data_size = data_size; g_platform.drop_files[index].data = g_platform.drop_buffer + offset; for (i64 i = 0; i < data_size; ++i) g_platform.drop_files[index].data[i] = data[i]; } #pragma GCC diagnostic pop // ================================================================ // // 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 typedef struct { b8 ready; i32 socket; u16 local_port; IP_Address address; } Socket_Slot; static b8 _sockets_ready = 0; static Socket_Slot _sockets[MAX_NUM_SOCKETS] = {0}; static void sockets_init(void) { if (_sockets_ready) return; signal(SIGPIPE, SIG_IGN); _sockets_ready = 1; } static 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; } } static b8 sockets_open(u16 slot, IP_Address address, u16 *local_port) { sockets_init(); 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) { LOG_ERROR("socket failed (errno %d)", 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); LOG_ERROR("bind failed (errno %d)", errno); return 0; } if (getsockname(_sockets[slot].socket, p, &(socklen_t) {p_len}) == -1) { close(_sockets[slot].socket); LOG_ERROR("getsockname failed (errno %d)", 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) { if (slot >= MAX_NUM_SOCKETS) { LOG_ERROR("Invalid slot %d.", (i32) (u32) slot); return 0; } if (address.protocol != IPv4_UDP && address.protocol != IPv6_UDP) { LOG_ERROR("Invalid address protocol %d.", (i32) (u32) address.protocol); return 0; } 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; LOG_ERROR("recvfrom failed (errno %d).", 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) { if (slot >= MAX_NUM_SOCKETS) { LOG_ERROR("Invalid slot %d.", (i32) (u32) slot); return 0; } if (address.protocol != IPv4_UDP && address.protocol != IPv6_UDP) { LOG_ERROR("Invalid address protocol %d.", (i32) (u32) address.protocol); return 0; } 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; LOG_ERROR("sendto failed (errno %d).", errno); return 0; } return sent; } #endif // defined(__unix__) // ================================================================ // // ALSA // // ================================================================ #if defined(__linux__) #include static b8 _sound_ready = 0; static i64 _sound_position = 0; static snd_pcm_t *_sound_out = NULL; static void sound_init(void) { if (_sound_ready) return; _sound_position = 0; i32 s; s = snd_pcm_open(&_sound_out, "default", SND_PCM_STREAM_PLAYBACK, 0); if (s < 0) { LOG_ERROR("snd_pcm_open failed: %s", snd_strerror(s)); return; } snd_pcm_hw_params_t *hw_params; snd_pcm_sw_params_t *sw_params; snd_pcm_hw_params_alloca(&hw_params); s = snd_pcm_hw_params_any(_sound_out, hw_params); if (s < 0) LOG_ERROR("snd_pcm_hw_params_any failed: %s", snd_strerror(s)); s = snd_pcm_hw_params_set_access(_sound_out, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); if (s < 0) LOG_ERROR("snd_pcm_hw_params_set_access failed: %s", snd_strerror(s)); s = snd_pcm_hw_params_set_format(_sound_out, hw_params, SND_PCM_FORMAT_FLOAT_LE); if (s < 0) LOG_ERROR("snd_pcm_hw_params_set_format failed: %s", snd_strerror(s)); s = snd_pcm_hw_params_set_rate(_sound_out, hw_params, SOUND_SAMPLE_RATE, 0); if (s < 0) LOG_ERROR("snd_pcm_hw_params_set_rate failed: %s", snd_strerror(s)); s = snd_pcm_hw_params_set_channels(_sound_out, hw_params, NUM_SOUND_CHANNELS); if (s < 0) LOG_ERROR("snd_pcm_hw_params_set_channels failed: %s", snd_strerror(s)); s = snd_pcm_hw_params(_sound_out, hw_params); if (s < 0) LOG_ERROR("snd_pcm_hw_params failed: %s", snd_strerror(s)); snd_pcm_sw_params_alloca(&sw_params); s = snd_pcm_sw_params_current(_sound_out, sw_params); if (s < 0) LOG_ERROR("snd_pcm_sw_params_current failed: %s", snd_strerror(s)); s = snd_pcm_sw_params_set_avail_min(_sound_out, sw_params, SOUND_AVAIL_MIN); if (s < 0) LOG_ERROR("snd_pcm_sw_params_set_avail_min failed: %s", snd_strerror(s)); s = snd_pcm_sw_params(_sound_out, sw_params); if (s < 0) LOG_ERROR("snd_pcm_sw_params failed: %s", snd_strerror(s)); s = snd_pcm_prepare(_sound_out); if (s < 0) LOG_ERROR("snd_pcm_prepare failed: %s", snd_strerror(s)); _sound_ready = 1; } static void sound_cleanup(void) { if (!_sound_ready) return; i32 s; s = snd_pcm_nonblock(_sound_out, 0); if (s < 0) LOG_ERROR("snd_pcm_nonblock failed: %s", snd_strerror(s)); s = snd_pcm_drain(_sound_out); if (s < 0) LOG_ERROR("snd_pcm_drain failed: %s", snd_strerror(s)); // FIXME Memory leaks, seems to be an ALSA bug. // snd_pcm_close(_sound_out); // snd_config_update_free_global(); _sound_ready = 0; } void p_handle_sound(void) { sound_init(); g_platform.num_sound_samples_elapsed = sound_samples_elapsed_(); i64 num_frames = g_platform.num_sound_samples_elapsed * NUM_SOUND_CHANNELS; if (num_frames > MAX_NUM_SOUND_FRAMES) { LOG_ERROR("Sound buffer overflow."); num_frames = MAX_NUM_SOUND_FRAMES; } i32 s; if (num_frames <= MAX_NUM_SOUND_FRAMES - _sound_position) { s = snd_pcm_writei(_sound_out, g_platform.sound_ring + _sound_position, num_frames / NUM_SOUND_CHANNELS); if (s < 0) LOG_ERROR("snd_pcm_writei failed: %s", snd_strerror(s)); memset(g_platform.sound_ring + _sound_position, 0, num_frames * sizeof *g_platform.sound_ring); } else { i64 part_one = MAX_NUM_SOUND_FRAMES - _sound_position; i64 part_two = num_frames - part_one; s = snd_pcm_writei(_sound_out, g_platform.sound_ring + _sound_position, part_one / NUM_SOUND_CHANNELS); if (s < 0) LOG_ERROR("snd_pcm_writei failed: %s", snd_strerror(s)); s = snd_pcm_writei(_sound_out, g_platform.sound_ring, part_two / NUM_SOUND_CHANNELS); if (s < 0) LOG_ERROR("snd_pcm_writei failed: %s", snd_strerror(s)); memset(g_platform.sound_ring + _sound_position, 0, part_one * sizeof *g_platform.sound_ring); memset(g_platform.sound_ring, 0, part_two * sizeof *g_platform.sound_ring); } _sound_position = (_sound_position + num_frames) % MAX_NUM_SOUND_FRAMES; } void p_queue_sound(i64 delay_in_samples, i64 num_samples, f32 *frames) { if (num_samples < 0) LOG_ERROR("Invalid num samples %lld.", num_samples); if (frames == NULL) return; if (delay_in_samples < 0) { frames += -delay_in_samples * NUM_SOUND_CHANNELS; num_samples -= delay_in_samples; delay_in_samples = 0; } if (num_samples <= 0) return; i64 num_frames = num_samples * NUM_SOUND_CHANNELS; if (num_frames > MAX_NUM_SOUND_FRAMES) { LOG_ERROR("Sound buffer overflow."); return; } sound_init(); i64 begin = (_sound_position + delay_in_samples) % MAX_NUM_SOUND_FRAMES; if (num_frames <= MAX_NUM_SOUND_FRAMES - begin) for (i64 i = 0; i < num_frames; ++i) g_platform.sound_ring[begin + i] += frames[i]; else { i64 part_one = MAX_NUM_SOUND_FRAMES - begin; i64 part_two = num_frames - part_one; for (i64 i = 0; i < part_one; ++i) g_platform.sound_ring[begin + i] += frames[i]; for (i64 i = 0; i < part_two; ++i) g_platform.sound_ring[i] += frames[part_one + i]; } } #endif // defined(__linux__) // ================================================================ // // X11 // // ================================================================ #if defined(__linux__) #include #include #include static i64 _frame_time = 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; static i16 _key_table [MAX_NUM_KEYS] = {0}; static b8 _key_repeat [MAX_NUM_KEYS] = {0}; static u32 _pixels_scaled [MAX_NUM_PIXELS] = {0}; static u32 _pixels_internal [MAX_NUM_PIXELS] = {0}; static c8 _clipboard_buffer [MAX_CLIPBOARD_SIZE] = {0}; void p_init(void) { _display = XOpenDisplay(NULL); if (_display == NULL) { LOG_ERROR("XOpenDisplay failed."); return; } _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); if (_gc == NULL) { LOG_ERROR("DefaultGC failed."); return; } XSetGraphicsExposures(_display, _gc, False); i32 display_width = DisplayWidth (_display, screen); i32 display_height = DisplayHeight(_display, screen); if (g_platform.frame_width <= 0 || g_platform.frame_height <= 0) { if (display_width >= 1680 && display_height >= 1020) { g_platform.frame_width = 1280; g_platform.frame_height = 720; } else if (display_width >= 800 && display_height >= 600) { g_platform.frame_width = display_width - 400; g_platform.frame_height = display_height - 300; } else if (display_width >= 400 && display_height >= 300) { g_platform.frame_width = 400; g_platform.frame_height = 300; } else { g_platform.frame_width = display_width; g_platform.frame_height = display_height; } } i32 x = (display_width - g_platform.frame_width) / 2; i32 y = (display_height - g_platform.frame_height) / 2; _window = XCreateWindow(_display, XDefaultRootWindow(_display), x, y, g_platform.frame_width, g_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); if (_im == NULL) { LOG_ERROR("XOpenIM failed."); return; } _ic = XCreateIC(_im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, _window, NULL); if (_ic == NULL) { LOG_ERROR("XCreateIC failed."); return; } _image = (XImage) { .width = g_platform.frame_width, .height = g_platform.frame_height, .depth = depth, .xoffset = 0, .format = ZPixmap, .data = (c8 *) _pixels_internal, .byte_order = LSBFirst, .bitmap_unit = 32, .bitmap_bit_order = LSBFirst, .bitmap_pad = 32, .bits_per_pixel = 32, .bytes_per_line = _image.width * 4, .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 (g_platform.title != NULL) XStoreName(_display, _window, g_platform.title); XMapWindow(_display, _window); XPutImage(_display, _window, _gc, &_image, 0, 0, 0, 0, _image.width, _image.height); XFlush(_display); } void p_cleanup(void) { if (!g_platform.graceful_exit) return; if (_window != 0) XDestroyWindow(_display, _window); if (_display != NULL) XCloseDisplay (_display); _display = NULL; _window = 0; sockets_cleanup(); sound_cleanup(); } i32 p_handle_events(void) { if (_display == NULL) return 0; _frame_time = p_time(); i32 num_events = 0; memset(g_platform.key_pressed, 0, sizeof g_platform.key_pressed); memset(_key_repeat, 0, sizeof _key_repeat); g_platform.input_size = 0; g_platform.cursor_dx = 0; g_platform.cursor_dy = 0; g_platform.wheel_dx = 0; g_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: g_platform.done = 1; break; case MotionNotify: g_platform.cursor_dx += ev.xmotion.x - g_platform.cursor_x; g_platform.cursor_dy += ev.xmotion.y - g_platform.cursor_y; g_platform.cursor_x = ev.xmotion.x; g_platform.cursor_y = ev.xmotion.y; break; case ButtonPress: g_platform.cursor_x = ev.xbutton.x; g_platform.cursor_y = ev.xbutton.y; switch (ev.xbutton.button) { case Button1: g_platform.key_down [BUTTON_LEFT] = 1; g_platform.key_pressed[BUTTON_LEFT] = 1; break; case Button2: g_platform.key_down [BUTTON_MIDDLE] = 1; g_platform.key_pressed[BUTTON_MIDDLE] = 1; break; case Button3: g_platform.key_down [BUTTON_RIGHT] = 1; g_platform.key_pressed[BUTTON_RIGHT] = 1; break; case Button4: ++g_platform.wheel_dy; break; case Button5: --g_platform.wheel_dy; break; case Button5 + 1: --g_platform.wheel_dx; break; case Button5 + 2: ++g_platform.wheel_dx; break; default:; } if (!requested_clipboard) { XConvertSelection(_display, _clipboard, _targets, _clipboard, _window, CurrentTime); requested_clipboard = 1; } break; case ButtonRelease: g_platform.cursor_x = ev.xbutton.x; g_platform.cursor_y = ev.xbutton.y; switch (ev.xbutton.button) { case Button1: g_platform.key_down[BUTTON_LEFT] = 0; break; case Button2: g_platform.key_down[BUTTON_MIDDLE] = 0; break; case Button3: g_platform.key_down[BUTTON_RIGHT] = 0; break; default:; } break; case KeyPress: { i16 k = _key_table[ev.xkey.keycode]; g_platform.cursor_x = ev.xkey.x; g_platform.cursor_y = ev.xkey.y; g_platform.key_down[k] = 1; if (!_key_repeat[k]) g_platform.key_pressed[k] = 1; g_platform.key_down[MOD_CTRL] = !!(ev.xkey.state & ControlMask); g_platform.key_down[MOD_SHIFT] = !!(ev.xkey.state & ShiftMask); g_platform.key_down[MOD_ALT] = !!(ev.xkey.state & Mod1Mask); g_platform.key_down[MOD_CAPS] = !!(ev.xkey.state & LockMask); g_platform.key_down[MOD_NUM] = !!(ev.xkey.state & Mod2Mask); g_platform.key_down[MOD_SCROLL] = !!(ev.xkey.state & Mod3Mask); g_platform.key_down[MOD_META] = !!(ev.xkey.state & Mod4Mask); if (g_platform.input_size < MAX_INPUT_SIZE) { if (k < 32 || k >= 128) g_platform.input[g_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) g_platform.input[g_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]; g_platform.cursor_x = ev.xkey.x; g_platform.cursor_y = ev.xkey.y; g_platform.key_down[k] = 0; _key_repeat[k] = 1; g_platform.key_down[MOD_CTRL] = !!(ev.xkey.state & ControlMask); g_platform.key_down[MOD_SHIFT] = !!(ev.xkey.state & ShiftMask); g_platform.key_down[MOD_ALT] = !!(ev.xkey.state & Mod1Mask); g_platform.key_down[MOD_CAPS] = !!(ev.xkey.state & LockMask); g_platform.key_down[MOD_NUM] = !!(ev.xkey.state & Mod2Mask); g_platform.key_down[MOD_SCROLL] = !!(ev.xkey.state & Mod3Mask); g_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 *) g_platform.clipboard, g_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; g_platform.clipboard_size = len; if (len > 0) memcpy(g_platform.clipboard, data, len); } if (data) XFree(data); } break; case EnterNotify: g_platform.has_cursor = 1; break; case LeaveNotify: g_platform.has_cursor = 0; break; case FocusOut: g_platform.has_focus = 0; break; case FocusIn: g_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) g_platform.done = 1; break; default:; } } XWindowAttributes attrs; XGetWindowAttributes(_display, _window, &attrs); if (attrs.width > 0 && attrs.height > 0 && attrs.width * attrs.height <= MAX_NUM_PIXELS) { _image.width = attrs.width; _image.height = attrs.height; _image.bytes_per_line = _image.width * 4; } i32 width = _image.width; i32 height = _image.height; if (g_platform.pixel_size <= 0) g_platform.pixel_size = DEFAULT_PIXEL_SIZE; if (g_platform.pixel_size <= MIN_PIXEL_SIZE) g_platform.pixel_size = MIN_PIXEL_SIZE; if (g_platform.pixel_size > 1) { width = (i32) floor(((f64) _image.width) / g_platform.pixel_size + .5); height = (i32) floor(((f64) _image.height) / g_platform.pixel_size + .5); } if (g_platform.real_width != _image.width || g_platform.real_height != _image.height) { ++num_events; g_platform.real_width = _image.width; g_platform.real_height = _image.height; } if (g_platform.frame_width != width || g_platform.frame_height != height) { ++num_events; g_platform.frame_width = width; g_platform.frame_height = height; } return num_events; } i32 p_wait_events(void) { i32 num_events = 0; for (;;) { num_events = p_handle_events(); if (num_events != 0) break; usleep(0); } return num_events; } void p_render_frame(void) { if (g_platform.done) return; if (g_platform.frame_width == _image.width && g_platform.frame_height == _image.height) { i64 size = g_platform.frame_width * g_platform.frame_height; for (i64 i = 0; i < size; ++i) _pixels_internal[i] = rgb_u32_from_f32((vec3_f32) { g_platform.pixels[i].x, g_platform.pixels[i].y, g_platform.pixels[i].z }); } else { i64 size = g_platform.frame_width * g_platform.frame_height; for (i64 i = 0; i < size; ++i) _pixels_scaled[i] = rgb_u32_from_f32((vec3_f32) { g_platform.pixels[i].x, g_platform.pixels[i].y, g_platform.pixels[i].z }); for (i64 j = 0; j < _image.height; ++j) { i64 j0 = (j * g_platform.frame_height) / _image.height; for (i64 i = 0; i < _image.width; ++i) { i64 i0 = (i * g_platform.frame_width) / _image.width; _pixels_internal[j * _image.width + i] = _pixels_scaled[j0 * g_platform.frame_width + i0]; } } } XPutImage(_display, _window, _gc, &_image, 0, 0, 0, 0, _image.width, _image.height); XFlush(_display); if (!g_platform.exact_resolution) { i64 frame_duration = p_time() - _frame_time; if (g_platform.pixel_size < MAX_PIXEL_SIZE && frame_duration > MAX_FRAME_DURATION) ++g_platform.pixel_size; else if (g_platform.pixel_size > MIN_PIXEL_SIZE && frame_duration < MIN_FRAME_DURATION) --g_platform.pixel_size; } } void p_clipboard_write(i64 size, c8 *data) { if (size > MAX_CLIPBOARD_SIZE) { LOG_ERROR("Size is too big %lld.", size); return; } XSetSelectionOwner(_display, _clipboard, _window, CurrentTime); g_platform.clipboard_size = size < MAX_CLIPBOARD_SIZE ? size : MAX_CLIPBOARD_SIZE; if (g_platform.clipboard_size > 0) memcpy(g_platform.clipboard, data, g_platform.clipboard_size); } #endif // defined(__linux__) // ================================================================ // // WebAssembly // // ================================================================ #if defined(__wasm__) static i32 _frame_width = 0; static i32 _frame_height = 0; static i32 _num_events = 0; static i32 _input_size = 0; static b8 _wait_events = 0; static i64 _timeout = 0; static i64 _sound_position = 0; static i64 _sound_read = 0; static u16 _key_map [MAX_NUM_KEYS] = {0}; static c8 _href [4096] = {0}; static u32 _pixels_scaled [MAX_NUM_PIXELS] = {0}; static u32 _pixels_internal [MAX_NUM_PIXELS] = {0}; static b8 _key_pressed [MAX_NUM_KEYS] = {0}; static f32 _sound_buffer [MAX_NUM_SOUND_FRAMES] = {0}; i64 p_recv(u16 slot, IP_Address address, i64 size, u8 *data, u16 *local_port, IP_Address *remote_address) { // Not implemented return 0; } i64 p_send(u16 slot, IP_Address address, i64 size, u8 *data, u16 *local_port) { // Not implemented return 0; } i32 p_time_impl(void); i64 p_time(void) { return p_time_impl(); } void p_sleep_for(i64 duration) { i64 t = p_time(); if (_timeout < t) _timeout = t + duration; else _timeout += duration; } void p_init(void) { ++_num_events; g_platform.done = 1; _sound_position = SOUND_AVAIL_MIN % MAX_NUM_SOUND_FRAMES; _sound_read = 0; } i32 p_handle_events(void) { g_platform.input_size = _input_size; _input_size = 0; for (i64 i = 0; i < MAX_NUM_KEYS; ++i) g_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(void) { _wait_events = 1; return p_handle_events(); } void p_render_frame(void) { if (_frame_width == g_platform.frame_width && _frame_height == g_platform.frame_height) { i64 size = g_platform.frame_width * g_platform.frame_height; for (i64 i = 0; i < size; ++i) _pixels_internal[i] = 0xff000000u | rgb_u32_from_f32((vec3_f32) { g_platform.pixels[i].z, g_platform.pixels[i].y, g_platform.pixels[i].x }); } else { i64 size = g_platform.frame_width * g_platform.frame_height; for (i64 i = 0; i < size; ++i) _pixels_scaled[i] = rgb_u32_from_f32((vec3_f32) { g_platform.pixels[i].z, g_platform.pixels[i].y, g_platform.pixels[i].x }); for (i64 j = 0; j < _frame_height; ++j) { i64 j0 = (j * g_platform.frame_height) / _frame_height; for (i64 i = 0; i < _frame_width; ++i) { i64 i0 = (i * g_platform.frame_width) / _frame_width; _pixels_internal[j * _frame_width + i] = 0xff000000u | _pixels_scaled[j0 * g_platform.frame_width + i0]; } } } } void p_clipboard_write_impl(i32 size, c8 *data); void p_clipboard_write(i64 size, c8 *data) { if (size < 0 || data == NULL) return; if (size > MAX_CLIPBOARD_SIZE) size = MAX_CLIPBOARD_SIZE; g_platform.clipboard_size = size; for (i64 i = 0; i < size; ++i) g_platform.clipboard[i] = data[i]; p_clipboard_write_impl((i32) size, data); } void p_handle_sound(void) { g_platform.num_sound_samples_elapsed = sound_samples_elapsed_(); _sound_position = (_sound_position + g_platform.num_sound_samples_elapsed * NUM_SOUND_CHANNELS) % MAX_NUM_SOUND_FRAMES; } void p_queue_sound(i64 delay_in_samples, i64 num_samples, f32 *frames) { if (frames == NULL) return; if (delay_in_samples < 0) { frames += -delay_in_samples * NUM_SOUND_CHANNELS; num_samples -= delay_in_samples; delay_in_samples = 0; } if (num_samples <= 0) return; i64 num_frames = num_samples * NUM_SOUND_CHANNELS; if (num_frames > MAX_NUM_SOUND_FRAMES) return; i64 begin = (_sound_position + delay_in_samples * NUM_SOUND_CHANNELS) % MAX_NUM_SOUND_FRAMES; if (num_frames <= MAX_NUM_SOUND_FRAMES - begin) for (i64 i = 0; i < num_frames; ++i) g_platform.sound_ring[begin + i] += frames[i]; else { i64 part_one = MAX_NUM_SOUND_FRAMES - begin; i64 part_two = num_frames - part_one; for (i64 i = 0; i < part_one; ++i) g_platform.sound_ring[begin + i] += frames[i]; for (i64 i = 0; i < part_two; ++i) g_platform.sound_ring[i] += frames[part_one + i]; } } __attribute__((export_name("js_max_num_keys"))) i32 js_max_num_keys(void) { return MAX_NUM_KEYS; } __attribute__((export_name("js_key_map"))) void *js_key_map(void) { i32 n = 0; _key_map[n++] = KEY_BACKSPACE; _key_map[n++] = KEY_TAB; _key_map[n++] = KEY_ENTER; _key_map[n++] = KEY_LCTRL; _key_map[n++] = KEY_RCTRL; _key_map[n++] = KEY_LSHIFT; _key_map[n++] = KEY_RSHIFT; _key_map[n++] = KEY_LALT; _key_map[n++] = KEY_RALT; _key_map[n++] = KEY_LEFT; _key_map[n++] = KEY_RIGHT; _key_map[n++] = KEY_UP; _key_map[n++] = KEY_DOWN; _key_map[n++] = KEY_PAUSE; _key_map[n++] = KEY_INSERT; _key_map[n++] = KEY_HOME; _key_map[n++] = KEY_END; _key_map[n++] = KEY_PAGEUP; _key_map[n++] = KEY_PAGEDOWN; _key_map[n++] = KEY_ESCAPE; _key_map[n++] = KEY_PRINTSCREEN; _key_map[n++] = KEY_SPACE; _key_map[n++] = KEY_LMETA; _key_map[n++] = KEY_RMETA; _key_map[n++] = KEY_QUOTE; _key_map[n++] = KEY_COMMA; _key_map[n++] = KEY_MINUS; _key_map[n++] = KEY_PERIOD; _key_map[n++] = KEY_SLASH; _key_map[n++] = KEY_0; _key_map[n++] = KEY_1; _key_map[n++] = KEY_2; _key_map[n++] = KEY_3; _key_map[n++] = KEY_4; _key_map[n++] = KEY_5; _key_map[n++] = KEY_6; _key_map[n++] = KEY_7; _key_map[n++] = KEY_8; _key_map[n++] = KEY_9; _key_map[n++] = KEY_COLON; _key_map[n++] = KEY_EQUAL; _key_map[n++] = KEY_LBRACE; _key_map[n++] = KEY_BACKSLASH; _key_map[n++] = KEY_RBRACE; _key_map[n++] = KEY_TILDE; _key_map[n++] = KEY_A; _key_map[n++] = KEY_B; _key_map[n++] = KEY_C; _key_map[n++] = KEY_D; _key_map[n++] = KEY_E; _key_map[n++] = KEY_F; _key_map[n++] = KEY_G; _key_map[n++] = KEY_H; _key_map[n++] = KEY_I; _key_map[n++] = KEY_J; _key_map[n++] = KEY_K; _key_map[n++] = KEY_L; _key_map[n++] = KEY_M; _key_map[n++] = KEY_N; _key_map[n++] = KEY_O; _key_map[n++] = KEY_P; _key_map[n++] = KEY_Q; _key_map[n++] = KEY_R; _key_map[n++] = KEY_S; _key_map[n++] = KEY_T; _key_map[n++] = KEY_U; _key_map[n++] = KEY_V; _key_map[n++] = KEY_W; _key_map[n++] = KEY_X; _key_map[n++] = KEY_Y; _key_map[n++] = KEY_Z; _key_map[n++] = KEY_DELETE; _key_map[n++] = KEY_F1; _key_map[n++] = KEY_F2; _key_map[n++] = KEY_F3; _key_map[n++] = KEY_F4; _key_map[n++] = KEY_F5; _key_map[n++] = KEY_F6; _key_map[n++] = KEY_F7; _key_map[n++] = KEY_F8; _key_map[n++] = KEY_F9; _key_map[n++] = KEY_F10; _key_map[n++] = KEY_F11; _key_map[n++] = KEY_F12; _key_map[n++] = KEY_F13; _key_map[n++] = KEY_F14; _key_map[n++] = KEY_F15; _key_map[n++] = KEY_F16; _key_map[n++] = KEY_F17; _key_map[n++] = KEY_F18; _key_map[n++] = KEY_F19; _key_map[n++] = KEY_F20; _key_map[n++] = KEY_F21; _key_map[n++] = KEY_F22; _key_map[n++] = KEY_F23; _key_map[n++] = KEY_F24; return _key_map; } __attribute__((export_name("js_sound_sample_rate"))) f64 js_sound_sample_rate(void) { return (f64) SOUND_SAMPLE_RATE; } __attribute__((export_name("js_num_sound_channels"))) i32 js_num_sound_channels(void) { return NUM_SOUND_CHANNELS; } __attribute__((export_name("js_max_num_sound_frames"))) i32 js_max_num_sound_frames(void) { return MAX_NUM_SOUND_FRAMES; } __attribute__((export_name("js_sound_buffer"))) void *js_sound_buffer(void) { return _sound_buffer; } __attribute__((export_name("js_href"))) void *js_href(void) { return _href; } __attribute__((export_name("js_href_size"))) i32 js_href_size(void) { return (sizeof _href) - 1; } __attribute__((export_name("js_main"))) void js_main(void) { main(1, (c8 *[1]) { _href }); } __attribute__((export_name("js_title"))) void *js_title(void) { return g_platform.title; } __attribute__((export_name("js_pixels"))) void *js_pixels(void) { return _pixels_internal; } __attribute__((export_name("js_frame"))) void js_frame(i32 frame_width, i32 frame_height, i32 num_samples) { i64 frame_time = p_time(); if (frame_width > 0 && frame_height > 0 && frame_width * frame_height <= MAX_NUM_PIXELS) { _frame_width = frame_width; _frame_height = frame_height; } i32 width = _frame_width; i32 height = _frame_height; if (g_platform.pixel_size <= 0) g_platform.pixel_size = DEFAULT_PIXEL_SIZE; if (g_platform.pixel_size <= MIN_PIXEL_SIZE) g_platform.pixel_size = MIN_PIXEL_SIZE; if (g_platform.pixel_size > 1) { width = (i32) floor(((f64) _frame_width) / g_platform.pixel_size + .5); height = (i32) floor(((f64) _frame_height) / g_platform.pixel_size + .5); } if (g_platform.real_width != _frame_width || g_platform.real_height != _frame_height) { ++_num_events; g_platform.real_width = _frame_width; g_platform.real_height = _frame_height; } if (g_platform.frame_width != width || g_platform.frame_height != height) { ++_num_events; g_platform.frame_width = width; g_platform.frame_height = height; } g_platform.done = 0; b8 do_render = (frame_time >= _timeout) && (_num_events > 0 || !_wait_events); if (do_render) { _wait_events = 0; update_and_render_frame(); } // Convert interleaved frames to non-interleaved. for (i64 j = 0; j < NUM_SOUND_CHANNELS; ++j) for (i64 i = 0; i < num_samples; ++i) { i64 n = (_sound_read + i * NUM_SOUND_CHANNELS + j) % MAX_NUM_SOUND_FRAMES; _sound_buffer[j * num_samples + i] = g_platform.sound_ring[n]; g_platform.sound_ring[n] = 0.f; } _sound_read = (_sound_read + num_samples * NUM_SOUND_CHANNELS) % MAX_NUM_SOUND_FRAMES; if (!g_platform.exact_resolution && do_render) { i64 frame_duration = p_time() - frame_time; if (g_platform.pixel_size < MAX_PIXEL_SIZE && frame_duration > MAX_FRAME_DURATION) ++g_platform.pixel_size; else if (g_platform.pixel_size > MIN_PIXEL_SIZE && frame_duration < MIN_FRAME_DURATION) --g_platform.pixel_size; } } __attribute__((export_name("js_mousemove"))) void js_mousemove(i32 x, i32 y) { ++_num_events; g_platform.cursor_dx = x - g_platform.cursor_x; g_platform.cursor_dy = y - g_platform.cursor_y; g_platform.cursor_x = x; g_platform.cursor_y = y; } __attribute__((export_name("js_mousedown"))) void js_mousedown(u32 buttons) { ++_num_events; if ((buttons & 1) && !g_platform.key_down[BUTTON_LEFT]) _key_pressed[BUTTON_LEFT] = 1; if ((buttons & 2) && !g_platform.key_down[BUTTON_RIGHT]) _key_pressed[BUTTON_RIGHT] = 1; if ((buttons & 4) && !g_platform.key_down[BUTTON_MIDDLE]) _key_pressed[BUTTON_MIDDLE] = 1; if (buttons & 1) g_platform.key_down[BUTTON_LEFT] = 1; if (buttons & 2) g_platform.key_down[BUTTON_RIGHT] = 1; if (buttons & 4) g_platform.key_down[BUTTON_MIDDLE] = 1; } __attribute__((export_name("js_mouseup"))) void js_mouseup(u32 buttons) { ++_num_events; if (!(buttons & 1)) g_platform.key_down[BUTTON_LEFT] = 0; if (!(buttons & 2)) g_platform.key_down[BUTTON_RIGHT] = 0; if (!(buttons & 4)) g_platform.key_down[BUTTON_MIDDLE] = 0; } __attribute__((export_name("js_keydown"))) void js_keydown(u32 key, u32 mod, u32 ch) { ++_num_events; _key_pressed[key] = 1; g_platform.key_down[key] = 1; g_platform.key_down[MOD_CTRL] = (mod & 1) ? 1 : 0; g_platform.key_down[MOD_SHIFT] = (mod & 2) ? 1 : 0; g_platform.key_down[MOD_ALT] = (mod & 4) ? 1 : 0; g_platform.key_down[MOD_META] = (mod & 8) ? 1 : 0; if (g_platform.input_size < MAX_INPUT_SIZE) g_platform.input[_input_size++] = (Input_Key) { .ctrl = (mod & 1) ? 1 : 0, .shift = (mod & 2) ? 1 : 0, .alt = (mod & 4) ? 1 : 0, .meta = (mod & 8) ? 1 : 0, .key = key, .c = ch, }; } __attribute__((export_name("js_keyup"))) void js_keyup(u32 key, u32 mod) { ++_num_events; g_platform.key_down[key] = 0; g_platform.key_down[MOD_CTRL] = (mod & 1) ? 1 : 0; g_platform.key_down[MOD_SHIFT] = (mod & 2) ? 1 : 0; g_platform.key_down[MOD_ALT] = (mod & 4) ? 1 : 0; g_platform.key_down[MOD_META] = (mod & 8) ? 1 : 0; } __attribute__((export_name("js_clipboard_size"))) i32 js_clipboard_size(void) { return g_platform.clipboard_size; } __attribute__((export_name("js_clipboard_buffer"))) void *js_clipboard_buffer(i32 len) { if (len < 0) len = 0; if (len > MAX_CLIPBOARD_SIZE) len = MAX_CLIPBOARD_SIZE; g_platform.clipboard_size = len; return g_platform.clipboard; } #endif // defined(__wasm__) // ================================================================ #undef LOG_ERROR #endif // REDUCED_SYSTEM_LAYER_IMPL_GUARD_ #endif // REDUCED_SYSTEM_LAYER_HEADER