diff options
author | Mitya Selivanov <automainint@guattari.tech> | 2025-01-16 03:38:24 +0100 |
---|---|---|
committer | Mitya Selivanov <automainint@guattari.tech> | 2025-01-16 03:38:24 +0100 |
commit | 725662a623ac36d44d2b44c568d8dd6c3eeeeca1 (patch) | |
tree | 50afdc84553f44ab0dc7dff425752e87f0a5e661 | |
parent | c6c72f54ea7f25defa7d39216f7713c46b873362 (diff) | |
download | reduced_system_layer-725662a623ac36d44d2b44c568d8dd6c3eeeeca1.zip |
Drag and drop impl on X11 (not tested)
-rw-r--r-- | examples/julia_set.c | 1 | ||||
-rw-r--r-- | examples/particles.c | 1 | ||||
-rw-r--r-- | graphics.c | 48 | ||||
-rw-r--r--[-rwxr-xr-x] | reduced_system_layer.c | 936 | ||||
-rw-r--r-- | test.c | 22 |
5 files changed, 653 insertions, 355 deletions
diff --git a/examples/julia_set.c b/examples/julia_set.c index e50a246..c7f7e9b 100644 --- a/examples/julia_set.c +++ b/examples/julia_set.c @@ -83,6 +83,7 @@ i32 main(i32 argc, c8 **argv) { g_platform = (Platform) { .title = "Julia Set", + .pixel_size = 1, .exact_resolution = 1, }; diff --git a/examples/particles.c b/examples/particles.c index 130b5bf..894fee0 100644 --- a/examples/particles.c +++ b/examples/particles.c @@ -118,6 +118,7 @@ i32 main(i32 argc, c8 **argv) { g_platform = (Platform) { .title = "Particles", + .pixel_size = 1, .exact_resolution = 1, }; @@ -51,6 +51,11 @@ vec3_f32 lch_from_rgb(vec3_f32 rgb); vec4_f32 with_alpha(vec3_f32 color, f32 alpha); vec3_f32 without_alpha(vec4_f32 color); +u32 rgb_u32_from_f32(vec3_f32 color); +vec3_f32 rgb_f32_from_u32(u32 color); +u32 rgba_u32_from_f32(vec4_f32 color); +vec4_f32 rgba_f32_from_u32(u32 color); + #define RGB(...) ((Brush) { .color = { __VA_ARGS__, 1.f } }) #define RGBA(...) ((Brush) { .color = { __VA_ARGS__ } }) #define LCH(...) ((Brush) { .color = with_alpha(gamma_apply(rgb_from_lch((vec3_f32) { __VA_ARGS__ })), 1.f) } }) @@ -442,6 +447,49 @@ vec3_f32 without_alpha(vec4_f32 color) { }; } +u32 rgb_u32_from_f32(vec3_f32 color) { + i32 ir = (i32) floor(color.x * 255. + .5); + i32 ig = (i32) floor(color.y * 255. + .5); + i32 ib = (i32) floor(color.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 = ((color & 0xff0000) >> 16) / 255.f, + .y = ((color & 0x00ff00) >> 8) / 255.f, + .z = (color & 0x0000ff) / 255.f, + }; +} + +u32 rgba_u32_from_f32(vec4_f32 color) { + i32 ir = (i32) floor(color.x * 255. + .5); + i32 ig = (i32) floor(color.y * 255. + .5); + i32 ib = (i32) floor(color.z * 255. + .5); + i32 ia = (i32) floor(color.w * 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; + u32 a = ia < 0 ? 0u : ia > 255 ? 255u : (u32) ia; + + return (a << 24) | (r << 16) | (g << 8) | b; +} + +vec4_f32 rgba_f32_from_u32(u32 color) { + return (vec4_f32) { + .x = ((color & 0x00ff0000) >> 16) / 255.f, + .y = ((color & 0x0000ff00) >> 8) / 255.f, + .z = (color & 0x000000ff) / 255.f, + .w = ((color & 0xff000000) >> 24) / 255.f, + }; +} + b8 rectangle_contains(f64 x0, f64 y0, f64 width, f64 height, f64 px, f64 py) { return px >= x0 && px < x0 + width && py >= y0 && py < y0 + height; } diff --git a/reduced_system_layer.c b/reduced_system_layer.c index 980e12c..017fc43 100755..100644 --- a/reduced_system_layer.c +++ b/reduced_system_layer.c @@ -1,125 +1,99 @@ -#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 <guattari.tech> -#/ -#/ ================================================================ -#/ -#/ 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 - +// ================================================================ +// +// 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 <guattari.tech> +// // ================================================================ // // Types @@ -159,13 +133,26 @@ typedef struct { f64 v[16]; } mat4; #endif // TYPES_HEADER_GUARD_ // ================================================================ + +#ifndef REDUCED_SYSTEM_LAYER_HEADER_GUARD_ +#define REDUCED_SYSTEM_LAYER_HEADER_GUARD_ + +#ifdef __cplusplus +extern "C" { +#endif + +// ================================================================ // -// Basic declarations +// User program interface // // ================================================================ -#ifndef REDUCED_SYSTEM_LAYER_HEADER_GUARD_ -#define REDUCED_SYSTEM_LAYER_HEADER_GUARD_ +// NOTE: This procedure is required for the WebAssembly compatibility. +void update_and_render_frame(void); + +#if defined(__wasm__) +i32 main(i32 argc, c8 **argv); +#endif // ================================================================ // @@ -206,11 +193,7 @@ typedef struct { f64 v[16]; } mat4; #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 @@ -241,28 +224,117 @@ typedef struct { f64 v[16]; } mat4; #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) +#ifndef DROP_FILES_BUFFER_SIZE +#define DROP_FILES_BUFFER_SIZE (20 * 1024 * 1024) #endif // ================================================================ // -// PLATFORM API +// Basic declarations // // ================================================================ -#if !defined(__wasm__) #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif -#include <stdlib.h> + +#ifndef NULL +#define NULL ((void *) 0) +#endif + +// ================================================================ +// +// Math +// +// ================================================================ + +#if !defined(__wasm__) #include <math.h> +#else +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 -#ifdef __cplusplus -extern "C" { +#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 + +// ================================================================ +// +// PLATFORM API +// +// ================================================================ + enum { IPv4_UDP = 1, IPv6_UDP = 2, @@ -417,6 +489,7 @@ typedef struct { c8 *title; i32 frame_width; i32 frame_height; + i32 pixel_size; b8 exact_resolution; b8 graceful_exit; @@ -426,7 +499,6 @@ typedef struct { b8 files_dropped; i32 real_width; i32 real_height; - i32 pixel_size; i64 input_size; i64 clipboard_size; i32 cursor_x; @@ -438,6 +510,7 @@ typedef struct { i64 sound_clock_time; i64 sound_clock_carry; i64 num_sound_samples_elapsed; + i64 sound_position; i64 num_drop_files; Drop_File *drop_files; @@ -448,7 +521,9 @@ typedef struct { c8 clipboard [MAX_CLIPBOARD_SIZE]; b8 key_down [MAX_NUM_KEYS]; b8 key_pressed [MAX_NUM_KEYS]; - u8 drop_buffer [DROP_FILE_BUFFER_SIZE]; + + // TODO: Allocator for big buffers + u8 drop_files_buffer[DROP_FILES_BUFFER_SIZE]; } Platform; typedef struct { @@ -462,15 +537,11 @@ typedef struct { } IP_Address; // UTF-8 -// NOTE We need UTF-8 because we use Xutf8LookupString on Xlib. +// 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); @@ -483,10 +554,6 @@ 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); @@ -503,147 +570,16 @@ 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 -} +} // extern "C" #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 // // ================================================================ @@ -747,7 +683,7 @@ i32 utf8_write(c32 c, c8 *buffer) { return 0; } -u32 rgb_u32_from_f32(vec3_f32 c) { +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); @@ -759,14 +695,6 @@ u32 rgb_u32_from_f32(vec3_f32 c) { 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(); @@ -784,11 +712,8 @@ static i64 sound_samples_elapsed_(void) { return num_samples; } -// TEMP -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" - static void drop_files_clean_(void) { + g_platform.files_dropped = 0; g_platform.num_drop_files = 0; g_platform.drop_files = NULL; } @@ -824,15 +749,15 @@ static void drop_files_set_num_(i64 num) { return; } - i64 available = DROP_FILE_BUFFER_SIZE - drop_files_data_size_(g_platform.num_drop_files); + i64 available = DROP_FILES_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)); + u8 *src = g_platform.drop_files_buffer + (DROP_FILES_BUFFER_SIZE - src_size); + u8 *dst = g_platform.drop_files_buffer + (DROP_FILES_BUFFER_SIZE - num * (i64) sizeof(Drop_File)); for (i64 i = 0; i < src_size; ++i) dst[i] = src[i]; @@ -843,53 +768,85 @@ static void drop_files_set_num_(i64 num) { g_platform.drop_files = (Drop_File *) dst; } +static void drop_files_empty_after_(i64 index) { + if (g_platform.drop_files == NULL) { + LOG_ERROR("Sanity"); + return; + } + + for (i64 i = index + 1; i < g_platform.num_drop_files; ++i) { + if (g_platform.drop_files[i].name_size != 0) { + LOG_ERROR("Sanity"); + g_platform.drop_files[i].name_size = 0; + } + + if (g_platform.drop_files[i].data_size != 0) { + LOG_ERROR("Sanity"); + g_platform.drop_files[i].data_size = 0; + } + } +} + +static void drop_files_empty_data_after_(i64 index) { + if (g_platform.drop_files == NULL) { + LOG_ERROR("Sanity"); + return; + } + + for (i64 i = index + 1; i < g_platform.num_drop_files; ++i) { + if (g_platform.drop_files[i].data_size != 0) { + LOG_ERROR("Sanity"); + g_platform.drop_files[i].data_size = 0; + } + } +} + 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) { + if (g_platform.drop_files == NULL) { LOG_ERROR("Sanity"); return; } + drop_files_empty_after_(index); + i64 offset = drop_files_data_size_(index); - i64 available = DROP_FILE_BUFFER_SIZE - offset - g_platform.num_drop_files * sizeof(Drop_File); + i64 available = DROP_FILES_BUFFER_SIZE - offset - g_platform.num_drop_files * sizeof(Drop_File); if (available < name_size + 1) { - LOG_ERROR("Sanity"); + LOG_ERROR("Out of memory"); return; } g_platform.drop_files[index].name_size = name_size + 1; - g_platform.drop_files[index].name = (c8 *) g_platform.drop_buffer + offset; + g_platform.drop_files[index].name = (c8 *) g_platform.drop_files_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) { +static void drop_files_set_data_(i64 index, i64 data_size) { + if (g_platform.drop_files == NULL) { LOG_ERROR("Sanity"); return; } + drop_files_empty_data_after_(index); + 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); + i64 available = DROP_FILES_BUFFER_SIZE - offset - g_platform.num_drop_files * sizeof(Drop_File); if (available < data_size) { - LOG_ERROR("Sanity"); + LOG_ERROR("Out of memory"); 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]; + g_platform.drop_files[index].data = g_platform.drop_files_buffer + offset; } -#pragma GCC diagnostic pop - // ================================================================ // // Unix @@ -913,10 +870,10 @@ void p_yield(void) { } void p_sleep_for(i64 duration) { - if (duration <= 0) { + if (duration == 0) usleep(0); + if (duration <= 0) return; - } if (duration >= 1000) // seconds @@ -1182,14 +1139,13 @@ i64 p_send(u16 slot, IP_Address address, i64 size, u8 *data, u16 *local_port) { #include <alsa/asoundlib.h> 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; + g_platform.sound_position = 0; i32 s; @@ -1263,7 +1219,7 @@ static void sound_cleanup(void) { if (s < 0) LOG_ERROR("snd_pcm_drain failed: %s", snd_strerror(s)); - // FIXME Memory leaks, seems to be an ALSA bug. + // FIXME: Memory leaks, seems to be an ALSA bug. // snd_pcm_close(_sound_out); // snd_config_update_free_global(); @@ -1283,17 +1239,17 @@ void p_handle_sound(void) { 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 (num_frames <= MAX_NUM_SOUND_FRAMES - g_platform.sound_position) { + s = snd_pcm_writei(_sound_out, g_platform.sound_ring + g_platform.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); + memset(g_platform.sound_ring + g_platform.sound_position, 0, num_frames * sizeof *g_platform.sound_ring); } else { - i64 part_one = MAX_NUM_SOUND_FRAMES - _sound_position; + i64 part_one = MAX_NUM_SOUND_FRAMES - g_platform.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); + s = snd_pcm_writei(_sound_out, g_platform.sound_ring + g_platform.sound_position, part_one / NUM_SOUND_CHANNELS); if (s < 0) LOG_ERROR("snd_pcm_writei failed: %s", snd_strerror(s)); @@ -1301,11 +1257,11 @@ void p_handle_sound(void) { 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 + g_platform.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; + g_platform.sound_position = (g_platform.sound_position + num_frames) % MAX_NUM_SOUND_FRAMES; } void p_queue_sound(i64 delay_in_samples, i64 num_samples, f32 *frames) { @@ -1326,13 +1282,13 @@ void p_queue_sound(i64 delay_in_samples, i64 num_samples, f32 *frames) { i64 num_frames = num_samples * NUM_SOUND_CHANNELS; if (num_frames > MAX_NUM_SOUND_FRAMES) { - LOG_ERROR("Sound buffer overflow."); + LOG_ERROR("Sound buffer overflow."); return; } sound_init(); - i64 begin = (_sound_position + delay_in_samples) % MAX_NUM_SOUND_FRAMES; + i64 begin = (g_platform.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) @@ -1358,22 +1314,43 @@ void p_queue_sound(i64 delay_in_samples, i64 num_samples, f32 *frames) { #if defined(__linux__) +#include <sys/stat.h> #include <X11/Xlib.h> #include <X11/Xutil.h> #include <X11/Xatom.h> -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 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 Window _drop_source = 0; +static Atom _wm_delete_window = 0; +static Atom _clipboard = 0; +static Atom _targets = 0; +static Atom _utf8_string = 0; +static Atom _target = 0; +static Atom _dnd_type_list = 0; +static Atom _dnd_selection = 0; +static Atom _dnd_enter = 0; +static Atom _dnd_position = 0; +static Atom _dnd_status = 0; +static Atom _dnd_leave = 0; +static Atom _dnd_drop = 0; +static Atom _dnd_finished = 0; +static Atom _dnd_action_copy = 0; +static Atom _dnd_action_move = 0; +static Atom _dnd_action_link = 0; +static Atom _dnd_action_ask = 0; +static Atom _dnd_action_private = 0; +static Atom _text_uri_list = 0; +static Atom _text_plain = 0; +static Atom _dnd_aware = 0; +static Window _dnd_source = 0; +static Atom _dnd_format = 0; +static u32 _dnd_version = 0; static i16 _key_table [MAX_NUM_KEYS] = {0}; static b8 _key_repeat [MAX_NUM_KEYS] = {0}; @@ -1567,12 +1544,35 @@ void p_init(void) { _targets = XInternAtom(_display, "TARGETS", False); _utf8_string = XInternAtom(_display, "UTF8_STRING", False); + _target = None; + XSetICFocus(_ic); XSetWMProtocols(_display, _window, &_wm_delete_window, 1); if (g_platform.title != NULL) XStoreName(_display, _window, g_platform.title); + _dnd_type_list = XInternAtom(_display, "XdndTypeList", False); + _dnd_selection = XInternAtom(_display, "XdndSelection", False); + _dnd_enter = XInternAtom(_display, "XdndEnter", False); + _dnd_position = XInternAtom(_display, "XdndPosition", False); + _dnd_status = XInternAtom(_display, "XdndStatus", False); + _dnd_leave = XInternAtom(_display, "XdndLeave", False); + _dnd_drop = XInternAtom(_display, "XdndDrop", False); + _dnd_finished = XInternAtom(_display, "XdndFinished", False); + _dnd_action_copy = XInternAtom(_display, "XdndActionCopy", False); + _dnd_action_move = XInternAtom(_display, "XdndActionMove", False); + _dnd_action_link = XInternAtom(_display, "XdndActionLink", False); + _dnd_action_ask = XInternAtom(_display, "XdndActionAsk", False); + _dnd_action_private = XInternAtom(_display, "XdndActionPrivate", False); + _text_uri_list = XInternAtom(_display, "text/uri-list", False); + _text_plain = XInternAtom(_display, "text/plain", False); + _dnd_aware = XInternAtom(_display, "XdndAware", False); + + _dnd_format = None; + + XChangeProperty(_display, _window, _dnd_aware, 4, 32, PropModeReplace, &(u8) {5}, 1); + XMapWindow(_display, _window); XPutImage(_display, _window, _gc, &_image, 0, 0, 0, 0, _image.width, _image.height); @@ -1603,6 +1603,9 @@ i32 p_handle_events(void) { i32 num_events = 0; + if (g_platform.files_dropped) + drop_files_clean_(); + memset(g_platform.key_pressed, 0, sizeof g_platform.key_pressed); memset(_key_repeat, 0, sizeof _key_repeat); @@ -1789,7 +1792,75 @@ i32 p_handle_events(void) { break; case SelectionNotify: - if (ev.xselection.property != None) { + if (ev.xselection.property == _dnd_selection) { + u8 *data; + i32 len; + + XGetWindowProperty( + _display, + ev.xselection.requestor, + _dnd_selection, + 0, + DROP_FILES_BUFFER_SIZE, + False, + ev.xselection.target, + &(Atom) {0}, + &(i32) {0}, + (void *) &len, + &(unsigned long) {0}, + &data + ); + + if (len == 0) + break; + + if (data) { + i64 num = 0; + for (i64 i = 0; i < len; ++i) + if (data[i] == '\n' || i + 1 == len) + ++num; + drop_files_set_num_(num); + + i64 index = 0; + i64 offset = 0; + + for (i64 i = 0; i < len; ++i) + if (data[i] == '\n' || i + 1 == len) { + drop_files_set_name_(index, i - offset, (c8 *) data + offset); + + ++index; + offset = i + 1; + } + + XFree(data); + } + + if (_dnd_version >= 2) { + XSendEvent( + _display, + _dnd_source, + False, + NoEventMask, + &(XEvent) { + .xclient = { + .type = ClientMessage, + .message_type = _dnd_finished, + .window = _drop_source, + .format = 32, + .data = { + .l = { + _window, + len, + _dnd_action_copy, + }, + }, + }, + } + ); + + XFlush(_display); + } + } else if (ev.xselection.property != None) { i64 len = 0; u8 *data = NULL; @@ -1802,8 +1873,8 @@ i32 p_handle_events(void) { False, AnyPropertyType, &(Atom) {0}, - &(int) {0}, - (unsigned long *) &len, + &(i32) {0}, + (void *) &len, &(unsigned long) {0}, &data ); @@ -1833,6 +1904,7 @@ i32 p_handle_events(void) { if (data) XFree(data); } + break; case EnterNotify: g_platform.has_cursor = 1; break; @@ -1854,6 +1926,163 @@ i32 p_handle_events(void) { case ClientMessage: if ((Atom) ev.xclient.data.l[0] == _wm_delete_window) g_platform.done = 1; + else { + if (ev.xclient.message_type == _dnd_enter) { + i64 count; + Atom *formats; + Atom real_formats[6]; + + u32 list = ev.xclient.data.l[1] & 1; + + _dnd_source = ev.xclient.data.l[0]; + _dnd_format = None; + _dnd_version = ev.xclient.data.l[1] >> 24; + + if (_dnd_version > 5) { + LOG_ERROR("X client message version not supported: %d.", _dnd_version); + break; + } + + if (list) + XGetWindowProperty( + _display, + _dnd_source, + _dnd_type_list, + 0, + DROP_FILES_BUFFER_SIZE / 4, + False, + 4, + &(Atom) {0}, + &(i32) {0}, + (void *) &count, + &(unsigned long) {0}, + (void *) &formats + ); + else { + count = 0; + + if (ev.xclient.data.l[2] != None) real_formats[count++] = ev.xclient.data.l[2]; + if (ev.xclient.data.l[3] != None) real_formats[count++] = ev.xclient.data.l[3]; + if (ev.xclient.data.l[4] != None) real_formats[count++] = ev.xclient.data.l[4]; + + formats = real_formats; + } + + for (i64 i = 0; i < count; ++i) + if (formats[i] == _text_uri_list || formats[i] == _text_plain) { + _dnd_format = formats[i]; + break; + } + + if (list) + XFree(formats); + + break; + } + + if (_dnd_version > 5) + break; + + if (ev.xclient.message_type == _dnd_position) { + i32 xabs = (ev.xclient.data.l[2] >> 16) & 0xffff; + i32 yabs = ev.xclient.data.l[2] & 0xffff; + + Window dummy; + i32 xpos; + i32 ypos; + + XTranslateCoordinates( + _display, + XDefaultRootWindow(_display), + _window, + xabs, yabs, + &xpos, &ypos, + &dummy + ); + + g_platform.cursor_dx += xpos - g_platform.cursor_x; + g_platform.cursor_dy += ypos - g_platform.cursor_y; + g_platform.cursor_x = xpos; + g_platform.cursor_y = ypos; + + XEvent reply = { + .xclient = { + .type = ClientMessage, + .message_type = _dnd_status, + .window = _drop_source, + .format = 32, + .data = { + .l = { _window, }, + }, + }, + }; + + if (_dnd_format) { + reply.xclient.data.l[1] = 1; + if (_dnd_version >= 2) + reply.xclient.data.l[4] = _dnd_action_copy; + } + + XSendEvent(_display, _dnd_source, False, NoEventMask, &reply); + XFlush(_display); + } else if (ev.xclient.message_type == _dnd_drop) { + g_platform.files_dropped = 1; + + if (g_platform.drop_files == NULL) + LOG_ERROR("No drop files."); + else + for (i64 i = 0; i < g_platform.num_drop_files; ++i) { + FILE *f = fopen(g_platform.drop_files[i].name, "rb"); + + if (f != NULL) { + i64 size = 0; + struct stat info; + if (fstat(fileno(f), &info) == 0 && S_ISREG(info.st_mode)) + size = (i64) info.st_size; + else + LOG_ERROR("Unable to get the file info: %s", g_platform.drop_files[i].name); + + drop_files_set_data_(i, size); + + i64 num = (i64) fread(g_platform.drop_files[i].data, 1, g_platform.drop_files[i].data_size + 1, f); + fclose(f); + + if (num != g_platform.drop_files[i].data_size) { + LOG_ERROR("Unable to read the file: %s", g_platform.drop_files[i].name); + drop_files_set_data_(i, 0); + } + } else + LOG_ERROR("Unable to open the file: %s", g_platform.drop_files[i].name); + } + + if (_dnd_format) + XConvertSelection( + _display, + _dnd_selection, + _dnd_format, + _dnd_selection, + _window, + _dnd_version >= 1 ? ev.xclient.data.l[2] : CurrentTime + ); + else if (_dnd_version >= 2) { + XEvent reply = { + .xclient = { + .type = ClientMessage, + .message_type = _dnd_finished, + .window = _drop_source, + .format = 32, + .data = { + .l = { _window, }, + }, + }, + }; + + XSendEvent(_display, _dnd_source, False, NoEventMask, &reply); + XFlush(_display); + } + } + } + break; default:; @@ -1918,11 +2147,11 @@ void p_render_frame(void) { 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 }); + _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 }); + _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; @@ -1975,7 +2204,6 @@ 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}; @@ -2002,6 +2230,9 @@ i64 p_time(void) { } void p_sleep_for(i64 duration) { + if (duration <= 0) + return; + i64 t = p_time(); if (_timeout < t) @@ -2012,9 +2243,11 @@ void p_sleep_for(i64 duration) { void p_init(void) { ++_num_events; - g_platform.done = 1; - _sound_position = SOUND_AVAIL_MIN % MAX_NUM_SOUND_FRAMES; - _sound_read = 0; + + g_platform.done = 1; + g_platform.sound_position = 0; + + _sound_read = (MAX_NUM_SOUND_FRAMES + ((-SOUND_AVAIL_MIN) % MAX_NUM_SOUND_FRAMES)) % MAX_NUM_SOUND_FRAMES; } i32 p_handle_events(void) { @@ -2041,11 +2274,11 @@ 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 }); + _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 }); + _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; @@ -2074,7 +2307,7 @@ void p_clipboard_write(i64 size, c8 *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; + g_platform.sound_position = (g_platform.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) { @@ -2094,7 +2327,7 @@ void p_queue_sound(i64 delay_in_samples, i64 num_samples, f32 *frames) { if (num_frames > MAX_NUM_SOUND_FRAMES) return; - i64 begin = (_sound_position + delay_in_samples * NUM_SOUND_CHANNELS) % MAX_NUM_SOUND_FRAMES; + i64 begin = (g_platform.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) @@ -2242,6 +2475,11 @@ __attribute__((export_name("js_href_size"))) i32 js_href_size(void) { } __attribute__((export_name("js_main"))) void js_main(void) { + (void) drop_files_clean_; + (void) drop_files_set_num_; + (void) drop_files_set_name_; + (void) drop_files_set_data_; + main(1, (c8 *[1]) { _href }); } @@ -60,10 +60,23 @@ typedef unsigned u32; typedef unsigned long long u64; typedef char c8; typedef int c32; -typedef signed char b8; +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_ // ================================================================ @@ -352,7 +365,7 @@ static void test_handle_signal(i32 signum) { longjmp(test_restore_execution, signum); } -static void test_setup_signals() { +static void test_setup_signals(void) { for (u32 i = 0; i < sizeof signums_ / sizeof *signums_; i++) signal(signums_[i], test_handle_signal); } @@ -696,7 +709,7 @@ void bench_register(c8 const *name, c8 const *file, bench_run_fn fn) { } } -static void bench_setup_signals() { +static void bench_setup_signals(void) { for (u32 i = 0; i < sizeof signums_ / sizeof *signums_; i++) signal(signums_[i], test_handle_signal); } @@ -726,7 +739,6 @@ static i32 compare_32_(void const *x_, void const *y_) { } i32 run_benchmarks(i32 argc, c8 **argv) { - i32 success_count = 0; i32 status = 0; i32 no_color = 0; i32 line_width = 20; @@ -895,7 +907,6 @@ i32 run_benchmarks(i32 argc, c8 **argv) { if (bench->repeats <= 0) { no_color || print_color_(yellow_); printf(" 0 runs\n"); - success_count++; } else if (bench_status == 0) { no_color || print_color_(red_); printf(" FAIL\n"); @@ -922,7 +933,6 @@ i32 run_benchmarks(i32 argc, c8 **argv) { printf("%-9g", (f64) roof * 0.001); no_color || print_color_(light_); printf(" %d runs\n", repeats); - success_count++; } } } |