diff options
-rw-r--r-- | runtime.c | 490 |
1 files changed, 425 insertions, 65 deletions
@@ -126,11 +126,12 @@ // - Graph // - Sine Wave // - Utility -// - UTF-8, UTF-16 +// - UTF-8, UTF-16 encoding // - Testing // - Stackless coroutines // - Allocator // - Profiling +// - BMP, PPM image formats // - Graphics // - Font // - Adaptive resolution @@ -614,6 +615,21 @@ typedef struct { } Network_Address; typedef struct { + b8 enable_text : 1; + b8 enable_image : 1; + b8 enable_sound : 1; + + i64 text_len; + c8 *text; + + i64 image_len; + u8 *image; + + i64 sound_len; + u8 *sound; +} Clipboard_Content; + +typedef struct { c8 *title; i32 frame_width; i32 frame_height; @@ -623,9 +639,6 @@ typedef struct { b8 exact_resolution : 1; b8 enable_gamma : 1; b8 graceful_shutdown : 1; - b8 enable_clipboard_text : 1; - b8 enable_clipboard_image : 1; - b8 enable_clipboard_sound : 1; b8 done : 1; b8 has_focus : 1; @@ -652,23 +665,18 @@ typedef struct { Input_Key *input; Drop_File *drop_files; - i64 clipboard_text_len; - c8 *clipboard_text; - - i64 clipboard_image_len; - u8 *clipboard_image; - - i64 clipboard_sound_len; - u8 *clipboard_sound; - b8 key_down [MAX_NUM_KEYS]; b8 key_pressed [MAX_NUM_KEYS]; - void *window_handle_win32; - u64 window_handle_x11; + Clipboard_Content clipboard; i64 memory_buffer_size; u8 *memory_buffer; + + // Platform-specific data + + void *window_handle_win32; + u64 window_handle_x11; } Platform; // UTF-8 @@ -2272,16 +2280,30 @@ static void drop_files_set_data_(i64 index, i64 data_size) { // // ================================================================ -static void store_clipboard_text_(i64 size, c8 *data) { - if (size < 0 || data == NULL) +static void store_clipboard_text_(i64 len, c8 *data) { + if (len < 0) { + LOG_error("Sanity"); return; + } - resize_dynamic_array_exact(&g_platform.clipboard_text_len, (void **) &g_platform.clipboard_text, 1, size + 1); + if (len != 0 && data == NULL) { + LOG_error("Sanity"); + return; + } - i64 len = g_platform.clipboard_text_len - 1; - mem_cpy_(g_platform.clipboard_text, data, len); - if (len >= 0) - g_platform.clipboard_text[len] = '\0'; + if (!g_platform.clipboard.enable_text) { + LOG_error("Clipboard text not enabled."); + return; + } + + resize_dynamic_array_exact(&g_platform.clipboard.text_len, (void **) &g_platform.clipboard.text, 1, len + 1); + + if (g_platform.clipboard.text_len != len + 1) + // Out of memory + return; + + mem_cpy_(g_platform.clipboard.text, data, len); + g_platform.clipboard.text[len] = '\0'; } // ================================================================ @@ -2581,11 +2603,18 @@ b8 bmp_write_to_memory(i64 *dst_len, u8 *dst, i64 width, i64 height, i64 stride, i64 headers_size = sizeof(BMP_File_Header_) + sizeof(BMP_Info_Header_); i64 file_size = headers_size + image_size; - *dst_len = file_size; - - if (dst == NULL) + if (dst == NULL) { // Only calculate required file size. + *dst_len = file_size; return 1; + } + + if (*dst_len < file_size) { + *dst_len = file_size; + + LOG_error("Not enough space."); + return 0; + } BMP_File_Header_ file_header = { .signature = BMP_Signature_, @@ -2642,32 +2671,352 @@ b8 bmp_write_to_memory(i64 *dst_len, u8 *dst, i64 width, i64 height, i64 stride, return 1; } +static b8 parse_u64_dec_(u64 *dst, c8 **src, c8 *eof) { + if (src == NULL || *src == NULL || eof == NULL) { + LOG_error("Sanity"); + return 0; + } + + if (*src >= eof || **src < '0' || **src > '9') + return 0; + + if (dst != NULL) { + *dst = 0; + while (*src < eof && **src >= '0' && **src <= '9') { + *dst *= 10; + *dst += **src - '0'; + ++*src; + } + } else + while (*src < eof && **src >= '0' && **src <= '9') + ++*src; + + return 1; +} + +static b8 parse_u1_(u8 *dst, c8 **src, c8 *eof) { + if (src == NULL || *src == NULL || eof == NULL) { + LOG_error("Sanity"); + return 0; + } + + if (*src >= eof || **src < '0' || **src > '1') + return 0; + + if (dst != NULL) + *dst = **src == '0' ? 0 : 1; + + ++*src; + + return 1; +} + +static void ppm_skip_spaces_(c8 **src, c8 *eof) { + if (src == NULL || *src == NULL || eof == NULL) { + LOG_error("Sanity"); + return; + } + + while (*src < eof && (**src == ' ' || **src == '\r' || **src == '\n')) + if (**src == '#') + do + ++*src; + while (*src < eof && **src != '\n'); + else + ++*src; +} + +static b8 ppm_parse_u64_(u64 *dst, c8 **src, c8 *eof) { + ppm_skip_spaces_(src, eof); + return parse_u64_dec_(dst, src, eof); +} + +static b8 ppm_parse_u1_(u8 *dst, c8 **src, c8 *eof) { + ppm_skip_spaces_(src, eof); + return parse_u1_(dst, src, eof); +} + b8 ppm_read_from_memory(i64 *width, i64 *height, i64 pixels_len, vec4_f32 *pixels, i64 src_len, u8 *src) { - // TODO + // NOTE: Binary Netpbm formats don't make sense so we ignore them. - (void) width; - (void) height; - (void) pixels_len; - (void) pixels; - (void) src_len; - (void) src; + if ((width == NULL) != (height == NULL)) { + LOG_error("Sanity"); + return 0; + } - LOG_error("Not implemented."); - return 0; + if (src_len <= 0 || src == NULL) { + LOG_error("Sanity"); + return 0; + } + + if (src_len < 3) { + LOG_error("File too small."); + return 0; + } + + if (src[0] != 'P' && (src[1] < '1' || src[1] > '7')) { + LOG_error("Invalid magic number."); + return 0; + } + + if (src[1] != '1' && src[1] != '2' && src[1] != '3') { + LOG_error("Unsupported format."); + return 0; + } + + c8 *s = (c8 *) src + 2; + c8 *eof = (c8 *) src + src_len; + + u64 image_width, image_height; + + if (!ppm_parse_u64_(&image_width, &s, eof) || !ppm_parse_u64_(&image_height, &s, eof)) { + LOG_error("Failed to parse width and height."); + return 0; + } + + if ((image_width & 0x8000000000000000ull) != 0 || (image_height & 0x8000000000000000) != 0) { + LOG_error("Too large width and height."); + return 0; + } + + u64 pixel_depth = 1; + + if (src[1] == '2' || src[1] == '3') + if (!ppm_parse_u64_(&pixel_depth, &s, eof)) { + LOG_error("Failed to parse pixel depth."); + return 0; + } + + if (pixel_depth == 0 || pixel_depth > 0xffffu) { + LOG_error("Invalid pixel depth value."); + return 0; + } + + *width = image_width; + *height = image_height; + + if (pixels_len == 0 && pixels == NULL) + // Only validate format and return width and height. + return 1; + + if ((i64) image_width * (i64) image_height > pixels_len) { + LOG_error("Not enough space."); + return 0; + } + + switch (src[1]) { + case '1': { + // Plain bitmap + + for (u64 j = 0; j < image_height; ++j) + for (u64 i = 0; i < image_width; ++i) { + u8 bit; + if (!ppm_parse_u1_(&bit, &s, eof)) { + LOG_error("Failed to parse pixels."); + return 0; + } + pixels[j * image_width + i] = (vec4_f32) { + bit ? 1.0f : 0.0f, + bit ? 1.0f : 0.0f, + bit ? 1.0f : 0.0f, + 1.0f, + }; + } + } break; + + case '2': { + // Plain graymap + + for (u64 j = 0; j < image_height; ++j) + for (u64 i = 0; i < image_width; ++i) { + u64 value; + if (!ppm_parse_u64_(&value, &s, eof)) { + LOG_error("Failed to parse pixel value."); + return 0; + } + if (value > pixel_depth) { + LOG_error("Invalid pixel value."); + return 0; + } + f32 amount = (f32) value / (f32) pixel_depth; + pixels[j * image_width + i] = (vec4_f32) { + amount, + amount, + amount, + 1.0f, + }; + } + } break; + + case '3': { + // Plain pixmap + + for (u64 j = 0; j < image_height; ++j) + for (u64 i = 0; i < image_width; ++i) { + u64 r, g, b; + if (!ppm_parse_u64_(&r, &s, eof) || !ppm_parse_u64_(&g, &s, eof) || !ppm_parse_u64_(&b, &s, eof)) { + LOG_error("Failed to parse pixel values."); + return 0; + } + if (r > pixel_depth || g > pixel_depth || b > pixel_depth) { + LOG_error("Invalid pixel value."); + return 0; + } + pixels[j * image_width + i] = (vec4_f32) { + (f32) r / (f32) pixel_depth, + (f32) g / (f32) pixel_depth, + (f32) b / (f32) pixel_depth, + 1.0f, + }; + } + } break; + + default: + LOG_error("Sanity"); + return 0; + } + + ppm_skip_spaces_(&s, eof); + + if (s < eof) { + LOG_error("Unexpected characters."); + return 0; + } + + return 1; +} + +static i64 len_of_printed_u64_dec_(u64 x) { + i64 len = 1; + + while (x >= 10) { + ++len; + x /= 10; + } + + return len; +} + +static b8 print_u64_dec_(c8 **dst, c8 *eof, u64 x) { + if (dst == NULL || *dst == NULL || eof == NULL) { + LOG_error("Sanity"); + return 0; + } + + u64 m = 1; + while (x / m >= 10) + m *= 10; + + while (m >= 10) { + if (*dst >= eof) { + LOG_error("EOF"); + return 0; + } + + **dst = '0' + (x / m); + ++*dst; + + x -= (x / m) * m; + m /= 10; + } + + if (*dst >= eof) { + LOG_error("EOF"); + return 0; + } + + **dst = '0' + x; + ++*dst; + + return 1; +} + +static b8 print_str_(c8 **dst, c8 *eof, i64 src_len, c8 *src) { + if (dst == NULL || *dst == NULL || eof == NULL || src == NULL || src_len < 0) { + LOG_error("Sanity"); + return 0; + } + + if (*dst + src_len > eof) + return 0; + + c8 *s_end = src + src_len; + for (; src < s_end; ++*dst, ++src) + **dst = *src; + + return 1; } b8 ppm_write_to_memory(i64 *dst_len, u8 *dst, i64 width, i64 height, i64 stride, vec4_f32 *pixels) { - // TODO + if (dst_len == NULL || pixels == NULL) { + LOG_error("Sanity"); + return 0; + } - (void) dst_len; - (void) dst; - (void) width; - (void) height; - (void) stride; - (void) pixels; + if (width <= 0 || height <= 0 || stride == 0) { + LOG_error("Sanity"); + return 0; + } - LOG_error("Not implemented."); - return 0; + i64 file_size = 0; + + file_size += 3; // magic number + file_size += 1 + len_of_printed_u64_dec_((u64) width); + file_size += 1 + len_of_printed_u64_dec_((u64) height); + file_size += 4; // 255 \n + + for (i64 j = 0; j < height; ++j) + for (i64 i = 0; i < width; ++i) { + u32 r = (u32) (pixels[j * stride + i].x * 255.0 + 0.5); + u32 g = (u32) (pixels[j * stride + i].y * 255.0 + 0.5); + u32 b = (u32) (pixels[j * stride + i].z * 255.0 + 0.5); + + file_size += 1 + len_of_printed_u64_dec_(r); + file_size += 1 + len_of_printed_u64_dec_(g); + file_size += 1 + len_of_printed_u64_dec_(b); + } + + if (dst == NULL) { + // Only calculate required file size. + *dst_len = file_size; + return 1; + } + + if (*dst_len < file_size) { + *dst_len = file_size; + + LOG_error("Not enough space."); + return 0; + } + + c8 *d = (c8 *) dst; + c8 *eof = (c8 *) dst + file_size; + + if (!print_str_ (&d, eof, 3, "P3\n") + || !print_u64_dec_(&d, eof, (u64) width) || !print_str_(&d, eof, 1, " ") + || !print_u64_dec_(&d, eof, (u64) height) || !print_str_(&d, eof, 1, " ") + || !print_u64_dec_(&d, eof, 255) || !print_str_(&d, eof, 1, "\n") + ) { + LOG_error("Sanity"); + return 0; + } + + for (i64 j = 0; j < height; ++j) + for (i64 i = 0; i < width; ++i) { + u32 r = (u32) (pixels[j * stride + i].x * 255.0 + 0.5); + u32 g = (u32) (pixels[j * stride + i].y * 255.0 + 0.5); + u32 b = (u32) (pixels[j * stride + i].z * 255.0 + 0.5); + + if (!print_u64_dec_(&d, eof, r) || !print_str_(&d, eof, 1, " ") + || !print_u64_dec_(&d, eof, g) || !print_str_(&d, eof, 1, " ") + || !print_u64_dec_(&d, eof, b) || !print_str_(&d, eof, 1, "\n") + ) { + LOG_error("Sanity"); + return 0; + } + } + + return 1; } b8 wav_read_from_memory(i64 *sample_rate_hz, i64 *num_channels, i64 *num_samples, i64 frames_len, f32 *frames, i64 src_len, u8 *src) { @@ -3493,11 +3842,13 @@ typedef struct { } WL_Output_; static i32 anonymous_shm_open(void) { - c8 name[13] = "scr_XXXXXX"; + c8 name[13] = "scr_000000"; i32 retries = 1000000; for (i32 i = 0; i < retries; ++i) { - snprintf(name + 4, 7, "%06d", i); + i64 n = len_of_printed_u64_dec_(i); + c8 *d = name + sizeof name - 1 - n; + print_u64_dec_(&d, name + sizeof name - 1, i); i32 fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); if (fd >= 0) { @@ -4203,14 +4554,17 @@ void shutdown_all_systems(void) { close_all_dynamic_libraries_(); g_platform.input_len = 0; - resize_dynamic_array_exact(&g_platform.input_capacity, (void **) &g_platform.input, sizeof *g_platform.input, 0); - resize_dynamic_array_exact(&g_platform.clipboard_text_len, (void **) &g_platform.clipboard_text, 1, 0); + resize_dynamic_array_exact(&g_platform.input_capacity, (void **) &g_platform.input, sizeof *g_platform.input, 0); + + resize_dynamic_array_exact(&g_platform.clipboard.text_len, (void **) &g_platform.clipboard.text, 1, 0); + resize_dynamic_array_exact(&g_platform.clipboard.image_len, (void **) &g_platform.clipboard.image, 1, 0); + resize_dynamic_array_exact(&g_platform.clipboard.sound_len, (void **) &g_platform.clipboard.sound, 1, 0); } void request_clipboard_(void) { if (_requested_clipboard) return; - if (!g_platform.enable_clipboard_text && !g_platform.enable_clipboard_image && !g_platform.enable_clipboard_sound) + if (!g_platform.clipboard.enable_text && !g_platform.clipboard.enable_image && !g_platform.clipboard.enable_sound) return; XConvertSelection(_display, _clipboard, _targets, _clipboard, _window, CurrentTime); @@ -4375,9 +4729,9 @@ i32 handle_main_window_events(void) { XGetSelectionOwner(_display, _clipboard) == _window) { if (ev.xselectionrequest.property != None) { if (ev.xselectionrequest.target == _targets) { - b8 has_text = g_platform.clipboard_text_len > 0; - b8 has_image = g_platform.clipboard_image_len > 0; - b8 has_sound = g_platform.clipboard_sound_len > 0; + b8 has_text = g_platform.clipboard.text_len > 0; + b8 has_image = g_platform.clipboard.image_len > 0; + b8 has_sound = g_platform.clipboard.sound_len > 0; if (!has_text && !has_image && !has_sound) XDeleteProperty( @@ -4412,8 +4766,8 @@ i32 handle_main_window_events(void) { ev.xselectionrequest.target, 8, PropModeReplace, - (u8 *) g_platform.clipboard_text, - g_platform.clipboard_text_len + (u8 *) g_platform.clipboard.text, + g_platform.clipboard.text_len ); } else if (ev.xselectionrequest.target == _image_bmp) { // TODO @@ -4547,40 +4901,40 @@ i32 handle_main_window_events(void) { break; } - if (g_platform.enable_clipboard_text && target_text != None) + if (g_platform.clipboard.enable_text && target_text != None) XConvertSelection(_display, _clipboard, target_text, _clipboard, _window, CurrentTime); - if (g_platform.enable_clipboard_image && target_image != None) + if (g_platform.clipboard.enable_image && target_image != None) XConvertSelection(_display, _clipboard, target_image, _clipboard, _window, CurrentTime); - if (g_platform.enable_clipboard_sound && target_sound != None) + if (g_platform.clipboard.enable_sound && target_sound != None) XConvertSelection(_display, _clipboard, target_sound, _clipboard, _window, CurrentTime); if (target_text == None && target_image == None && target_sound == None) _requested_clipboard = 0; } else if (ev.xselection.target == XA_STRING || ev.xselection.target == _text_plain || ev.xselection.target == _utf8_string) { - if (g_platform.enable_clipboard_text) { - resize_dynamic_array_exact(&g_platform.clipboard_text_len, (void **) &g_platform.clipboard_text, 1, len + 1); - if (g_platform.clipboard_text_len == len + 1) { - mem_cpy_(g_platform.clipboard_text, data, len); - g_platform.clipboard_text[len] = '\0'; + if (g_platform.clipboard.enable_text) { + resize_dynamic_array_exact(&g_platform.clipboard.text_len, (void **) &g_platform.clipboard.text, 1, len + 1); + if (g_platform.clipboard.text_len == len + 1) { + mem_cpy_(g_platform.clipboard.text, data, len); + g_platform.clipboard.text[len] = '\0'; } } _requested_clipboard = 0; } else if (ev.xselection.target == _image_bmp) { - if (g_platform.enable_clipboard_image) { + if (g_platform.clipboard.enable_image) { // TODO LOG_error("Receive BMP image - not implemented."); } _requested_clipboard = 0; } else if (ev.xselection.target == _image_ppm) { - if (g_platform.enable_clipboard_image) { + if (g_platform.clipboard.enable_image) { // TODO LOG_error("Receive PPM image - not implemented."); } _requested_clipboard = 0; } else if (ev.xselection.target == _audio_wav) { - if (g_platform.enable_clipboard_sound) { + if (g_platform.clipboard.enable_sound) { // TODO LOG_error("Receive WAV audio - not implemented."); } @@ -4785,7 +5139,7 @@ void write_clipboard_text(i64 size, c8 *data) { store_clipboard_text_(size, data); - if (g_platform.clipboard_text_len > 0) + if (g_platform.clipboard.text_len > 0) XSetSelectionOwner(_display, _clipboard, _window, CurrentTime); } @@ -5757,6 +6111,8 @@ TEST("BMP image") { resize_dynamic_array_exact(&pixels_len, (void **) &pixels, sizeof *pixels, 0); resize_dynamic_array_exact(&bytes_len, (void **) &bytes, sizeof *bytes, 0); + + // TODO: Test with actual images. } TEST("PPM image") { @@ -5822,6 +6178,8 @@ TEST("PPM image") { resize_dynamic_array_exact(&pixels_len, (void **) &pixels, sizeof *pixels, 0); resize_dynamic_array_exact(&bytes_len, (void **) &bytes, sizeof *bytes, 0); + + // TODO: Test with actual images. } TEST("WAV audio") { @@ -5889,6 +6247,8 @@ TEST("WAV audio") { resize_dynamic_array_exact(&frames_len, (void **) &frames, sizeof *frames, 0); resize_dynamic_array_exact(&bytes_len, (void **) &bytes, sizeof *bytes, 0); + + // TODO: Test with actual wav files. } #undef TEST_FILE |